passagemath-graphs 10.6.1rc1__cp310-cp310-musllinux_1_2_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
- passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
- passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
- passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
- passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
- passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
- passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
- sage/all__sagemath_graphs.py +39 -0
- sage/combinat/abstract_tree.py +2723 -0
- sage/combinat/all__sagemath_graphs.py +34 -0
- sage/combinat/binary_tree.py +5306 -0
- sage/combinat/cluster_algebra_quiver/all.py +22 -0
- sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
- sage/combinat/cluster_algebra_quiver/interact.py +124 -0
- sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
- sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
- sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
- sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
- sage/combinat/designs/MOLS_handbook_data.py +570 -0
- sage/combinat/designs/all.py +58 -0
- sage/combinat/designs/bibd.py +1655 -0
- sage/combinat/designs/block_design.py +1071 -0
- sage/combinat/designs/covering_array.py +269 -0
- sage/combinat/designs/covering_design.py +530 -0
- sage/combinat/designs/database.py +5615 -0
- sage/combinat/designs/design_catalog.py +122 -0
- sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/designs_pyx.pxd +21 -0
- sage/combinat/designs/designs_pyx.pyx +993 -0
- sage/combinat/designs/difference_family.py +3951 -0
- sage/combinat/designs/difference_matrices.py +279 -0
- sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
- sage/combinat/designs/ext_rep.py +1064 -0
- sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
- sage/combinat/designs/group_divisible_designs.py +361 -0
- sage/combinat/designs/incidence_structures.py +2357 -0
- sage/combinat/designs/latin_squares.py +581 -0
- sage/combinat/designs/orthogonal_arrays.py +2244 -0
- sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
- sage/combinat/designs/resolvable_bibd.py +815 -0
- sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
- sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/subhypergraph_search.pyx +530 -0
- sage/combinat/designs/twographs.py +306 -0
- sage/combinat/finite_state_machine.py +14874 -0
- sage/combinat/finite_state_machine_generators.py +2006 -0
- sage/combinat/graph_path.py +448 -0
- sage/combinat/interval_posets.py +3908 -0
- sage/combinat/nu_tamari_lattice.py +269 -0
- sage/combinat/ordered_tree.py +1446 -0
- sage/combinat/posets/all.py +46 -0
- sage/combinat/posets/bubble_shuffle.py +247 -0
- sage/combinat/posets/cartesian_product.py +493 -0
- sage/combinat/posets/d_complete.py +182 -0
- sage/combinat/posets/elements.py +273 -0
- sage/combinat/posets/forest.py +30 -0
- sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/hasse_cython.pyx +174 -0
- sage/combinat/posets/hasse_diagram.py +3672 -0
- sage/combinat/posets/hochschild_lattice.py +158 -0
- sage/combinat/posets/incidence_algebras.py +794 -0
- sage/combinat/posets/lattices.py +5117 -0
- sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/linear_extension_iterator.pyx +292 -0
- sage/combinat/posets/linear_extensions.py +1037 -0
- sage/combinat/posets/mobile.py +275 -0
- sage/combinat/posets/moebius_algebra.py +776 -0
- sage/combinat/posets/poset_examples.py +2178 -0
- sage/combinat/posets/posets.py +9360 -0
- sage/combinat/rooted_tree.py +1070 -0
- sage/combinat/shard_order.py +239 -0
- sage/combinat/tamari_lattices.py +384 -0
- sage/combinat/yang_baxter_graph.py +923 -0
- sage/databases/all__sagemath_graphs.py +1 -0
- sage/databases/knotinfo_db.py +1231 -0
- sage/ext_data/all__sagemath_graphs.py +1 -0
- sage/ext_data/graphs/graph_plot_js.html +330 -0
- sage/ext_data/kenzo/CP2.txt +45 -0
- sage/ext_data/kenzo/CP3.txt +349 -0
- sage/ext_data/kenzo/CP4.txt +4774 -0
- sage/ext_data/kenzo/README.txt +49 -0
- sage/ext_data/kenzo/S4.txt +20 -0
- sage/graphs/all.py +42 -0
- sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/asteroidal_triples.pyx +320 -0
- sage/graphs/base/all.py +1 -0
- sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/boost_graph.pxd +106 -0
- sage/graphs/base/boost_graph.pyx +3045 -0
- sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/c_graph.pxd +106 -0
- sage/graphs/base/c_graph.pyx +5096 -0
- sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/dense_graph.pxd +28 -0
- sage/graphs/base/dense_graph.pyx +801 -0
- sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/graph_backends.pxd +5 -0
- sage/graphs/base/graph_backends.pyx +797 -0
- sage/graphs/base/overview.py +85 -0
- sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/sparse_graph.pxd +90 -0
- sage/graphs/base/sparse_graph.pyx +1653 -0
- sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_dense_graph.pxd +5 -0
- sage/graphs/base/static_dense_graph.pyx +1032 -0
- sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_backend.pxd +27 -0
- sage/graphs/base/static_sparse_backend.pyx +1583 -0
- sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_graph.pxd +37 -0
- sage/graphs/base/static_sparse_graph.pyx +1375 -0
- sage/graphs/bipartite_graph.py +2732 -0
- sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/centrality.pyx +1038 -0
- sage/graphs/cographs.py +519 -0
- sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/comparability.pyx +851 -0
- sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/connectivity.pxd +157 -0
- sage/graphs/connectivity.pyx +4813 -0
- sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/convexity_properties.pxd +16 -0
- sage/graphs/convexity_properties.pyx +870 -0
- sage/graphs/digraph.py +4754 -0
- sage/graphs/digraph_generators.py +1993 -0
- sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/distances_all_pairs.pxd +12 -0
- sage/graphs/distances_all_pairs.pyx +2938 -0
- sage/graphs/domination.py +1363 -0
- sage/graphs/dot2tex_utils.py +100 -0
- sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/edge_connectivity.pyx +1215 -0
- sage/graphs/generators/all.py +1 -0
- sage/graphs/generators/basic.py +1769 -0
- sage/graphs/generators/chessboard.py +538 -0
- sage/graphs/generators/classical_geometries.py +1611 -0
- sage/graphs/generators/degree_sequence.py +235 -0
- sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generators/distance_regular.pyx +2846 -0
- sage/graphs/generators/families.py +4759 -0
- sage/graphs/generators/intersection.py +565 -0
- sage/graphs/generators/platonic_solids.py +262 -0
- sage/graphs/generators/random.py +2623 -0
- sage/graphs/generators/smallgraphs.py +5741 -0
- sage/graphs/generators/world_map.py +724 -0
- sage/graphs/generic_graph.py +26867 -0
- sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generic_graph_pyx.pxd +34 -0
- sage/graphs/generic_graph_pyx.pyx +1673 -0
- sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/genus.pyx +622 -0
- sage/graphs/graph.py +9645 -0
- sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_coloring.pyx +2284 -0
- sage/graphs/graph_database.py +1177 -0
- sage/graphs/graph_decompositions/all.py +1 -0
- sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
- sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
- sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
- sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
- sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
- sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/graph_products.pyx +508 -0
- sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
- sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
- sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
- sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
- sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
- sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
- sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
- sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
- sage/graphs/graph_editor.py +82 -0
- sage/graphs/graph_generators.py +3314 -0
- sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_generators_pyx.pyx +95 -0
- sage/graphs/graph_input.py +812 -0
- sage/graphs/graph_latex.py +2064 -0
- sage/graphs/graph_list.py +410 -0
- sage/graphs/graph_plot.py +1756 -0
- sage/graphs/graph_plot_js.py +338 -0
- sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/hyperbolicity.pyx +1704 -0
- sage/graphs/hypergraph_generators.py +364 -0
- sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/independent_sets.pxd +13 -0
- sage/graphs/independent_sets.pyx +402 -0
- sage/graphs/isgci.py +1033 -0
- sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/isoperimetric_inequalities.pyx +489 -0
- sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/line_graph.pyx +743 -0
- sage/graphs/lovasz_theta.py +77 -0
- sage/graphs/matching.py +1633 -0
- sage/graphs/matching_covered_graph.py +3590 -0
- sage/graphs/orientations.py +1489 -0
- sage/graphs/partial_cube.py +459 -0
- sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/path_enumeration.pyx +2040 -0
- sage/graphs/pq_trees.py +1129 -0
- sage/graphs/print_graphs.py +201 -0
- sage/graphs/schnyder.py +865 -0
- sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/spanning_tree.pyx +1457 -0
- sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/strongly_regular_db.pyx +3340 -0
- sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/traversals.pxd +9 -0
- sage/graphs/traversals.pyx +1872 -0
- sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/trees.pxd +15 -0
- sage/graphs/trees.pyx +310 -0
- sage/graphs/tutte_polynomial.py +713 -0
- sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/views.pyx +794 -0
- sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/weakly_chordal.pyx +604 -0
- sage/groups/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
- sage/knots/all.py +6 -0
- sage/knots/free_knotinfo_monoid.py +507 -0
- sage/knots/gauss_code.py +291 -0
- sage/knots/knot.py +682 -0
- sage/knots/knot_table.py +284 -0
- sage/knots/knotinfo.py +2900 -0
- sage/knots/link.py +4715 -0
- sage/sandpiles/all.py +13 -0
- sage/sandpiles/examples.py +225 -0
- sage/sandpiles/sandpile.py +6365 -0
- sage/topology/all.py +22 -0
- sage/topology/cell_complex.py +1214 -0
- sage/topology/cubical_complex.py +1976 -0
- sage/topology/delta_complex.py +1806 -0
- sage/topology/filtered_simplicial_complex.py +744 -0
- sage/topology/moment_angle_complex.py +823 -0
- sage/topology/simplicial_complex.py +5160 -0
- sage/topology/simplicial_complex_catalog.py +92 -0
- sage/topology/simplicial_complex_examples.py +1680 -0
- sage/topology/simplicial_complex_homset.py +205 -0
- sage/topology/simplicial_complex_morphism.py +836 -0
- sage/topology/simplicial_set.py +4102 -0
- sage/topology/simplicial_set_catalog.py +55 -0
- sage/topology/simplicial_set_constructions.py +2954 -0
- sage/topology/simplicial_set_examples.py +865 -0
- sage/topology/simplicial_set_morphism.py +1464 -0
@@ -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)
|