passagemath-combinat 10.6.42__cp314-cp314-musllinux_1_2_x86_64.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 (400) hide show
  1. passagemath_combinat/__init__.py +3 -0
  2. passagemath_combinat-10.6.42.dist-info/METADATA +160 -0
  3. passagemath_combinat-10.6.42.dist-info/RECORD +400 -0
  4. passagemath_combinat-10.6.42.dist-info/WHEEL +5 -0
  5. passagemath_combinat-10.6.42.dist-info/top_level.txt +3 -0
  6. passagemath_combinat.libs/libgmp-0e7fc84e.so.10.5.0 +0 -0
  7. passagemath_combinat.libs/libsymmetrica-81fe8739.so.3.0.0 +0 -0
  8. sage/algebras/affine_nil_temperley_lieb.py +263 -0
  9. sage/algebras/all.py +24 -0
  10. sage/algebras/all__sagemath_combinat.py +35 -0
  11. sage/algebras/askey_wilson.py +935 -0
  12. sage/algebras/associated_graded.py +345 -0
  13. sage/algebras/cellular_basis.py +350 -0
  14. sage/algebras/cluster_algebra.py +2766 -0
  15. sage/algebras/down_up_algebra.py +860 -0
  16. sage/algebras/free_algebra.py +1698 -0
  17. sage/algebras/free_algebra_element.py +345 -0
  18. sage/algebras/free_algebra_quotient.py +405 -0
  19. sage/algebras/free_algebra_quotient_element.py +295 -0
  20. sage/algebras/free_zinbiel_algebra.py +885 -0
  21. sage/algebras/hall_algebra.py +783 -0
  22. sage/algebras/hecke_algebras/all.py +4 -0
  23. sage/algebras/hecke_algebras/ariki_koike_algebra.py +1796 -0
  24. sage/algebras/hecke_algebras/ariki_koike_specht_modules.py +475 -0
  25. sage/algebras/hecke_algebras/cubic_hecke_algebra.py +3520 -0
  26. sage/algebras/hecke_algebras/cubic_hecke_base_ring.py +1473 -0
  27. sage/algebras/hecke_algebras/cubic_hecke_matrix_rep.py +1079 -0
  28. sage/algebras/iwahori_hecke_algebra.py +3095 -0
  29. sage/algebras/jordan_algebra.py +1773 -0
  30. sage/algebras/lie_conformal_algebras/abelian_lie_conformal_algebra.py +113 -0
  31. sage/algebras/lie_conformal_algebras/affine_lie_conformal_algebra.py +156 -0
  32. sage/algebras/lie_conformal_algebras/all.py +18 -0
  33. sage/algebras/lie_conformal_algebras/bosonic_ghosts_lie_conformal_algebra.py +134 -0
  34. sage/algebras/lie_conformal_algebras/examples.py +43 -0
  35. sage/algebras/lie_conformal_algebras/fermionic_ghosts_lie_conformal_algebra.py +131 -0
  36. sage/algebras/lie_conformal_algebras/finitely_freely_generated_lca.py +139 -0
  37. sage/algebras/lie_conformal_algebras/free_bosons_lie_conformal_algebra.py +174 -0
  38. sage/algebras/lie_conformal_algebras/free_fermions_lie_conformal_algebra.py +167 -0
  39. sage/algebras/lie_conformal_algebras/freely_generated_lie_conformal_algebra.py +107 -0
  40. sage/algebras/lie_conformal_algebras/graded_lie_conformal_algebra.py +135 -0
  41. sage/algebras/lie_conformal_algebras/lie_conformal_algebra.py +353 -0
  42. sage/algebras/lie_conformal_algebras/lie_conformal_algebra_element.py +236 -0
  43. sage/algebras/lie_conformal_algebras/lie_conformal_algebra_with_basis.py +78 -0
  44. sage/algebras/lie_conformal_algebras/lie_conformal_algebra_with_structure_coefs.py +328 -0
  45. sage/algebras/lie_conformal_algebras/n2_lie_conformal_algebra.py +117 -0
  46. sage/algebras/lie_conformal_algebras/neveu_schwarz_lie_conformal_algebra.py +86 -0
  47. sage/algebras/lie_conformal_algebras/virasoro_lie_conformal_algebra.py +82 -0
  48. sage/algebras/lie_conformal_algebras/weyl_lie_conformal_algebra.py +205 -0
  49. sage/algebras/nil_coxeter_algebra.py +191 -0
  50. sage/algebras/q_commuting_polynomials.py +673 -0
  51. sage/algebras/q_system.py +608 -0
  52. sage/algebras/quantum_clifford.py +959 -0
  53. sage/algebras/quantum_groups/ace_quantum_onsager.py +693 -0
  54. sage/algebras/quantum_groups/all.py +9 -0
  55. sage/algebras/quantum_groups/fock_space.py +2219 -0
  56. sage/algebras/quantum_groups/q_numbers.py +207 -0
  57. sage/algebras/quantum_groups/quantum_group_gap.py +2695 -0
  58. sage/algebras/quantum_groups/representations.py +591 -0
  59. sage/algebras/quantum_matrix_coordinate_algebra.py +1006 -0
  60. sage/algebras/quantum_oscillator.py +623 -0
  61. sage/algebras/quaternion_algebra.py +20 -0
  62. sage/algebras/quaternion_algebra_element.py +55 -0
  63. sage/algebras/rational_cherednik_algebra.py +525 -0
  64. sage/algebras/schur_algebra.py +670 -0
  65. sage/algebras/shuffle_algebra.py +1011 -0
  66. sage/algebras/splitting_algebra.py +779 -0
  67. sage/algebras/tensor_algebra.py +709 -0
  68. sage/algebras/yangian.py +1082 -0
  69. sage/algebras/yokonuma_hecke_algebra.py +1018 -0
  70. sage/all__sagemath_combinat.py +35 -0
  71. sage/combinat/SJT.py +255 -0
  72. sage/combinat/affine_permutation.py +2405 -0
  73. sage/combinat/algebraic_combinatorics.py +55 -0
  74. sage/combinat/all.py +53 -0
  75. sage/combinat/all__sagemath_combinat.py +195 -0
  76. sage/combinat/alternating_sign_matrix.py +2063 -0
  77. sage/combinat/baxter_permutations.py +346 -0
  78. sage/combinat/bijectionist.py +3220 -0
  79. sage/combinat/binary_recurrence_sequences.py +1180 -0
  80. sage/combinat/blob_algebra.py +685 -0
  81. sage/combinat/catalog_partitions.py +27 -0
  82. sage/combinat/chas/all.py +23 -0
  83. sage/combinat/chas/fsym.py +1180 -0
  84. sage/combinat/chas/wqsym.py +2601 -0
  85. sage/combinat/cluster_complex.py +326 -0
  86. sage/combinat/colored_permutations.py +2039 -0
  87. sage/combinat/colored_permutations_representations.py +964 -0
  88. sage/combinat/composition_signed.py +142 -0
  89. sage/combinat/composition_tableau.py +855 -0
  90. sage/combinat/constellation.py +1729 -0
  91. sage/combinat/core.py +751 -0
  92. sage/combinat/counting.py +12 -0
  93. sage/combinat/crystals/affine.py +742 -0
  94. sage/combinat/crystals/affine_factorization.py +518 -0
  95. sage/combinat/crystals/affinization.py +331 -0
  96. sage/combinat/crystals/alcove_path.py +2013 -0
  97. sage/combinat/crystals/all.py +22 -0
  98. sage/combinat/crystals/bkk_crystals.py +141 -0
  99. sage/combinat/crystals/catalog.py +115 -0
  100. sage/combinat/crystals/catalog_elementary_crystals.py +18 -0
  101. sage/combinat/crystals/catalog_infinity_crystals.py +33 -0
  102. sage/combinat/crystals/catalog_kirillov_reshetikhin.py +18 -0
  103. sage/combinat/crystals/crystals.py +257 -0
  104. sage/combinat/crystals/direct_sum.py +260 -0
  105. sage/combinat/crystals/elementary_crystals.py +1251 -0
  106. sage/combinat/crystals/fast_crystals.py +441 -0
  107. sage/combinat/crystals/fully_commutative_stable_grothendieck.py +1205 -0
  108. sage/combinat/crystals/generalized_young_walls.py +1076 -0
  109. sage/combinat/crystals/highest_weight_crystals.py +436 -0
  110. sage/combinat/crystals/induced_structure.py +695 -0
  111. sage/combinat/crystals/infinity_crystals.py +730 -0
  112. sage/combinat/crystals/kac_modules.py +863 -0
  113. sage/combinat/crystals/kirillov_reshetikhin.py +4196 -0
  114. sage/combinat/crystals/kyoto_path_model.py +497 -0
  115. sage/combinat/crystals/letters.cpython-314-x86_64-linux-musl.so +0 -0
  116. sage/combinat/crystals/letters.pxd +79 -0
  117. sage/combinat/crystals/letters.pyx +3056 -0
  118. sage/combinat/crystals/littelmann_path.py +1518 -0
  119. sage/combinat/crystals/monomial_crystals.py +1262 -0
  120. sage/combinat/crystals/multisegments.py +462 -0
  121. sage/combinat/crystals/mv_polytopes.py +467 -0
  122. sage/combinat/crystals/pbw_crystal.py +511 -0
  123. sage/combinat/crystals/pbw_datum.cpython-314-x86_64-linux-musl.so +0 -0
  124. sage/combinat/crystals/pbw_datum.pxd +4 -0
  125. sage/combinat/crystals/pbw_datum.pyx +487 -0
  126. sage/combinat/crystals/polyhedral_realization.py +372 -0
  127. sage/combinat/crystals/spins.cpython-314-x86_64-linux-musl.so +0 -0
  128. sage/combinat/crystals/spins.pxd +21 -0
  129. sage/combinat/crystals/spins.pyx +756 -0
  130. sage/combinat/crystals/star_crystal.py +290 -0
  131. sage/combinat/crystals/subcrystal.py +464 -0
  132. sage/combinat/crystals/tensor_product.py +1177 -0
  133. sage/combinat/crystals/tensor_product_element.cpython-314-x86_64-linux-musl.so +0 -0
  134. sage/combinat/crystals/tensor_product_element.pxd +35 -0
  135. sage/combinat/crystals/tensor_product_element.pyx +1870 -0
  136. sage/combinat/crystals/virtual_crystal.py +420 -0
  137. sage/combinat/cyclic_sieving_phenomenon.py +204 -0
  138. sage/combinat/debruijn_sequence.cpython-314-x86_64-linux-musl.so +0 -0
  139. sage/combinat/debruijn_sequence.pyx +355 -0
  140. sage/combinat/decorated_permutation.py +270 -0
  141. sage/combinat/degree_sequences.cpython-314-x86_64-linux-musl.so +0 -0
  142. sage/combinat/degree_sequences.pyx +588 -0
  143. sage/combinat/derangements.py +527 -0
  144. sage/combinat/descent_algebra.py +1008 -0
  145. sage/combinat/diagram.py +1551 -0
  146. sage/combinat/diagram_algebras.py +5886 -0
  147. sage/combinat/dyck_word.py +4349 -0
  148. sage/combinat/e_one_star.py +1623 -0
  149. sage/combinat/enumerated_sets.py +123 -0
  150. sage/combinat/expnums.cpython-314-x86_64-linux-musl.so +0 -0
  151. sage/combinat/expnums.pyx +148 -0
  152. sage/combinat/fast_vector_partitions.cpython-314-x86_64-linux-musl.so +0 -0
  153. sage/combinat/fast_vector_partitions.pyx +346 -0
  154. sage/combinat/fqsym.py +1977 -0
  155. sage/combinat/free_dendriform_algebra.py +954 -0
  156. sage/combinat/free_prelie_algebra.py +1141 -0
  157. sage/combinat/fully_commutative_elements.py +1077 -0
  158. sage/combinat/fully_packed_loop.py +1523 -0
  159. sage/combinat/gelfand_tsetlin_patterns.py +1409 -0
  160. sage/combinat/gray_codes.py +311 -0
  161. sage/combinat/grossman_larson_algebras.py +667 -0
  162. sage/combinat/growth.py +4352 -0
  163. sage/combinat/hall_polynomial.py +188 -0
  164. sage/combinat/hillman_grassl.py +866 -0
  165. sage/combinat/integer_matrices.py +329 -0
  166. sage/combinat/integer_vectors_mod_permgroup.py +1238 -0
  167. sage/combinat/k_tableau.py +4564 -0
  168. sage/combinat/kazhdan_lusztig.py +215 -0
  169. sage/combinat/key_polynomial.py +885 -0
  170. sage/combinat/knutson_tao_puzzles.py +2286 -0
  171. sage/combinat/lr_tableau.py +311 -0
  172. sage/combinat/matrices/all.py +24 -0
  173. sage/combinat/matrices/hadamard_matrix.py +3790 -0
  174. sage/combinat/matrices/latin.py +2912 -0
  175. sage/combinat/misc.py +401 -0
  176. sage/combinat/multiset_partition_into_sets_ordered.py +3541 -0
  177. sage/combinat/ncsf_qsym/all.py +21 -0
  178. sage/combinat/ncsf_qsym/combinatorics.py +317 -0
  179. sage/combinat/ncsf_qsym/generic_basis_code.py +1427 -0
  180. sage/combinat/ncsf_qsym/ncsf.py +5637 -0
  181. sage/combinat/ncsf_qsym/qsym.py +4053 -0
  182. sage/combinat/ncsf_qsym/tutorial.py +447 -0
  183. sage/combinat/ncsym/all.py +21 -0
  184. sage/combinat/ncsym/bases.py +855 -0
  185. sage/combinat/ncsym/dual.py +593 -0
  186. sage/combinat/ncsym/ncsym.py +2076 -0
  187. sage/combinat/necklace.py +551 -0
  188. sage/combinat/non_decreasing_parking_function.py +634 -0
  189. sage/combinat/nu_dyck_word.py +1474 -0
  190. sage/combinat/output.py +861 -0
  191. sage/combinat/parallelogram_polyomino.py +4326 -0
  192. sage/combinat/parking_functions.py +1602 -0
  193. sage/combinat/partition_algebra.py +1998 -0
  194. sage/combinat/partition_kleshchev.py +1982 -0
  195. sage/combinat/partition_shifting_algebras.py +584 -0
  196. sage/combinat/partition_tuple.py +3114 -0
  197. sage/combinat/path_tableaux/all.py +13 -0
  198. sage/combinat/path_tableaux/catalog.py +29 -0
  199. sage/combinat/path_tableaux/dyck_path.py +380 -0
  200. sage/combinat/path_tableaux/frieze.py +476 -0
  201. sage/combinat/path_tableaux/path_tableau.py +728 -0
  202. sage/combinat/path_tableaux/semistandard.py +510 -0
  203. sage/combinat/perfect_matching.py +779 -0
  204. sage/combinat/plane_partition.py +3300 -0
  205. sage/combinat/q_bernoulli.cpython-314-x86_64-linux-musl.so +0 -0
  206. sage/combinat/q_bernoulli.pyx +128 -0
  207. sage/combinat/quickref.py +81 -0
  208. sage/combinat/recognizable_series.py +2051 -0
  209. sage/combinat/regular_sequence.py +4316 -0
  210. sage/combinat/regular_sequence_bounded.py +543 -0
  211. sage/combinat/restricted_growth.py +81 -0
  212. sage/combinat/ribbon.py +20 -0
  213. sage/combinat/ribbon_shaped_tableau.py +489 -0
  214. sage/combinat/ribbon_tableau.py +1180 -0
  215. sage/combinat/rigged_configurations/all.py +46 -0
  216. sage/combinat/rigged_configurations/bij_abstract_class.py +548 -0
  217. sage/combinat/rigged_configurations/bij_infinity.py +370 -0
  218. sage/combinat/rigged_configurations/bij_type_A.py +163 -0
  219. sage/combinat/rigged_configurations/bij_type_A2_dual.py +338 -0
  220. sage/combinat/rigged_configurations/bij_type_A2_even.py +218 -0
  221. sage/combinat/rigged_configurations/bij_type_A2_odd.py +199 -0
  222. sage/combinat/rigged_configurations/bij_type_B.py +900 -0
  223. sage/combinat/rigged_configurations/bij_type_C.py +267 -0
  224. sage/combinat/rigged_configurations/bij_type_D.py +771 -0
  225. sage/combinat/rigged_configurations/bij_type_D_tri.py +392 -0
  226. sage/combinat/rigged_configurations/bij_type_D_twisted.py +576 -0
  227. sage/combinat/rigged_configurations/bij_type_E67.py +402 -0
  228. sage/combinat/rigged_configurations/bijection.py +143 -0
  229. sage/combinat/rigged_configurations/kleber_tree.py +1475 -0
  230. sage/combinat/rigged_configurations/kr_tableaux.py +1898 -0
  231. sage/combinat/rigged_configurations/rc_crystal.py +461 -0
  232. sage/combinat/rigged_configurations/rc_infinity.py +540 -0
  233. sage/combinat/rigged_configurations/rigged_configuration_element.py +2403 -0
  234. sage/combinat/rigged_configurations/rigged_configurations.py +1918 -0
  235. sage/combinat/rigged_configurations/rigged_partition.cpython-314-x86_64-linux-musl.so +0 -0
  236. sage/combinat/rigged_configurations/rigged_partition.pxd +15 -0
  237. sage/combinat/rigged_configurations/rigged_partition.pyx +680 -0
  238. sage/combinat/rigged_configurations/tensor_product_kr_tableaux.py +499 -0
  239. sage/combinat/rigged_configurations/tensor_product_kr_tableaux_element.py +428 -0
  240. sage/combinat/rsk.py +3438 -0
  241. sage/combinat/schubert_polynomial.py +508 -0
  242. sage/combinat/set_partition.py +3318 -0
  243. sage/combinat/set_partition_iterator.cpython-314-x86_64-linux-musl.so +0 -0
  244. sage/combinat/set_partition_iterator.pyx +136 -0
  245. sage/combinat/set_partition_ordered.py +1590 -0
  246. sage/combinat/sf/abreu_nigro.py +346 -0
  247. sage/combinat/sf/all.py +52 -0
  248. sage/combinat/sf/character.py +576 -0
  249. sage/combinat/sf/classical.py +319 -0
  250. sage/combinat/sf/dual.py +996 -0
  251. sage/combinat/sf/elementary.py +549 -0
  252. sage/combinat/sf/hall_littlewood.py +1028 -0
  253. sage/combinat/sf/hecke.py +336 -0
  254. sage/combinat/sf/homogeneous.py +464 -0
  255. sage/combinat/sf/jack.py +1428 -0
  256. sage/combinat/sf/k_dual.py +1458 -0
  257. sage/combinat/sf/kfpoly.py +447 -0
  258. sage/combinat/sf/llt.py +789 -0
  259. sage/combinat/sf/macdonald.py +2019 -0
  260. sage/combinat/sf/monomial.py +525 -0
  261. sage/combinat/sf/multiplicative.py +113 -0
  262. sage/combinat/sf/new_kschur.py +1786 -0
  263. sage/combinat/sf/ns_macdonald.py +964 -0
  264. sage/combinat/sf/orthogonal.py +246 -0
  265. sage/combinat/sf/orthotriang.py +355 -0
  266. sage/combinat/sf/powersum.py +963 -0
  267. sage/combinat/sf/schur.py +880 -0
  268. sage/combinat/sf/sf.py +1653 -0
  269. sage/combinat/sf/sfa.py +7053 -0
  270. sage/combinat/sf/symplectic.py +253 -0
  271. sage/combinat/sf/witt.py +721 -0
  272. sage/combinat/shifted_primed_tableau.py +2735 -0
  273. sage/combinat/shuffle.py +830 -0
  274. sage/combinat/sidon_sets.py +146 -0
  275. sage/combinat/similarity_class_type.py +1721 -0
  276. sage/combinat/sine_gordon.py +618 -0
  277. sage/combinat/six_vertex_model.py +784 -0
  278. sage/combinat/skew_partition.py +2053 -0
  279. sage/combinat/skew_tableau.py +2989 -0
  280. sage/combinat/sloane_functions.py +8935 -0
  281. sage/combinat/specht_module.py +1403 -0
  282. sage/combinat/species/all.py +48 -0
  283. sage/combinat/species/characteristic_species.py +321 -0
  284. sage/combinat/species/composition_species.py +273 -0
  285. sage/combinat/species/cycle_species.py +284 -0
  286. sage/combinat/species/empty_species.py +155 -0
  287. sage/combinat/species/functorial_composition_species.py +148 -0
  288. sage/combinat/species/generating_series.py +673 -0
  289. sage/combinat/species/library.py +148 -0
  290. sage/combinat/species/linear_order_species.py +169 -0
  291. sage/combinat/species/misc.py +83 -0
  292. sage/combinat/species/partition_species.py +290 -0
  293. sage/combinat/species/permutation_species.py +268 -0
  294. sage/combinat/species/product_species.py +423 -0
  295. sage/combinat/species/recursive_species.py +476 -0
  296. sage/combinat/species/set_species.py +192 -0
  297. sage/combinat/species/species.py +820 -0
  298. sage/combinat/species/structure.py +539 -0
  299. sage/combinat/species/subset_species.py +243 -0
  300. sage/combinat/species/sum_species.py +225 -0
  301. sage/combinat/subword.py +564 -0
  302. sage/combinat/subword_complex.py +2122 -0
  303. sage/combinat/subword_complex_c.cpython-314-x86_64-linux-musl.so +0 -0
  304. sage/combinat/subword_complex_c.pyx +119 -0
  305. sage/combinat/super_tableau.py +821 -0
  306. sage/combinat/superpartition.py +1154 -0
  307. sage/combinat/symmetric_group_algebra.py +3774 -0
  308. sage/combinat/symmetric_group_representations.py +1830 -0
  309. sage/combinat/t_sequences.py +877 -0
  310. sage/combinat/tableau.py +9506 -0
  311. sage/combinat/tableau_residues.py +860 -0
  312. sage/combinat/tableau_tuple.py +5353 -0
  313. sage/combinat/tiling.py +2432 -0
  314. sage/combinat/triangles_FHM.py +777 -0
  315. sage/combinat/tutorial.py +1857 -0
  316. sage/combinat/vector_partition.py +337 -0
  317. sage/combinat/words/abstract_word.py +1722 -0
  318. sage/combinat/words/all.py +59 -0
  319. sage/combinat/words/alphabet.py +268 -0
  320. sage/combinat/words/finite_word.py +7201 -0
  321. sage/combinat/words/infinite_word.py +113 -0
  322. sage/combinat/words/lyndon_word.py +652 -0
  323. sage/combinat/words/morphic.py +351 -0
  324. sage/combinat/words/morphism.py +3878 -0
  325. sage/combinat/words/paths.py +2932 -0
  326. sage/combinat/words/shuffle_product.py +278 -0
  327. sage/combinat/words/suffix_trees.py +1873 -0
  328. sage/combinat/words/word.py +769 -0
  329. sage/combinat/words/word_char.cpython-314-x86_64-linux-musl.so +0 -0
  330. sage/combinat/words/word_char.pyx +847 -0
  331. sage/combinat/words/word_datatypes.cpython-314-x86_64-linux-musl.so +0 -0
  332. sage/combinat/words/word_datatypes.pxd +4 -0
  333. sage/combinat/words/word_datatypes.pyx +1067 -0
  334. sage/combinat/words/word_generators.py +2026 -0
  335. sage/combinat/words/word_infinite_datatypes.py +1218 -0
  336. sage/combinat/words/word_options.py +99 -0
  337. sage/combinat/words/words.py +2396 -0
  338. sage/data_structures/all__sagemath_combinat.py +1 -0
  339. sage/databases/all__sagemath_combinat.py +13 -0
  340. sage/databases/findstat.py +4897 -0
  341. sage/databases/oeis.py +2058 -0
  342. sage/databases/sloane.py +393 -0
  343. sage/dynamics/all__sagemath_combinat.py +14 -0
  344. sage/dynamics/cellular_automata/all.py +7 -0
  345. sage/dynamics/cellular_automata/catalog.py +34 -0
  346. sage/dynamics/cellular_automata/elementary.py +612 -0
  347. sage/dynamics/cellular_automata/glca.py +477 -0
  348. sage/dynamics/cellular_automata/solitons.py +1463 -0
  349. sage/dynamics/finite_dynamical_system.py +1249 -0
  350. sage/dynamics/finite_dynamical_system_catalog.py +382 -0
  351. sage/games/all.py +7 -0
  352. sage/games/hexad.py +704 -0
  353. sage/games/quantumino.py +591 -0
  354. sage/games/sudoku.py +889 -0
  355. sage/games/sudoku_backtrack.cpython-314-x86_64-linux-musl.so +0 -0
  356. sage/games/sudoku_backtrack.pyx +189 -0
  357. sage/groups/all__sagemath_combinat.py +1 -0
  358. sage/groups/indexed_free_group.py +489 -0
  359. sage/libs/all__sagemath_combinat.py +6 -0
  360. sage/libs/lrcalc/__init__.py +1 -0
  361. sage/libs/lrcalc/lrcalc.py +525 -0
  362. sage/libs/symmetrica/__init__.py +7 -0
  363. sage/libs/symmetrica/all.py +101 -0
  364. sage/libs/symmetrica/kostka.pxi +168 -0
  365. sage/libs/symmetrica/part.pxi +193 -0
  366. sage/libs/symmetrica/plet.pxi +42 -0
  367. sage/libs/symmetrica/sab.pxi +196 -0
  368. sage/libs/symmetrica/sb.pxi +332 -0
  369. sage/libs/symmetrica/sc.pxi +192 -0
  370. sage/libs/symmetrica/schur.pxi +956 -0
  371. sage/libs/symmetrica/symmetrica.cpython-314-x86_64-linux-musl.so +0 -0
  372. sage/libs/symmetrica/symmetrica.pxi +1172 -0
  373. sage/libs/symmetrica/symmetrica.pyx +39 -0
  374. sage/monoids/all.py +13 -0
  375. sage/monoids/automatic_semigroup.py +1054 -0
  376. sage/monoids/free_abelian_monoid.py +315 -0
  377. sage/monoids/free_abelian_monoid_element.cpython-314-x86_64-linux-musl.so +0 -0
  378. sage/monoids/free_abelian_monoid_element.pxd +16 -0
  379. sage/monoids/free_abelian_monoid_element.pyx +397 -0
  380. sage/monoids/free_monoid.py +335 -0
  381. sage/monoids/free_monoid_element.py +431 -0
  382. sage/monoids/hecke_monoid.py +65 -0
  383. sage/monoids/string_monoid.py +817 -0
  384. sage/monoids/string_monoid_element.py +547 -0
  385. sage/monoids/string_ops.py +143 -0
  386. sage/monoids/trace_monoid.py +972 -0
  387. sage/rings/all__sagemath_combinat.py +2 -0
  388. sage/sat/all.py +4 -0
  389. sage/sat/boolean_polynomials.py +405 -0
  390. sage/sat/converters/__init__.py +6 -0
  391. sage/sat/converters/anf2cnf.py +14 -0
  392. sage/sat/converters/polybori.py +611 -0
  393. sage/sat/solvers/__init__.py +5 -0
  394. sage/sat/solvers/cryptominisat.py +287 -0
  395. sage/sat/solvers/dimacs.py +783 -0
  396. sage/sat/solvers/picosat.py +228 -0
  397. sage/sat/solvers/sat_lp.py +156 -0
  398. sage/sat/solvers/satsolver.cpython-314-x86_64-linux-musl.so +0 -0
  399. sage/sat/solvers/satsolver.pxd +3 -0
  400. sage/sat/solvers/satsolver.pyx +405 -0
@@ -0,0 +1,2766 @@
1
+ # sage_setup: distribution = sagemath-combinat
2
+ # sage.doctest: needs sage.graphs sage.modules
3
+ r"""
4
+ Cluster algebras
5
+
6
+ This file constructs cluster algebras using the Parent-Element framework.
7
+ The implementation mainly utilizes structural theorems from [FZ2007]_.
8
+
9
+ The key points being used here are these:
10
+
11
+ - cluster variables are parametrized by their g-vectors;
12
+
13
+ - g-vectors (together with c-vectors) provide a self-standing model for the
14
+ combinatorics behind any cluster algebra;
15
+
16
+ - each cluster variable in any cluster algebra can be computed, by the
17
+ separation of additions formula, from its g-vector and F-polynomial.
18
+
19
+ Accordingly this file provides three classes:
20
+
21
+ - :class:`ClusterAlgebra`
22
+
23
+ - :class:`ClusterAlgebraSeed`
24
+
25
+ - :class:`ClusterAlgebraElement`
26
+
27
+ :class:`ClusterAlgebra`, constructed as a subobject of
28
+ :class:`sage.rings.polynomial.laurent_polynomial_ring.LaurentPolynomialRing_generic`,
29
+ is the frontend of this implementation. It provides all the algebraic
30
+ features (like ring morphisms), it computes cluster variables, it is
31
+ responsible for controlling the exploration of the exchange graph and
32
+ serves as the repository for all the data recursively computed so far.
33
+ In particular, all g-vectors and all F-polynomials of known cluster
34
+ variables as well as a mutation path by which they can be obtained
35
+ are recorded. In the optic of efficiency, this implementation does not
36
+ store directly the exchange graph nor the exchange relations. Both of
37
+ these could be added to :class:`ClusterAlgebra` with minimal effort.
38
+
39
+ :class:`ClusterAlgebraSeed` provides the combinatorial backbone
40
+ for :class:`ClusterAlgebra`. It is an auxiliary class and therefore its
41
+ instances should **not** be directly created by the user. Rather it
42
+ should be accessed via :meth:`ClusterAlgebra.current_seed`
43
+ and :meth:`ClusterAlgebra.initial_seed`. The task of performing current
44
+ seed mutations is delegated to this class. Seeds are considered equal if
45
+ they have the same parent cluster algebra and they can be obtained from
46
+ each other by a permutation of their data (i.e. if they coincide as
47
+ unlabelled seeds). Cluster algebras whose initial seeds are equal in the
48
+ above sense are not considered equal but are endowed with coercion maps
49
+ to each other. More generally, a cluster algebra is endowed with coercion
50
+ maps from any cluster algebra which is obtained by freezing a collection
51
+ of initial cluster variables and/or permuting both cluster variables
52
+ and coefficients.
53
+
54
+ :class:`ClusterAlgebraElement` is a thin wrapper around
55
+ :class:`sage.rings.polynomial.laurent_polynomial.LaurentPolynomial`
56
+ providing all the functions specific to cluster variables.
57
+ Elements of a cluster algebra with principal coefficients have special methods
58
+ and these are grouped in the subclass :class:`PrincipalClusterAlgebraElement`.
59
+
60
+ One more remark about this implementation. Instances of
61
+ :class:`ClusterAlgebra` are built by identifying the initial cluster variables
62
+ with the generators of :meth:`ClusterAlgebra.ambient`. In particular, this
63
+ forces a specific embedding into the ambient field of rational expressions. In
64
+ view of this, although cluster algebras themselves are independent of the
65
+ choice of initial seed, :meth:`ClusterAlgebra.mutate_initial` is forced to
66
+ return a different instance of :class:`ClusterAlgebra`. At the moment there
67
+ is no coercion implemented among the two instances but this could in
68
+ principle be added to :meth:`ClusterAlgebra.mutate_initial`.
69
+
70
+ REFERENCES:
71
+
72
+ - [FZ2007]_
73
+ - [LLZ2014]_
74
+ - [NZ2012]_
75
+
76
+ AUTHORS:
77
+
78
+ - Dylan Rupel (2015-06-15): initial version
79
+
80
+ - Salvatore Stella (2015-06-15): initial version
81
+
82
+ EXAMPLES:
83
+
84
+ We begin by creating a simple cluster algebra and printing its
85
+ initial exchange matrix::
86
+
87
+ sage: A = ClusterAlgebra(['A', 2]); A
88
+ A Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring
89
+ sage: A.b_matrix()
90
+ [ 0 1]
91
+ [-1 0]
92
+
93
+ ``A`` is of finite type so we can explore all its exchange graph::
94
+
95
+ sage: A.explore_to_depth(infinity)
96
+
97
+ and get all its g-vectors, F-polynomials, and cluster variables::
98
+
99
+ sage: sorted(A.g_vectors_so_far())
100
+ [(-1, 0), (-1, 1), (0, -1), (0, 1), (1, 0)]
101
+ sage: sorted(A.F_polynomials_so_far(), key=str)
102
+ [1, 1, u0 + 1, u0*u1 + u0 + 1, u1 + 1]
103
+ sage: sorted(A.cluster_variables_so_far(), key=str)
104
+ [(x0 + 1)/x1, (x0 + x1 + 1)/(x0*x1), (x1 + 1)/x0, x0, x1]
105
+
106
+ Simple operations among cluster variables behave as expected::
107
+
108
+ sage: s = A.cluster_variable((0, -1)); s
109
+ (x0 + 1)/x1
110
+ sage: t = A.cluster_variable((-1, 1)); t
111
+ (x1 + 1)/x0
112
+ sage: t + s
113
+ (x0^2 + x1^2 + x0 + x1)/(x0*x1)
114
+ sage: _.parent() == A
115
+ True
116
+ sage: t - s
117
+ (-x0^2 + x1^2 - x0 + x1)/(x0*x1)
118
+ sage: _.parent() == A
119
+ True
120
+ sage: t*s
121
+ (x0*x1 + x0 + x1 + 1)/(x0*x1)
122
+ sage: _.parent() == A
123
+ True
124
+ sage: t/s
125
+ (x1^2 + x1)/(x0^2 + x0)
126
+ sage: _.parent() == A
127
+ False
128
+
129
+ Division is not guaranteed to yield an element of ``A`` so it returns an
130
+ element of ``A.ambient().fraction_field()`` instead::
131
+
132
+ sage: (t/s).parent() == A.ambient().fraction_field()
133
+ True
134
+
135
+ We can compute denominator vectors of any element of ``A``::
136
+
137
+ sage: (t*s).d_vector()
138
+ (1, 1)
139
+
140
+ Since we are in rank 2 and we do not have coefficients we can compute the
141
+ greedy element associated to any denominator vector::
142
+
143
+ sage: A.rank() == 2 and A.coefficients() == ()
144
+ True
145
+ sage: A.greedy_element((1, 1))
146
+ (x0 + x1 + 1)/(x0*x1)
147
+ sage: _ == t*s
148
+ False
149
+
150
+ not surprising since there is no cluster in ``A`` containing
151
+ both ``t`` and ``s``::
152
+
153
+ sage: seeds = A.seeds(mutating_F=false)
154
+ sage: [ S for S in seeds if (0, -1) in S and (-1, 1) in S ]
155
+ []
156
+
157
+ indeed::
158
+
159
+ sage: A.greedy_element((1, 1)) == A.cluster_variable((-1, 0))
160
+ True
161
+
162
+ Disabling F-polynomials in the computation just done was redundant because we
163
+ already explored the whole exchange graph before. Though in different
164
+ circumstances it could have saved us considerable time.
165
+
166
+ g-vectors and F-polynomials can be computed from elements of ``A`` only if
167
+ ``A`` has principal coefficients at the initial seed::
168
+
169
+ sage: (t*s).g_vector()
170
+ Traceback (most recent call last):
171
+ ...
172
+ AttributeError: 'ClusterAlgebra_with_category.element_class' object has no attribute 'g_vector'...
173
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True)
174
+ sage: A.explore_to_depth(infinity)
175
+ sage: s = A.cluster_variable((0, -1)); s
176
+ (x0*y1 + 1)/x1
177
+ sage: t = A.cluster_variable((-1, 1)); t
178
+ (x1 + y0)/x0
179
+ sage: (t*s).g_vector()
180
+ (-1, 0)
181
+ sage: (t*s).F_polynomial()
182
+ u0*u1 + u0 + u1 + 1
183
+ sage: (t*s).is_homogeneous()
184
+ True
185
+ sage: (t+s).is_homogeneous()
186
+ False
187
+ sage: (t+s).homogeneous_components()
188
+ {(-1, 1): (x1 + y0)/x0, (0, -1): (x0*y1 + 1)/x1}
189
+
190
+ Each cluster algebra is endowed with a reference to a current seed;
191
+ it could be useful to assign a name to it::
192
+
193
+ sage: A = ClusterAlgebra(['F', 4])
194
+ sage: len(A.g_vectors_so_far())
195
+ 4
196
+ sage: A.current_seed()
197
+ The initial seed of a Cluster Algebra with cluster variables x0, x1, x2, x3
198
+ and no coefficients over Integer Ring
199
+ sage: A.current_seed() == A.initial_seed()
200
+ True
201
+ sage: S = A.current_seed()
202
+ sage: S.b_matrix()
203
+ [ 0 1 0 0]
204
+ [-1 0 -1 0]
205
+ [ 0 2 0 1]
206
+ [ 0 0 -1 0]
207
+ sage: S.g_matrix()
208
+ [1 0 0 0]
209
+ [0 1 0 0]
210
+ [0 0 1 0]
211
+ [0 0 0 1]
212
+ sage: S.cluster_variables()
213
+ [x0, x1, x2, x3]
214
+
215
+ and use ``S`` to walk around the exchange graph of ``A``::
216
+
217
+ sage: S.mutate(0); S
218
+ The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3
219
+ and no coefficients over Integer Ring obtained from the initial
220
+ by mutating in direction 0
221
+ sage: S.b_matrix()
222
+ [ 0 -1 0 0]
223
+ [ 1 0 -1 0]
224
+ [ 0 2 0 1]
225
+ [ 0 0 -1 0]
226
+ sage: S.g_matrix()
227
+ [-1 0 0 0]
228
+ [ 1 1 0 0]
229
+ [ 0 0 1 0]
230
+ [ 0 0 0 1]
231
+ sage: S.cluster_variables()
232
+ [(x1 + 1)/x0, x1, x2, x3]
233
+ sage: S.mutate('sinks'); S
234
+ The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3
235
+ and no coefficients over Integer Ring obtained from the initial
236
+ by mutating along the sequence [0, 2]
237
+ sage: S.mutate([2, 3, 2, 1, 0]); S
238
+ The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3
239
+ and no coefficients over Integer Ring obtained from the initial
240
+ by mutating along the sequence [0, 3, 2, 1, 0]
241
+ sage: S.g_vectors()
242
+ [(0, 1, -2, 0), (-1, 2, -2, 0), (0, 1, -1, 0), (0, 0, 0, -1)]
243
+ sage: S.cluster_variable(3)
244
+ (x2 + 1)/x3
245
+
246
+ Walking around by mutating ``S`` updates the information stored in ``A``::
247
+
248
+ sage: len(A.g_vectors_so_far())
249
+ 10
250
+ sage: A.current_seed().path_from_initial_seed()
251
+ [0, 3, 2, 1, 0]
252
+ sage: A.current_seed() == S
253
+ True
254
+
255
+ Starting from ``A.initial_seed()`` still records data in ``A`` but does not
256
+ update ``A.current_seed()``::
257
+
258
+ sage: S1 = A.initial_seed()
259
+ sage: S1.mutate([2, 1, 3])
260
+ sage: len(A.g_vectors_so_far())
261
+ 11
262
+ sage: S1 == A.current_seed()
263
+ False
264
+
265
+ Since :class:`ClusterAlgebra` inherits from :class:`UniqueRepresentation`,
266
+ computed data is shared across instances::
267
+
268
+ sage: A1 = ClusterAlgebra(['F', 4])
269
+ sage: A1 is A
270
+ True
271
+ sage: len(A1.g_vectors_so_far())
272
+ 11
273
+
274
+ It can be useful, at times to forget all computed data. Because of
275
+ :class:`UniqueRepresentation` this cannot be achieved by simply creating a
276
+ new instance; instead it has to be manually triggered by::
277
+
278
+ sage: A.clear_computed_data()
279
+ sage: len(A.g_vectors_so_far())
280
+ 4
281
+
282
+ Given a cluster algebra ``A`` we may be looking for a specific cluster
283
+ variable::
284
+
285
+ sage: A = ClusterAlgebra(['E', 8, 1])
286
+ sage: v = (-1, 1, -1, 1, -1, 1, 0, 0, 1)
287
+ sage: A.find_g_vector(v, depth=2)
288
+ sage: seq = A.find_g_vector(v); seq # random
289
+ [0, 1, 2, 4, 3]
290
+ sage: v in A.initial_seed().mutate(seq, inplace=False).g_vectors()
291
+ True
292
+
293
+ This also performs mutations of F-polynomials::
294
+
295
+ sage: A.F_polynomial((-1, 1, -1, 1, -1, 1, 0, 0, 1))
296
+ u0*u1*u2*u3*u4 + u0*u1*u2*u4 + u0*u2*u3*u4 + u0*u1*u2 + u0*u2*u4
297
+ + u2*u3*u4 + u0*u2 + u0*u4 + u2*u4 + u0 + u2 + u4 + 1
298
+
299
+ which might not be a good idea in algebras that are too big. One workaround is
300
+ to first disable F-polynomials and then recompute only the desired mutations::
301
+
302
+ sage: # long time
303
+ sage: A.reset_exploring_iterator(mutating_F=False)
304
+ sage: v = (-1, 1, -2, 2, -1, 1, -1, 1, 1)
305
+ sage: seq = A.find_g_vector(v); seq # random
306
+ [1, 0, 2, 6, 5, 4, 3, 8, 1]
307
+ sage: S = A.initial_seed().mutate(seq, inplace=False)
308
+ sage: v in S.g_vectors()
309
+ True
310
+ sage: A.current_seed().mutate(seq)
311
+ sage: A.F_polynomial((-1, 1, -2, 2, -1, 1, -1, 1, 1))
312
+ u0*u1^2*u2^2*u3*u4*u5*u6*u8 +
313
+ ...
314
+ 2*u2 + u4 + u6 + 1
315
+
316
+ We can manually freeze cluster variables and get coercions in between
317
+ the two algebras::
318
+
319
+ sage: A = ClusterAlgebra(['F', 4]); A
320
+ A Cluster Algebra with cluster variables x0, x1, x2, x3 and no coefficients
321
+ over Integer Ring
322
+ sage: A1 = ClusterAlgebra(A.b_matrix().matrix_from_columns([0, 1, 2]), coefficient_prefix='x'); A1
323
+ A Cluster Algebra with cluster variables x0, x1, x2 and coefficient x3
324
+ over Integer Ring
325
+ sage: A.has_coerce_map_from(A1)
326
+ True
327
+
328
+ and we also have an immersion of ``A.base()`` into ``A`` and of ``A``
329
+ into ``A.ambient()``::
330
+
331
+ sage: A.has_coerce_map_from(A.base())
332
+ True
333
+ sage: A.ambient().has_coerce_map_from(A)
334
+ True
335
+
336
+ but there is currently no coercion in between algebras obtained by
337
+ mutating at the initial seed::
338
+
339
+ sage: A1 = A.mutate_initial(0); A1
340
+ A Cluster Algebra with cluster variables x4, x1, x2, x3 and no coefficients
341
+ over Integer Ring
342
+ sage: A.b_matrix() == A1.b_matrix()
343
+ False
344
+ sage: [X.has_coerce_map_from(Y) for X, Y in [(A, A1), (A1, A)]]
345
+ [False, False]
346
+ """
347
+
348
+ # ****************************************************************************
349
+ # Copyright (C) 2015 Dylan Rupel and Salvatore Stella
350
+ #
351
+ # This program is free software: you can redistribute it and/or modify
352
+ # it under the terms of the GNU General Public License as published by
353
+ # the Free Software Foundation, either version 2 of the License, or
354
+ # (at your option) any later version.
355
+ # https://www.gnu.org/licenses/
356
+ # ****************************************************************************
357
+
358
+ from copy import copy
359
+ from typing import Any
360
+
361
+ from sage.arith.misc import binomial
362
+ from sage.categories.homset import Hom
363
+ from sage.categories.morphism import SetMorphism
364
+ from sage.categories.rings import Rings
365
+ from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
366
+ from sage.combinat.permutation import Permutation
367
+ from sage.graphs.digraph import DiGraph
368
+ from sage.matrix.constructor import identity_matrix, matrix
369
+ from sage.matrix.special import block_matrix
370
+ from sage.misc.cachefunc import cached_method
371
+ from sage.misc.lazy_import import lazy_import
372
+ from sage.misc.misc_c import prod
373
+ from sage.modules.free_module_element import vector
374
+ from sage.rings.infinity import infinity
375
+ from sage.rings.integer import Integer
376
+ from sage.rings.integer_ring import ZZ
377
+ from sage.rings.polynomial.laurent_polynomial_ring import (LaurentPolynomialRing_generic,
378
+ LaurentPolynomialRing)
379
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
380
+ from sage.rings.rational_field import QQ
381
+ from sage.structure.element_wrapper import ElementWrapper
382
+ from sage.structure.parent import Parent
383
+ from sage.structure.sage_object import SageObject
384
+ from sage.structure.unique_representation import UniqueRepresentation
385
+
386
+ lazy_import('sage.geometry.cone', 'Cone')
387
+ lazy_import('sage.geometry.fan', 'Fan')
388
+
389
+ ##############################################################################
390
+ # Elements of a cluster algebra
391
+ ##############################################################################
392
+
393
+
394
+ class ClusterAlgebraElement(ElementWrapper):
395
+ """
396
+ An element of a cluster algebra.
397
+ """
398
+ # AdditiveMagmas.Subobjects currently does not implements _add_
399
+ def _add_(self, other):
400
+ r"""
401
+ Return the sum of ``self`` and ``other``.
402
+
403
+ INPUT:
404
+
405
+ - ``other`` -- an element of ``self.parent()``
406
+
407
+ EXAMPLES::
408
+
409
+ sage: A = ClusterAlgebra(['F', 4])
410
+ sage: A.an_element() + A.an_element()
411
+ 2*x0
412
+ """
413
+ return self.parent().retract(self.lift() + other.lift())
414
+
415
+ def _neg_(self):
416
+ r"""
417
+ Return the negative of ``self``.
418
+
419
+ EXAMPLES::
420
+
421
+ sage: A = ClusterAlgebra(['F', 4])
422
+ sage: -A.an_element()
423
+ -x0
424
+ """
425
+ return self.parent().retract(-self.lift())
426
+
427
+ def _div_(self, other):
428
+ r"""
429
+ Return the quotient of ``self`` and ``other``.
430
+
431
+ .. WARNING::
432
+
433
+ The result of a division is not guaranteed to be inside
434
+ :meth:`parent` therefore this method does not return an
435
+ instance of :class:`ClusterAlgebraElement`.
436
+
437
+ EXAMPLES::
438
+
439
+ sage: A = ClusterAlgebra(['F', 4])
440
+ sage: x = A.an_element()
441
+ sage: x/x
442
+ 1
443
+ sage: _.parent()
444
+ Multivariate Laurent Polynomial Ring in x0, x1, x2, x3
445
+ over Integer Ring
446
+ sage: A.retract(x/x)
447
+ 1
448
+ sage: _.parent()
449
+ A Cluster Algebra with cluster variables x0, x1, x2, x3
450
+ and no coefficients over Integer Ring
451
+ """
452
+ return self.lift() / other.lift()
453
+
454
+ def d_vector(self) -> tuple:
455
+ r"""
456
+ Return the denominator vector of ``self`` as a tuple of integers.
457
+
458
+ EXAMPLES::
459
+
460
+ sage: A = ClusterAlgebra(['F', 4], principal_coefficients=True)
461
+ sage: A.current_seed().mutate([0, 2, 1])
462
+ sage: x = A.cluster_variable((-1, 2, -2, 2)) * A.cluster_variable((0, 0, 0, 1))**2
463
+ sage: x.d_vector()
464
+ (1, 1, 2, -2)
465
+ """
466
+ monomials = self.lift().monomial_coefficients()
467
+ minimal = map(min, zip(*monomials))
468
+ return tuple(-vector(minimal))[:self.parent().rank()]
469
+
470
+ def _repr_(self) -> str:
471
+ r"""
472
+ Return the string representation of ``self``.
473
+
474
+ EXAMPLES::
475
+
476
+ sage: A = ClusterAlgebra(['F', 4], principal_coefficients=True)
477
+ sage: A.current_seed().mutate([0, 2, 1])
478
+ sage: A.cluster_variable((-1, 2, -2, 2))
479
+ (x0*x2^2*y0*y1*y2^2 + x1^3*x3^2 + x1^2*x3^2*y0 + 2*x1^2*x3*y2 + 2*x1*x3*y0*y2 + x1*y2^2 + y0*y2^2)/(x0*x1*x2^2)
480
+ """
481
+ numer, denom = self.lift()._fraction_pair()
482
+ return repr(numer / denom)
483
+
484
+
485
+ class PrincipalClusterAlgebraElement(ClusterAlgebraElement):
486
+ """
487
+ An element in a cluster algebra with principle coefficients.
488
+ """
489
+ def g_vector(self):
490
+ r"""
491
+ Return the g-vector of ``self``.
492
+
493
+ EXAMPLES::
494
+
495
+ sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True)
496
+ sage: A.cluster_variable((1, 0)).g_vector() == (1, 0)
497
+ True
498
+ sage: sum(A.initial_cluster_variables()).g_vector()
499
+ Traceback (most recent call last):
500
+ ...
501
+ ValueError: this element does not have a well defined g-vector
502
+ """
503
+ if not self.is_homogeneous():
504
+ raise ValueError("this element does not have a well defined g-vector")
505
+ return self._g_vector
506
+
507
+ def F_polynomial(self):
508
+ r"""
509
+ Return the F-polynomial of ``self``.
510
+
511
+ EXAMPLES::
512
+
513
+ sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True)
514
+ sage: S = A.initial_seed()
515
+ sage: S.mutate([0, 1, 0])
516
+ sage: S.cluster_variable(0).F_polynomial() == S.F_polynomial(0)
517
+ True
518
+ sage: sum(A.initial_cluster_variables()).F_polynomial()
519
+ Traceback (most recent call last):
520
+ ...
521
+ ValueError: this element does not have a well defined g-vector
522
+ """
523
+ if not self.is_homogeneous():
524
+ raise ValueError("this element does not have a well defined g-vector")
525
+ subs_dict = {}
526
+ A = self.parent()
527
+ for x in A.initial_cluster_variables():
528
+ subs_dict[x.lift()] = A._U(1)
529
+ for i in range(A.rank()):
530
+ subs_dict[A.coefficient(i).lift()] = A._U.gen(i)
531
+ return A._U(self.lift().substitute(subs_dict))
532
+
533
+ def is_homogeneous(self) -> bool:
534
+ r"""
535
+ Return ``True`` if ``self`` is a homogeneous element
536
+ of ``self.parent()``.
537
+
538
+ EXAMPLES::
539
+
540
+ sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True)
541
+ sage: A.cluster_variable((1, 0)).is_homogeneous()
542
+ True
543
+ sage: x = A.cluster_variable((1, 0)) + A.cluster_variable((0, 1))
544
+ sage: x.is_homogeneous()
545
+ False
546
+ """
547
+ return getattr(self, '_is_homogeneous', len(self.homogeneous_components()) == 1)
548
+
549
+ def homogeneous_components(self) -> dict:
550
+ r"""
551
+ Return a dictionary of the homogeneous components of ``self``.
552
+
553
+ OUTPUT:
554
+
555
+ A dictionary whose keys are homogeneous degrees and whose values
556
+ are the summands of ``self`` of the given degree.
557
+
558
+ EXAMPLES::
559
+
560
+ sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True)
561
+ sage: x = A.cluster_variable((1, 0)) + A.cluster_variable((0, 1))
562
+ sage: x.homogeneous_components()
563
+ {(0, 1): x1, (1, 0): x0}
564
+ """
565
+ deg_matrix = block_matrix([[identity_matrix(self.parent().rank()),
566
+ -self.parent().b_matrix()]])
567
+ components: dict[tuple, Any] = {}
568
+ x = self.lift()
569
+ monomials = x.monomials()
570
+ for m in monomials:
571
+ g_vect = tuple(deg_matrix * vector(m.exponents()[0]))
572
+ if g_vect in components:
573
+ components[g_vect] += self.parent().retract(x.monomial_coefficient(m) * m)
574
+ else:
575
+ components[g_vect] = self.parent().retract(x.monomial_coefficient(m) * m)
576
+ for g_vect, compo in components.items():
577
+ compo._is_homogeneous = True
578
+ compo._g_vector = g_vect
579
+ self._is_homogeneous = (len(components) == 1)
580
+ if self._is_homogeneous:
581
+ self._g_vector = next(iter(components))
582
+ return components
583
+
584
+ def theta_basis_decomposition(self):
585
+ r"""
586
+ Return the decomposition of ``self`` in the theta basis.
587
+
588
+ OUTPUT:
589
+
590
+ A dictionary whose keys are the g-vectors and whose values are the coefficients
591
+ in the decomposition of ``self`` in the theta basis.
592
+
593
+ EXAMPLES::
594
+
595
+ sage: A = ClusterAlgebra(matrix([[0,-2],[3,0]]), principal_coefficients=True)
596
+ sage: f = (A.theta_basis_element((1,0)) + A.theta_basis_element((0,1)))**2 + A.coefficient(1)* A.theta_basis_element((1,1))
597
+ sage: decomposition = f.theta_basis_decomposition()
598
+ sage: sum(decomposition[g] * A.theta_basis_element(g) for g in decomposition) == f
599
+ True
600
+ sage: f = A.theta_basis_element((4,-4))*A.theta_basis_element((1,-1))
601
+ sage: decomposition = f.theta_basis_decomposition()
602
+ sage: sum(decomposition[g] * A.theta_basis_element(g) for g in decomposition) == f
603
+ True
604
+ """
605
+ A = self.parent()
606
+ B = A.b_matrix()
607
+ U = A._U
608
+ out = {}
609
+ zero_A = A(0)
610
+ zero_U = U(0)
611
+ zero_t = (0,) * A.rank()
612
+
613
+ components = self.homogeneous_components()
614
+
615
+ for g_vect in components:
616
+ f_poly = components[g_vect].F_polynomial()
617
+ g_vect = vector(g_vect)
618
+ while f_poly != zero_U:
619
+ coeffs = f_poly.monomial_coefficients()
620
+ y_exp = min(coeffs)
621
+ coeff = coeffs[y_exp]
622
+ g_theta = tuple(g_vect + B * vector(y_exp))
623
+ out[g_theta] = out.get(g_theta, zero_A) + A({zero_t + tuple(y_exp): coeff})
624
+ f_poly -= U({y_exp: coeff}) * A.theta_basis_F_polynomial(g_theta)
625
+
626
+ return out
627
+
628
+
629
+ ##############################################################################
630
+ # Seeds
631
+ ##############################################################################
632
+
633
+ class ClusterAlgebraSeed(SageObject):
634
+ """
635
+ A seed in a Cluster Algebra.
636
+
637
+ INPUT:
638
+
639
+ - ``B`` -- a skew-symmetrizable integer matrix
640
+ - ``C`` -- the matrix of c-vectors of ``self``
641
+ - ``G`` -- the matrix of g-vectors of ``self``
642
+ - ``parent`` -- :class:`ClusterAlgebra`; the algebra to which the
643
+ seed belongs
644
+ - ``path`` -- list (default: ``[]``); the mutation sequence from the
645
+ initial seed of ``parent`` to ``self``
646
+
647
+ .. WARNING::
648
+
649
+ Seeds should **not** be created manually: no test is performed to
650
+ assert that they are built from consistent data nor that they
651
+ really are seeds of ``parent``. If you create seeds with
652
+ inconsistent data all sort of things can go wrong, even
653
+ :meth:`__eq__` is no longer guaranteed to give correct answers.
654
+ Use at your own risk.
655
+ """
656
+ def __init__(self, B, C, G, parent, **kwargs):
657
+ r"""
658
+ Initialize ``self``.
659
+
660
+ EXAMPLES::
661
+
662
+ sage: A = ClusterAlgebra(['F', 4])
663
+ sage: from sage.algebras.cluster_algebra import ClusterAlgebraSeed
664
+ sage: ClusterAlgebraSeed(A.b_matrix(), identity_matrix(4), identity_matrix(4), A, path=[1, 2, 3])
665
+ The seed of a Cluster Algebra with cluster variables x0, x1, x2, x3
666
+ and no coefficients over Integer Ring obtained from the initial
667
+ by mutating along the sequence [1, 2, 3]
668
+ """
669
+ self._B = copy(B)
670
+ self._C = copy(C)
671
+ self._G = copy(G)
672
+ self._parent = parent
673
+ self._path = kwargs.get('path', [])
674
+
675
+ def __copy__(self):
676
+ r"""
677
+ Return a copy of ``self``.
678
+
679
+ EXAMPLES::
680
+
681
+ sage: A = ClusterAlgebra(['A', 3])
682
+ sage: S = copy(A.current_seed())
683
+ sage: S == A.current_seed()
684
+ True
685
+ sage: S is not A.current_seed()
686
+ True
687
+ """
688
+ other = type(self).__new__(type(self))
689
+ other._B = copy(self._B)
690
+ other._C = copy(self._C)
691
+ other._G = copy(self._G)
692
+ other._parent = self._parent
693
+ other._path = copy(self._path)
694
+ return other
695
+
696
+ def __eq__(self, other):
697
+ r"""
698
+ Test equality of two seeds.
699
+
700
+ INPUT:
701
+
702
+ - ``other`` -- a :class:`ClusterAlgebraSeed`
703
+
704
+ ALGORITHM:
705
+
706
+ ``self`` and ``other`` are deemed to be equal if they have the same
707
+ parent and their set of g-vectors coincide, i.e. this tests
708
+ equality of unlabelled seeds.
709
+
710
+ EXAMPLES::
711
+
712
+ sage: A = ClusterAlgebra(['A', 3])
713
+ sage: A.clear_computed_data()
714
+ sage: S = copy(A.current_seed())
715
+ sage: S.mutate([0, 2, 0])
716
+ sage: S == A.current_seed()
717
+ False
718
+ sage: S.mutate(2)
719
+ sage: S == A.current_seed()
720
+ True
721
+
722
+ sage: A = ClusterAlgebra(['B', 2], principal_coefficients=True)
723
+ sage: S = A.current_seed()
724
+ sage: S.mutate(0)
725
+ sage: S == A.current_seed()
726
+ True
727
+ """
728
+ return (isinstance(other, ClusterAlgebraSeed) and
729
+ self.parent() == other.parent() and
730
+ frozenset(self.g_vectors()) == frozenset(other.g_vectors()))
731
+
732
+ def __contains__(self, element) -> bool:
733
+ r"""
734
+ Test whether ``element`` belong to ``self``.
735
+
736
+ INPUT:
737
+
738
+ - ``element`` -- either a g-vector or an element of :meth:`parent`
739
+
740
+ EXAMPLES::
741
+
742
+ sage: A = ClusterAlgebra(['A', 3])
743
+ sage: S = A.initial_seed()
744
+ sage: (1, 0, 0) in S
745
+ True
746
+ sage: (1, 1, 0) in S
747
+ False
748
+ sage: A.cluster_variable((1, 0, 0)) in S
749
+ True
750
+ """
751
+ if isinstance(element, ClusterAlgebraElement):
752
+ cluster = self.cluster_variables()
753
+ else:
754
+ element = tuple(element)
755
+ cluster = self.g_vectors()
756
+ return element in cluster
757
+
758
+ def __hash__(self):
759
+ r"""
760
+ Return the hash of ``self``.
761
+
762
+ ALGORITHM:
763
+
764
+ For speed purposes the hash is computed on :meth:`self.g_vectors`.
765
+ In particular it is guaranteed to be unique only within a given
766
+ instance of :class:`ClusterAlgebra`. Moreover unlabelled seeds that
767
+ have the same set of g-vectors have the same hash.
768
+
769
+ EXAMPLES::
770
+
771
+ sage: A = ClusterAlgebra(['A', 3])
772
+ sage: S = A.initial_seed()
773
+ sage: T = S.mutate(1, inplace=False)
774
+ sage: hash(S) == hash(S)
775
+ True
776
+ sage: hash(S) == hash(T)
777
+ False
778
+ """
779
+ return hash(frozenset(self.g_vectors()))
780
+
781
+ def _repr_(self) -> str:
782
+ r"""
783
+ Return the string representation of ``self``.
784
+
785
+ EXAMPLES::
786
+
787
+ sage: A = ClusterAlgebra(['A', 3])
788
+ sage: A.clear_computed_data()
789
+ sage: S = A.current_seed(); S
790
+ The initial seed of a Cluster Algebra with cluster variables x0, x1, x2
791
+ and no coefficients over Integer Ring
792
+ sage: S.mutate(0); S
793
+ The seed of a Cluster Algebra with cluster variables x0, x1, x2
794
+ and no coefficients over Integer Ring obtained from the initial
795
+ by mutating in direction 0
796
+ sage: S.mutate(1); S
797
+ The seed of a Cluster Algebra with cluster variables x0, x1, x2
798
+ and no coefficients over Integer Ring obtained from the initial
799
+ by mutating along the sequence [0, 1]
800
+ """
801
+ if not self._path:
802
+ return "The initial seed of a %s" % str(self.parent())[2:]
803
+ if len(self._path) == 1:
804
+ return "The seed of a %s obtained from the initial by mutating in direction %s" % (str(self.parent())[2:], str(self._path[0]))
805
+ return "The seed of a %s obtained from the initial by mutating along the sequence %s" % (str(self.parent())[2:], str(self._path))
806
+
807
+ def parent(self):
808
+ r"""
809
+ Return the parent of ``self``.
810
+
811
+ EXAMPLES::
812
+
813
+ sage: A = ClusterAlgebra(['B', 3])
814
+ sage: A.current_seed().parent() == A
815
+ True
816
+ """
817
+ return self._parent
818
+
819
+ def depth(self) -> int:
820
+ r"""
821
+ Return the length of a mutation sequence from the initial seed
822
+ of :meth:`parent` to ``self``.
823
+
824
+ .. WARNING::
825
+
826
+ This is the length of the mutation sequence returned by
827
+ :meth:`path_from_initial_seed`, which need not be the
828
+ shortest possible.
829
+
830
+ EXAMPLES::
831
+
832
+ sage: A = ClusterAlgebra(['A', 2])
833
+ sage: S1 = A.initial_seed()
834
+ sage: S1.mutate([0, 1, 0, 1])
835
+ sage: S1.depth()
836
+ 4
837
+ sage: S2 = A.initial_seed()
838
+ sage: S2.mutate(1)
839
+ sage: S2.depth()
840
+ 1
841
+ sage: S1 == S2
842
+ True
843
+ """
844
+ return len(self._path)
845
+
846
+ def path_from_initial_seed(self) -> list:
847
+ r"""
848
+ Return a mutation sequence from the initial seed of :meth:`parent`
849
+ to ``self``.
850
+
851
+ .. WARNING::
852
+
853
+ This is the path used to compute ``self`` and it does not
854
+ have to be the shortest possible.
855
+
856
+ EXAMPLES::
857
+
858
+ sage: A = ClusterAlgebra(['A', 2])
859
+ sage: S1 = A.initial_seed()
860
+ sage: S1.mutate([0, 1, 0, 1])
861
+ sage: S1.path_from_initial_seed()
862
+ [0, 1, 0, 1]
863
+ sage: S2 = A.initial_seed()
864
+ sage: S2.mutate(1)
865
+ sage: S2.path_from_initial_seed()
866
+ [1]
867
+ sage: S1 == S2
868
+ True
869
+ """
870
+ return copy(self._path)
871
+
872
+ def b_matrix(self):
873
+ r"""
874
+ Return the exchange matrix of ``self``.
875
+
876
+ EXAMPLES::
877
+
878
+ sage: A = ClusterAlgebra(['A', 3])
879
+ sage: S = A.initial_seed()
880
+ sage: S.b_matrix()
881
+ [ 0 1 0]
882
+ [-1 0 -1]
883
+ [ 0 1 0]
884
+ """
885
+ return copy(self._B)
886
+
887
+ def c_matrix(self):
888
+ r"""
889
+ Return the matrix whose columns are the c-vectors of ``self``.
890
+
891
+ EXAMPLES::
892
+
893
+ sage: A = ClusterAlgebra(['A', 3])
894
+ sage: S = A.initial_seed()
895
+ sage: S.c_matrix()
896
+ [1 0 0]
897
+ [0 1 0]
898
+ [0 0 1]
899
+ """
900
+ return copy(self._C)
901
+
902
+ def c_vector(self, j) -> tuple:
903
+ r"""
904
+ Return the ``j``-th c-vector of ``self``.
905
+
906
+ INPUT:
907
+
908
+ - ``j`` -- integer in ``range(self.parent().rank())``;
909
+ the index of the c-vector to return
910
+
911
+ EXAMPLES::
912
+
913
+ sage: A = ClusterAlgebra(['A', 3])
914
+ sage: S = A.initial_seed()
915
+ sage: S.c_vector(0)
916
+ (1, 0, 0)
917
+ sage: S.mutate(0)
918
+ sage: S.c_vector(0)
919
+ (-1, 0, 0)
920
+ sage: S.c_vector(1)
921
+ (1, 1, 0)
922
+ """
923
+ return tuple(self._C.column(j))
924
+
925
+ def c_vectors(self) -> list[tuple]:
926
+ r"""
927
+ Return all the c-vectors of ``self``.
928
+
929
+ EXAMPLES::
930
+
931
+ sage: A = ClusterAlgebra(['A', 3])
932
+ sage: S = A.initial_seed()
933
+ sage: S.c_vectors()
934
+ [(1, 0, 0), (0, 1, 0), (0, 0, 1)]
935
+ """
936
+ return list(map(tuple, self._C.columns()))
937
+
938
+ def g_matrix(self):
939
+ r"""
940
+ Return the matrix whose columns are the g-vectors of ``self``.
941
+
942
+ EXAMPLES::
943
+
944
+ sage: A = ClusterAlgebra(['A', 3])
945
+ sage: S = A.initial_seed()
946
+ sage: S.g_matrix()
947
+ [1 0 0]
948
+ [0 1 0]
949
+ [0 0 1]
950
+ """
951
+ return copy(self._G)
952
+
953
+ def g_vector(self, j) -> tuple:
954
+ r"""
955
+ Return the ``j``-th g-vector of ``self``.
956
+
957
+ INPUT:
958
+
959
+ - ``j`` -- integer in ``range(self.parent().rank())``;
960
+ the index of the g-vector to return
961
+
962
+ EXAMPLES::
963
+
964
+ sage: A = ClusterAlgebra(['A', 3])
965
+ sage: S = A.initial_seed()
966
+ sage: S.g_vector(0)
967
+ (1, 0, 0)
968
+ """
969
+ return tuple(self._G.column(j))
970
+
971
+ def g_vectors(self) -> list[tuple]:
972
+ r"""
973
+ Return all the g-vectors of ``self``.
974
+
975
+ EXAMPLES::
976
+
977
+ sage: A = ClusterAlgebra(['A', 3])
978
+ sage: S = A.initial_seed()
979
+ sage: S.g_vectors()
980
+ [(1, 0, 0), (0, 1, 0), (0, 0, 1)]
981
+ """
982
+ return list(map(tuple, self._G.columns()))
983
+
984
+ def F_polynomial(self, j):
985
+ r"""
986
+ Return the ``j``-th F-polynomial of ``self``.
987
+
988
+ INPUT:
989
+
990
+ - ``j`` -- integer in ``range(self.parent().rank())``;
991
+ the index of the F-polynomial to return
992
+
993
+ EXAMPLES::
994
+
995
+ sage: A = ClusterAlgebra(['A', 3])
996
+ sage: S = A.initial_seed()
997
+ sage: S.F_polynomial(0)
998
+ 1
999
+ """
1000
+ return self.parent().F_polynomial(self.g_vector(j))
1001
+
1002
+ def F_polynomials(self) -> list:
1003
+ r"""
1004
+ Return all the F-polynomials of ``self``.
1005
+
1006
+ EXAMPLES::
1007
+
1008
+ sage: A = ClusterAlgebra(['A', 3])
1009
+ sage: S = A.initial_seed()
1010
+ sage: S.F_polynomials()
1011
+ [1, 1, 1]
1012
+ """
1013
+ return [self.parent().F_polynomial(g) for g in self.g_vectors()]
1014
+
1015
+ def cluster_variable(self, j):
1016
+ r"""
1017
+ Return the ``j``-th cluster variable of ``self``.
1018
+
1019
+ INPUT:
1020
+
1021
+ - ``j`` -- integer in ``range(self.parent().rank())``;
1022
+ the index of the cluster variable to return
1023
+
1024
+ EXAMPLES::
1025
+
1026
+ sage: A = ClusterAlgebra(['A', 3])
1027
+ sage: S = A.initial_seed()
1028
+ sage: S.cluster_variable(0)
1029
+ x0
1030
+ sage: S.mutate(0)
1031
+ sage: S.cluster_variable(0)
1032
+ (x1 + 1)/x0
1033
+ """
1034
+ return self.parent().cluster_variable(self.g_vector(j))
1035
+
1036
+ def cluster_variables(self) -> list:
1037
+ r"""
1038
+ Return all the cluster variables of ``self``.
1039
+
1040
+ EXAMPLES::
1041
+
1042
+ sage: A = ClusterAlgebra(['A', 3])
1043
+ sage: S = A.initial_seed()
1044
+ sage: S.cluster_variables()
1045
+ [x0, x1, x2]
1046
+ """
1047
+ return [self.parent().cluster_variable(g) for g in self.g_vectors()]
1048
+
1049
+ def mutate(self, direction, **kwargs):
1050
+ r"""
1051
+ Mutate ``self``.
1052
+
1053
+ INPUT:
1054
+
1055
+ - ``direction`` -- in which direction(s) to mutate, it can be:
1056
+
1057
+ * an integer in ``range(self.rank())`` to mutate in one direction only
1058
+ * an iterable of such integers to mutate along a sequence
1059
+ * a string "sinks" or "sources" to mutate at all sinks or sources simultaneously
1060
+
1061
+ - ``inplace`` -- boolean (default: ``True``); whether to mutate in place
1062
+ or to return a new object
1063
+
1064
+ - ``mutating_F`` -- boolean (default: ``True``); whether to compute
1065
+ F-polynomials while mutating
1066
+
1067
+ .. NOTE::
1068
+
1069
+ While knowing F-polynomials is essential to computing
1070
+ cluster variables, the process of mutating them is quite slow.
1071
+ If you care only about combinatorial data like g-vectors and
1072
+ c-vectors, setting ``mutating_F=False`` yields significant
1073
+ benefits in terms of speed.
1074
+
1075
+ EXAMPLES::
1076
+
1077
+ sage: A = ClusterAlgebra(['A', 2])
1078
+ sage: S = A.initial_seed()
1079
+ sage: S.mutate(0); S
1080
+ The seed of a Cluster Algebra with cluster variables x0, x1
1081
+ and no coefficients over Integer Ring obtained from the initial
1082
+ by mutating in direction 0
1083
+ sage: S.mutate(5)
1084
+ Traceback (most recent call last):
1085
+ ...
1086
+ ValueError: cannot mutate in direction 5
1087
+ """
1088
+ n = self.parent().rank()
1089
+
1090
+ # do we want to change self?
1091
+ inplace = kwargs.pop('inplace', True)
1092
+ if inplace:
1093
+ to_mutate = self
1094
+ else:
1095
+ to_mutate = copy(self)
1096
+
1097
+ # construct mutation sequence
1098
+ # if you change this be considerate and change also :class:`ClusterAlgebra`.mutate_initial
1099
+ if direction == "sinks":
1100
+ B = self.b_matrix()
1101
+ seq = [i for i in range(n) if all(x <= 0 for x in B.column(i))]
1102
+ elif direction == "sources":
1103
+ B = self.b_matrix()
1104
+ seq = [i for i in range(n) if all(x >= 0 for x in B.column(i))]
1105
+ else:
1106
+ try:
1107
+ seq = iter(direction)
1108
+ except TypeError:
1109
+ seq = iter((direction, ))
1110
+
1111
+ # are we mutating F-polynomials?
1112
+ mutating_F = kwargs.pop('mutating_F', True)
1113
+
1114
+ for k in seq:
1115
+ if k not in range(n):
1116
+ raise ValueError(f'cannot mutate in direction {k}')
1117
+
1118
+ # store new mutation path
1119
+ if to_mutate._path and to_mutate._path[-1] == k:
1120
+ to_mutate._path.pop()
1121
+ else:
1122
+ to_mutate._path.append(k)
1123
+
1124
+ # find sign of k-th c-vector
1125
+ if any(x > 0 for x in to_mutate._C.column(k)):
1126
+ eps = +1
1127
+ else:
1128
+ eps = -1
1129
+
1130
+ # store the g-vector to be mutated in case we are mutating F-polynomials also
1131
+ old_g_vector = to_mutate.g_vector(k)
1132
+
1133
+ # compute new G-matrix
1134
+ J = identity_matrix(n)
1135
+ for j in range(n):
1136
+ J[j, k] += max(0, -eps * to_mutate._B[j, k])
1137
+ J[k, k] = -1
1138
+ to_mutate._G = to_mutate._G * J
1139
+
1140
+ # path to new g-vector (we store the shortest encountered so far)
1141
+ g_vector = to_mutate.g_vector(k)
1142
+ if g_vector not in to_mutate.parent()._path_dict or len(to_mutate.parent()._path_dict[g_vector]) > len(to_mutate._path):
1143
+ to_mutate.parent()._path_dict[g_vector] = copy(to_mutate._path)
1144
+
1145
+ # compute F-polynomials
1146
+ if mutating_F and g_vector not in to_mutate.parent()._F_poly_dict:
1147
+ to_mutate.parent()._F_poly_dict[g_vector] = to_mutate._mutated_F(k, old_g_vector)
1148
+
1149
+ # compute new C-matrix
1150
+ J = identity_matrix(n)
1151
+ for j in range(n):
1152
+ J[k, j] += max(0, eps * to_mutate._B[k, j])
1153
+ J[k, k] = -1
1154
+ to_mutate._C = to_mutate._C * J
1155
+
1156
+ # compute new B-matrix
1157
+ to_mutate._B.mutate(k)
1158
+
1159
+ # return if we need to
1160
+ if not inplace:
1161
+ return to_mutate
1162
+
1163
+ def _mutated_F(self, k, old_g_vector):
1164
+ r"""
1165
+ Compute new F-polynomial obtained by mutating in direction ``k``.
1166
+
1167
+ INPUT:
1168
+
1169
+ - ``k`` -- an integer in ``range(self.parent().rank())``;
1170
+ the direction in which we are mutating
1171
+
1172
+ - ``old_g_vector`` -- tuple; the `k`-th g-vector of ``self``
1173
+ before mutating
1174
+
1175
+ .. NOTE::
1176
+
1177
+ This function is the bottleneck of :meth:`mutate`. The problem is
1178
+ that operations on polynomials are slow. One can get a significant
1179
+ speed boost by disabling this method calling :meth:`mutate` with
1180
+ ``mutating_F=False``.
1181
+
1182
+ EXAMPLES::
1183
+
1184
+ sage: A = ClusterAlgebra(['A', 2])
1185
+ sage: S = A.initial_seed()
1186
+ sage: S.mutate(0)
1187
+ sage: S._mutated_F(0, (1, 0))
1188
+ u0 + 1
1189
+
1190
+ Check that :issue:`28176` is fixed::
1191
+
1192
+ sage: A = ClusterAlgebra(matrix([[0,2],[-2,0]]))
1193
+ sage: S = A.initial_seed()
1194
+ sage: S.mutate([1, 0, 1])
1195
+ sage: parent(S._mutated_F(1, (0, -1)))
1196
+ Multivariate Polynomial Ring in u0, u1 over Rational Field
1197
+ """
1198
+ alg = self.parent()
1199
+ pos = alg._U(1)
1200
+ neg = alg._U(1)
1201
+ for j in range(alg.rank()):
1202
+ if self._C[j, k] > 0:
1203
+ pos *= alg._U.gen(j) ** self._C[j, k]
1204
+ else:
1205
+ neg *= alg._U.gen(j) ** (-self._C[j, k])
1206
+ if self._B[j, k] > 0:
1207
+ pos *= self.F_polynomial(j) ** self._B[j, k]
1208
+ elif self._B[j, k] < 0:
1209
+ neg *= self.F_polynomial(j) ** (-self._B[j, k])
1210
+ return (pos + neg) // alg.F_polynomial(old_g_vector)
1211
+
1212
+ ##############################################################################
1213
+ # Cluster algebras
1214
+ ##############################################################################
1215
+
1216
+
1217
+ class ClusterAlgebra(Parent, UniqueRepresentation):
1218
+ r"""
1219
+ A Cluster Algebra.
1220
+
1221
+ INPUT:
1222
+
1223
+ - ``data`` -- some data defining a cluster algebra; it can be anything
1224
+ that can be parsed by :class:`ClusterQuiver`
1225
+
1226
+ - ``scalars`` -- a ring (default: `\ZZ`); the scalars over
1227
+ which the cluster algebra is defined
1228
+
1229
+ - ``cluster_variable_prefix`` -- string (default: ``'x'``); it needs to be
1230
+ a valid variable name
1231
+
1232
+ - ``cluster_variable_names`` -- list of strings; each element needs
1233
+ to be a valid variable name; supersedes ``cluster_variable_prefix``
1234
+
1235
+ - ``coefficient_prefix`` -- string (default: ``'y'``); it needs to be
1236
+ a valid variable name
1237
+
1238
+ - ``coefficient_names`` -- list of strings; each element needs
1239
+ to be a valid variable name; supersedes ``cluster_variable_prefix``
1240
+
1241
+ - ``principal_coefficients`` -- boolean (default: ``False``); supersedes any
1242
+ coefficient defined by ``data``
1243
+
1244
+ ALGORITHM:
1245
+
1246
+ The implementation is mainly based on [FZ2007]_ and [NZ2012]_.
1247
+
1248
+ EXAMPLES::
1249
+
1250
+ sage: B = matrix([(0, 1, 0, 0), (-1, 0, -1, 0), (0, 1, 0, 1), (0, 0, -2, 0), (-1, 0, 0, 0), (0, -1, 0, 0)])
1251
+ sage: A = ClusterAlgebra(B); A
1252
+ A Cluster Algebra with cluster variables x0, x1, x2, x3
1253
+ and coefficients y0, y1 over Integer Ring
1254
+ sage: A.gens()
1255
+ (x0, x1, x2, x3, y0, y1)
1256
+ sage: A = ClusterAlgebra(['A', 2]); A
1257
+ A Cluster Algebra with cluster variables x0, x1 and no coefficients
1258
+ over Integer Ring
1259
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True); A.gens()
1260
+ (x0, x1, y0, y1)
1261
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True, coefficient_prefix='x'); A.gens()
1262
+ (x0, x1, x2, x3)
1263
+ sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, cluster_variable_names=['a', 'b', 'c']); A.gens()
1264
+ (a, b, c, y0, y1, y2)
1265
+ sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, cluster_variable_names=['a', 'b'])
1266
+ Traceback (most recent call last):
1267
+ ...
1268
+ ValueError: cluster_variable_names should be an iterable of 3 valid variable names
1269
+ sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, coefficient_names=['a', 'b', 'c']); A.gens()
1270
+ (x0, x1, x2, a, b, c)
1271
+ sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, coefficient_names=['a', 'b'])
1272
+ Traceback (most recent call last):
1273
+ ...
1274
+ ValueError: coefficient_names should be an iterable of 3 valid variable names
1275
+ """
1276
+
1277
+ @staticmethod
1278
+ def __classcall__(self, data, **kwargs):
1279
+ r"""
1280
+ Preparse input.
1281
+
1282
+ EXAMPLES::
1283
+
1284
+ sage: A = ClusterAlgebra(['A', 2]); A # indirect doctest
1285
+ A Cluster Algebra with cluster variables x0, x1 and no coefficients
1286
+ over Integer Ring
1287
+
1288
+ Check that :issue:`28176` is fixed::
1289
+
1290
+ sage: A1 = ClusterAlgebra(['A',2])
1291
+ sage: A2 = ClusterAlgebra(['A',2], cluster_variable_prefix='x')
1292
+ sage: A1 is A2
1293
+ True
1294
+ sage: A3 = ClusterAlgebra(Matrix([[0,1],[-1,0]]))
1295
+ sage: A1 is A3
1296
+ True
1297
+ sage: A4 = ClusterAlgebra([[0,1]]) # built from a digraph
1298
+ sage: A1 is A4
1299
+ True
1300
+ """
1301
+ # Use ClusterQuiver to parse the input; eventually we may want to avoid this
1302
+ Q = ClusterQuiver(data)
1303
+
1304
+ # Rank
1305
+ n = Q.n()
1306
+
1307
+ # Exchange matrix
1308
+ B0 = Q.b_matrix()[:n, :]
1309
+
1310
+ # Coefficient matrix
1311
+ if kwargs.pop('principal_coefficients', False):
1312
+ M0 = identity_matrix(n)
1313
+ else:
1314
+ M0 = Q.b_matrix()[n:, :]
1315
+ m = M0.nrows()
1316
+
1317
+ B0 = block_matrix([[B0], [M0]])
1318
+ B0.set_immutable()
1319
+
1320
+ # Determine the names of the initial cluster variables
1321
+ kwargs.setdefault('cluster_variable_prefix', 'x')
1322
+ kwargs['cluster_variable_names'] = tuple(kwargs.get('cluster_variable_names',
1323
+ [kwargs['cluster_variable_prefix'] + str(i) for i in range(n)]))
1324
+ if len(kwargs['cluster_variable_names']) != n:
1325
+ raise ValueError("cluster_variable_names should be an iterable of %d valid variable names" % n)
1326
+
1327
+ # Determine the names of the coefficients
1328
+ coefficient_prefix = kwargs.pop('coefficient_prefix', 'y')
1329
+ offset = n if coefficient_prefix == kwargs['cluster_variable_prefix'] else 0
1330
+ kwargs['coefficient_names'] = tuple(kwargs.get('coefficient_names',
1331
+ [coefficient_prefix + str(i) for i in range(offset, m + offset)]))
1332
+ if len(kwargs['coefficient_names']) != m:
1333
+ raise ValueError("coefficient_names should be an iterable of %d valid variable names" % m)
1334
+
1335
+ # Compute the next free index for new named variables
1336
+ # This is the first integer nfi such that for any j >= nfi
1337
+ # kwargs['cluster_variable_prefix']+str(j) is not the name of an
1338
+ # initial cluster variable nor a coefficient. This will be used in
1339
+ # mutate_initial to name new cluster variables.
1340
+ splitnames = (w.partition(kwargs['cluster_variable_prefix'])
1341
+ for w in
1342
+ kwargs['cluster_variable_names'] + kwargs['coefficient_names'])
1343
+ nfi = 1 + max((int(v) for u, _, v in splitnames
1344
+ if u == '' and v.isdigit()), default=-1)
1345
+ kwargs.setdefault('next_free_index', nfi)
1346
+
1347
+ # Determine scalars
1348
+ kwargs.setdefault('scalars', ZZ)
1349
+
1350
+ return super().__classcall__(self, B0, **kwargs)
1351
+
1352
+ def __init__(self, B, **kwargs):
1353
+ """
1354
+ Initialize ``self``.
1355
+
1356
+ TESTS::
1357
+
1358
+ sage: B = matrix([(0, 1, 0, 0), (-1, 0, -1, 0), (0, 1, 0, 1), (0, 0, -2, 0), (-1, 0, 0, 0), (0, -1, 0, 0)])
1359
+ sage: A = ClusterAlgebra(B)
1360
+ sage: A.clear_computed_data()
1361
+ sage: TestSuite(A).run()
1362
+ sage: A = ClusterAlgebra(['A', 2])
1363
+ sage: A.clear_computed_data()
1364
+ sage: TestSuite(A).run()
1365
+ sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True)
1366
+ sage: A.clear_computed_data()
1367
+ sage: TestSuite(A).run()
1368
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True, coefficient_prefix='x')
1369
+ sage: A.clear_computed_data()
1370
+ sage: TestSuite(A).run()
1371
+ sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, cluster_variable_names=['a','b','c'])
1372
+ sage: A.clear_computed_data()
1373
+ sage: TestSuite(A).run()
1374
+ sage: A = ClusterAlgebra(['A', 3], principal_coefficients=True, coefficient_names=['a','b','c'])
1375
+ sage: A.clear_computed_data()
1376
+ sage: TestSuite(A).run()
1377
+ """
1378
+ # Exchange matrix
1379
+ self._B0 = copy(B)
1380
+
1381
+ # Rank
1382
+ self._n = B.ncols()
1383
+
1384
+ M0 = B[self._n:, :]
1385
+ m = M0.nrows()
1386
+
1387
+ # Ambient space for F-polynomials
1388
+ # NOTE: for speed purposes we need to have QQ here instead of the more
1389
+ # natural ZZ. The reason is that _mutated_F is faster if we do not cast
1390
+ # the result to polynomials but then we get "rational" coefficients
1391
+ self._U = PolynomialRing(QQ, ['u%s' % i for i in range(self._n)])
1392
+
1393
+ # Setup infrastructure to store computed data
1394
+ self.clear_computed_data()
1395
+
1396
+ # Data to build new named variables
1397
+ self._cluster_variable_prefix = kwargs['cluster_variable_prefix']
1398
+ self._next_free_index = kwargs['next_free_index']
1399
+
1400
+ # Base ring
1401
+ base = LaurentPolynomialRing(kwargs['scalars'], kwargs['coefficient_names']) if m > 0 else kwargs['scalars']
1402
+
1403
+ # Have we got principal coefficients?
1404
+ self.Element = PrincipalClusterAlgebraElement if M0 == identity_matrix(self._n) else ClusterAlgebraElement
1405
+
1406
+ # Setup Parent and ambient
1407
+ names = kwargs['cluster_variable_names'] + kwargs['coefficient_names']
1408
+ self._ambient = LaurentPolynomialRing(kwargs['scalars'], names)
1409
+ Parent.__init__(self, base=base, category=Rings(kwargs['scalars']).Commutative().Subobjects(), names=names)
1410
+
1411
+ # Data to compute cluster variables using separation of additions
1412
+ # NOTE: storing both _B0 as rectangular matrix and _yhat is redundant.
1413
+ # We keep both around for speed purposes.
1414
+ self._y = {self._U.gen(j): prod(self._base.gen(i) ** M0[i, j] for i in range(m))
1415
+ for j in range(self._n)}
1416
+ self._yhat = {self._U.gen(j): prod(self._ambient.gen(i) ** self._B0[i, j]
1417
+ for i in range(self._n + m))
1418
+ for j in range(self._n)}
1419
+
1420
+ # Register embedding into self.ambient()
1421
+ embedding = SetMorphism(Hom(self, self.ambient()), lambda x: x.lift())
1422
+ self._populate_coercion_lists_(embedding=embedding)
1423
+
1424
+ def _repr_(self) -> str:
1425
+ r"""
1426
+ Return the string representation of ``self``.
1427
+
1428
+ EXAMPLES::
1429
+
1430
+ sage: A = ClusterAlgebra(matrix(1), principal_coefficients=True); A
1431
+ A Cluster Algebra with cluster variable x0
1432
+ and coefficient y0 over Integer Ring
1433
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True); A
1434
+ A Cluster Algebra with cluster variables x0, x1
1435
+ and coefficients y0, y1 over Integer Ring
1436
+ """
1437
+ var_names = self.initial_cluster_variable_names()
1438
+ var_names_str = (" " if len(var_names) == 1 else "s ") + ", ".join(var_names)
1439
+ coeff_names = self.coefficient_names()
1440
+ coeff_prefix = " and" + (" " if len(coeff_names) > 0 else " no ") + "coefficient"
1441
+ coeff = coeff_prefix + (" " if len(coeff_names) == 1 else "s ") + ", ".join(coeff_names) + (" " if len(coeff_names) > 0 else "")
1442
+ return "A Cluster Algebra with cluster variable" + var_names_str + coeff + "over " + repr(self.scalars())
1443
+
1444
+ def _an_element_(self):
1445
+ r"""
1446
+ Return an element of ``self``.
1447
+
1448
+ EXAMPLES::
1449
+
1450
+ sage: A = ClusterAlgebra(['A', 2])
1451
+ sage: A.an_element()
1452
+ x0
1453
+ """
1454
+ return self.initial_cluster_variable(0)
1455
+
1456
+ def _coerce_map_from_(self, other):
1457
+ r"""
1458
+ Test whether there is a coercion from ``other`` to ``self``.
1459
+
1460
+ ALGORITHM:
1461
+
1462
+ If ``other`` is an instance of :class:`ClusterAlgebra` then allow
1463
+ coercion if ``other.ambient()`` can be coerced into ``self.ambient()``
1464
+ and ``other`` can be obtained from ``self`` by permuting variables
1465
+ and coefficients and/or freezing some initial cluster variables.
1466
+
1467
+ Otherwise allow anything that coerces into ``self.base()`` to coerce
1468
+ into ``self``.
1469
+
1470
+ EXAMPLES::
1471
+
1472
+ sage: B1 = matrix([(0, 1, 0, 0), (-1, 0, -1, 0), (0, 1, 0, 1), (0, 0, -2, 0), (-1, 0, 0, 0), (0, -1, 0, 0)])
1473
+ sage: B2 = B1.matrix_from_columns([0, 1, 2])
1474
+ sage: A1 = ClusterAlgebra(B1, coefficient_prefix='x')
1475
+ sage: A2 = ClusterAlgebra(B2, coefficient_prefix='x')
1476
+ sage: A1.has_coerce_map_from(A2)
1477
+ True
1478
+ sage: A2.has_coerce_map_from(A1)
1479
+ False
1480
+ sage: f = A1.coerce_map_from(A2)
1481
+ sage: seq = A2.find_g_vector((-1, 1, -1)); seq # random
1482
+ [0, 2, 1]
1483
+ sage: S = A1.initial_seed(); S.mutate(seq)
1484
+ sage: S.cluster_variable(seq[-1]) == f(A2.cluster_variable((-1, 1, -1)))
1485
+ True
1486
+ sage: B3 = B1.matrix_from_columns([1, 2, 3]); B3
1487
+ [ 1 0 0]
1488
+ [ 0 -1 0]
1489
+ [ 1 0 1]
1490
+ [ 0 -2 0]
1491
+ [ 0 0 0]
1492
+ [-1 0 0]
1493
+ sage: G = PermutationGroup(['(1, 2, 3, 4)'])
1494
+ sage: B3.permute_rows(G.gen(0)); B3
1495
+ [ 0 -1 0]
1496
+ [ 1 0 1]
1497
+ [ 0 -2 0]
1498
+ [ 1 0 0]
1499
+ [ 0 0 0]
1500
+ [-1 0 0]
1501
+ sage: A3 = ClusterAlgebra(B3, cluster_variable_names=['x1', 'x2', 'x3'], coefficient_names=['x0', 'x4', 'x5'])
1502
+ sage: A1.has_coerce_map_from(A3)
1503
+ True
1504
+ sage: g = A1.coerce_map_from(A3)
1505
+ sage: seq1 = A3.find_g_vector((1, -2, 2))
1506
+ sage: seq2 = [G.gen(0)(x + 1) - 1 for x in seq1 ]
1507
+ sage: S = A1.initial_seed(); S.mutate(seq2)
1508
+ sage: S.cluster_variable(seq2[-1]) == g(A3.cluster_variable((1, -2, 2)))
1509
+ True
1510
+
1511
+ Check that :issue:`23654` is fixed::
1512
+
1513
+ sage: A = ClusterAlgebra(['A',2])
1514
+ sage: AA = ClusterAlgebra(['A',3])
1515
+ sage: A.has_coerce_map_from(AA)
1516
+ False
1517
+ """
1518
+ if isinstance(other, ClusterAlgebra):
1519
+ gen_s = self.gens()
1520
+ gen_o = other.gens()
1521
+ if len(gen_s) == len(gen_o):
1522
+ f = self.ambient().coerce_map_from(other.ambient())
1523
+ if f is not None:
1524
+ perm = Permutation([gen_s.index(self(f(v))) + 1 for v in gen_o])
1525
+ n = self.rank()
1526
+ M0 = self._B0[n:, :]
1527
+ m = M0.nrows()
1528
+ B = block_matrix([[self.b_matrix(), -M0.transpose()], [M0, matrix(m)]])
1529
+ B.permute_rows_and_columns(perm, perm)
1530
+ return B[:, :other.rank()] == other._B0
1531
+
1532
+ # everything that is in the base can be coerced to self
1533
+ return self.base().has_coerce_map_from(other)
1534
+
1535
+ @cached_method
1536
+ def coxeter_element(self):
1537
+ r"""
1538
+ Return the Coxeter element associated to the initial exchange matrix, if acyclic.
1539
+
1540
+ EXAMPLES::
1541
+
1542
+ sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]]))
1543
+ sage: A.coxeter_element()
1544
+ [0, 1, 2]
1545
+
1546
+ Raise an error if the initial exchange matrix is not acyclic::
1547
+
1548
+ sage: A = ClusterAlgebra(matrix([[0,1,-1],[-1,0,1],[1,-1,0]]))
1549
+ sage: A.coxeter_element()
1550
+ Traceback (most recent call last):
1551
+ ...
1552
+ ValueError: the initial exchange matrix is not acyclic
1553
+ """
1554
+ dg = DiGraph(self.b_matrix().apply_map(lambda x: ZZ.zero() if x <= 0 else ZZ.one()))
1555
+ acyclic, coxeter = dg.is_directed_acyclic(certificate=True)
1556
+ if not acyclic:
1557
+ raise ValueError("the initial exchange matrix is not acyclic")
1558
+ return coxeter
1559
+
1560
+ @cached_method
1561
+ def is_acyclic(self) -> bool:
1562
+ r"""
1563
+ Return ``True`` if the exchange matrix in the initial seed is acyclic, ``False`` otherwise.
1564
+
1565
+ EXAMPLES::
1566
+
1567
+ sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]]))
1568
+ sage: A.is_acyclic()
1569
+ True
1570
+ sage: A = ClusterAlgebra(matrix([[0,1,-1],[-1,0,1],[1,-1,0]]))
1571
+ sage: A.is_acyclic()
1572
+ False
1573
+ """
1574
+ dg = DiGraph(self.b_matrix().apply_map(lambda x: ZZ.zero() if x <= 0 else ZZ.one()))
1575
+ return dg.is_directed_acyclic()
1576
+
1577
+ def rank(self):
1578
+ r"""
1579
+ Return the rank of ``self``, i.e. the number of cluster variables
1580
+ in any seed.
1581
+
1582
+ EXAMPLES::
1583
+
1584
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True); A
1585
+ A Cluster Algebra with cluster variables x0, x1
1586
+ and coefficients y0, y1 over Integer Ring
1587
+ sage: A.rank()
1588
+ 2
1589
+ """
1590
+ return self._n
1591
+
1592
+ def current_seed(self):
1593
+ r"""
1594
+ Return the current seed of ``self``.
1595
+
1596
+ EXAMPLES::
1597
+
1598
+ sage: A = ClusterAlgebra(['A', 2])
1599
+ sage: A.clear_computed_data()
1600
+ sage: A.current_seed()
1601
+ The initial seed of a Cluster Algebra with cluster variables x0, x1
1602
+ and no coefficients over Integer Ring
1603
+ """
1604
+ return self._seed
1605
+
1606
+ def set_current_seed(self, seed):
1607
+ r"""
1608
+ Set the value reported by :meth:`current_seed` to ``seed``,
1609
+ if it makes sense.
1610
+
1611
+ INPUT:
1612
+
1613
+ - ``seed`` -- a :class:`ClusterAlgebraSeed`
1614
+
1615
+ EXAMPLES::
1616
+
1617
+ sage: A = ClusterAlgebra(['A', 2])
1618
+ sage: A.clear_computed_data()
1619
+ sage: S = copy(A.current_seed())
1620
+ sage: S.mutate([0, 1, 0])
1621
+ sage: A.current_seed() == S
1622
+ False
1623
+ sage: A.set_current_seed(S)
1624
+ sage: A.current_seed() == S
1625
+ True
1626
+ sage: A1 = ClusterAlgebra(['B', 2])
1627
+ sage: A.set_current_seed(A1.initial_seed())
1628
+ Traceback (most recent call last):
1629
+ ...
1630
+ ValueError: this is not a seed in this cluster algebra
1631
+ """
1632
+ if self.contains_seed(seed):
1633
+ self._seed = seed
1634
+ else:
1635
+ raise ValueError("this is not a seed in this cluster algebra")
1636
+
1637
+ def reset_current_seed(self):
1638
+ r"""
1639
+ Reset the value reported by :meth:`current_seed`
1640
+ to :meth:`initial_seed`.
1641
+
1642
+ EXAMPLES::
1643
+
1644
+ sage: A = ClusterAlgebra(['A', 2])
1645
+ sage: A.clear_computed_data()
1646
+ sage: A.current_seed().mutate([1, 0])
1647
+ sage: A.current_seed() == A.initial_seed()
1648
+ False
1649
+ sage: A.reset_current_seed()
1650
+ sage: A.current_seed() == A.initial_seed()
1651
+ True
1652
+ """
1653
+ self._seed = self.initial_seed()
1654
+
1655
+ def clear_computed_data(self):
1656
+ r"""
1657
+ Clear the cache of computed g-vectors and F-polynomials
1658
+ and reset both the current seed and the exploring iterator.
1659
+
1660
+ EXAMPLES::
1661
+
1662
+ sage: A = ClusterAlgebra(['A', 2])
1663
+ sage: A.clear_computed_data()
1664
+ sage: sorted(A.g_vectors_so_far())
1665
+ [(0, 1), (1, 0)]
1666
+ sage: A.current_seed().mutate([1, 0])
1667
+ sage: sorted(A.g_vectors_so_far())
1668
+ [(-1, 0), (0, -1), (0, 1), (1, 0)]
1669
+ sage: A.clear_computed_data()
1670
+ sage: sorted(A.g_vectors_so_far())
1671
+ [(0, 1), (1, 0)]
1672
+ """
1673
+ I = identity_matrix(self._n)
1674
+ self._path_dict = {v: [] for v in map(tuple, I.columns())}
1675
+ self._F_poly_dict = {v: self._U(1) for v in self._path_dict}
1676
+ self.reset_current_seed()
1677
+ self.reset_exploring_iterator()
1678
+
1679
+ def contains_seed(self, seed) -> bool:
1680
+ r"""
1681
+ Test if ``seed`` is a seed of ``self``.
1682
+
1683
+ INPUT:
1684
+
1685
+ - ``seed`` -- a :class:`ClusterAlgebraSeed`
1686
+
1687
+ EXAMPLES::
1688
+
1689
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True); A
1690
+ A Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring
1691
+ sage: S = copy(A.current_seed())
1692
+ sage: A.contains_seed(S)
1693
+ True
1694
+ """
1695
+ computed_sd = self.initial_seed()
1696
+ computed_sd.mutate(seed._path, mutating_F=False)
1697
+ return computed_sd == seed
1698
+
1699
+ def initial_seed(self):
1700
+ r"""
1701
+ Return the initial seed of ``self``.
1702
+
1703
+ EXAMPLES::
1704
+
1705
+ sage: A = ClusterAlgebra(['A', 2])
1706
+ sage: A.initial_seed()
1707
+ The initial seed of a Cluster Algebra with cluster variables x0, x1 and no coefficients over Integer Ring
1708
+ """
1709
+ n = self.rank()
1710
+ I = identity_matrix(n)
1711
+ return ClusterAlgebraSeed(self.b_matrix(), I, I, self)
1712
+
1713
+ def b_matrix(self):
1714
+ r"""
1715
+ Return the initial exchange matrix of ``self``.
1716
+
1717
+ EXAMPLES::
1718
+
1719
+ sage: A = ClusterAlgebra(['A', 2])
1720
+ sage: A.b_matrix()
1721
+ [ 0 1]
1722
+ [-1 0]
1723
+ """
1724
+ n = self.rank()
1725
+ return copy(self._B0[:n, :])
1726
+
1727
+ def euler_matrix(self):
1728
+ r"""
1729
+ Return the Euler matrix associated to ``self``.
1730
+
1731
+ ALGORITHM:
1732
+
1733
+ This method returns the matrix of the bilinear form defined in Equation (2.1) of [ReSt2020]_ .
1734
+
1735
+ EXAMPLES::
1736
+
1737
+ sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]]))
1738
+ sage: A.euler_matrix()
1739
+ [ 1 0 0]
1740
+ [-1 1 0]
1741
+ [-1 -1 1]
1742
+
1743
+ Raise an error if the initial exchange matrix is not acyclic::
1744
+
1745
+ sage: A = ClusterAlgebra(matrix([[0,1,-1],[-1,0,1],[1,-1,0]]))
1746
+ sage: A.euler_matrix()
1747
+ Traceback (most recent call last):
1748
+ ...
1749
+ ValueError: the initial exchange matrix is not acyclic
1750
+ """
1751
+ if not self.is_acyclic():
1752
+ raise ValueError("the initial exchange matrix is not acyclic")
1753
+ return 1 + self.b_matrix().apply_map(lambda x: min(ZZ.zero(), x))
1754
+
1755
+ def d_vector_to_g_vector(self, d) -> tuple:
1756
+ r"""
1757
+ Return the g-vector of an element of ``self`` having d-vector ``d``
1758
+
1759
+ INPUT:
1760
+
1761
+ - ``d`` -- the d-vector
1762
+
1763
+ ALGORITHM:
1764
+
1765
+ This method implements the piecewise-linear map `\\nu_c` introduced in Section 9.1 of [ReSt2020]_.
1766
+
1767
+ .. WARNING::
1768
+
1769
+ This implementation works only when the initial exchange matrix is acyclic.
1770
+
1771
+ EXAMPLES::
1772
+
1773
+ sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]]))
1774
+ sage: A.d_vector_to_g_vector((1,0,-1))
1775
+ (-1, 1, 2)
1776
+ """
1777
+ dm = vector(x if x < 0 else 0 for x in d)
1778
+ dp = vector(d) - dm
1779
+ return tuple(- dm - self.euler_matrix() * dp)
1780
+
1781
+ def g_vector_to_d_vector(self, g) -> tuple:
1782
+ r"""
1783
+ Return the d-vector of an element of ``self`` having g-vector ``g``
1784
+
1785
+ INPUT:
1786
+
1787
+ - ``g`` -- the g-vector
1788
+
1789
+ ALGORITHM:
1790
+
1791
+ This method implements the inverse of the piecewise-linear map `\\nu_c` introduced in Section 9.1 of [ReSt2020]_.
1792
+
1793
+ .. WARNING::
1794
+
1795
+ This implementation works only when the initial exchange matrix is acyclic.
1796
+
1797
+ EXAMPLES::
1798
+
1799
+ sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]]))
1800
+ sage: A.g_vector_to_d_vector((-1,1,2))
1801
+ (1, 0, -1)
1802
+ """
1803
+ E = -self.euler_matrix()
1804
+ c = self.coxeter_element()
1805
+ dp = vector(ZZ, self.rank())
1806
+ g = vector(g)
1807
+ for i in c:
1808
+ dp[i] = -min(g[i], 0)
1809
+ g += min(g[i], 0) * E.column(i)
1810
+ return tuple(-g + dp)
1811
+
1812
+ def g_vectors(self, mutating_F=True):
1813
+ r"""
1814
+ Return an iterator producing all the g-vectors of ``self``.
1815
+
1816
+ INPUT:
1817
+
1818
+ - ``mutating_F`` -- boolean (default: ``True``); whether to compute
1819
+ F-polynomials; disable this for speed considerations
1820
+
1821
+ ALGORITHM:
1822
+
1823
+ This method does not use the caching framework provided by ``self``,
1824
+ but recomputes all the g-vectors from scratch. On the other hand it
1825
+ stores the results so that other methods like :meth:`g_vectors_so_far`
1826
+ can access them afterwards.
1827
+
1828
+ EXAMPLES::
1829
+
1830
+ sage: A = ClusterAlgebra(['A', 3])
1831
+ sage: len(list(A.g_vectors()))
1832
+ 9
1833
+ """
1834
+ seeds = self.seeds(mutating_F=mutating_F)
1835
+ found_so_far = set()
1836
+ for g in next(seeds).g_vectors():
1837
+ found_so_far.add(g)
1838
+ yield g
1839
+ for S in seeds:
1840
+ j = S.path_from_initial_seed()[-1]
1841
+ g = S.g_vector(j)
1842
+ if g not in found_so_far:
1843
+ found_so_far.add(g)
1844
+ yield g
1845
+
1846
+ def cluster_variables(self):
1847
+ r"""
1848
+ Return an iterator producing all the cluster variables of ``self``.
1849
+
1850
+ ALGORITHM:
1851
+
1852
+ This method does not use the caching framework provided by ``self``,
1853
+ but recomputes all the cluster variables from scratch. On the other
1854
+ hand it stores the results so that other methods like
1855
+ :meth:`cluster_variables_so_far` can access them afterwards.
1856
+
1857
+ EXAMPLES::
1858
+
1859
+ sage: A = ClusterAlgebra(['A', 3])
1860
+ sage: len(list(A.cluster_variables()))
1861
+ 9
1862
+ """
1863
+ return map(self.cluster_variable, self.g_vectors())
1864
+
1865
+ def F_polynomials(self):
1866
+ r"""
1867
+ Return an iterator producing all the F_polynomials of ``self``.
1868
+
1869
+ ALGORITHM:
1870
+
1871
+ This method does not use the caching framework provided by ``self``,
1872
+ but recomputes all the F-polynomials from scratch. On the other hand
1873
+ it stores the results so that other methods like
1874
+ :meth:`F_polynomials_so_far` can access them afterwards.
1875
+
1876
+ EXAMPLES::
1877
+
1878
+ sage: A = ClusterAlgebra(['A', 3])
1879
+ sage: len(list(A.F_polynomials()))
1880
+ 9
1881
+ """
1882
+ return map(self.F_polynomial, self.g_vectors())
1883
+
1884
+ def g_vectors_so_far(self) -> list:
1885
+ r"""
1886
+ Return a list of the g-vectors of cluster variables encountered so far.
1887
+
1888
+ EXAMPLES::
1889
+
1890
+ sage: A = ClusterAlgebra(['A', 2])
1891
+ sage: A.clear_computed_data()
1892
+ sage: A.current_seed().mutate(0)
1893
+ sage: sorted(A.g_vectors_so_far())
1894
+ [(-1, 1), (0, 1), (1, 0)]
1895
+ """
1896
+ return list(self._path_dict)
1897
+
1898
+ def cluster_variables_so_far(self) -> list:
1899
+ r"""
1900
+ Return a list of the cluster variables encountered so far.
1901
+
1902
+ EXAMPLES::
1903
+
1904
+ sage: A = ClusterAlgebra(['A', 2])
1905
+ sage: A.clear_computed_data()
1906
+ sage: A.current_seed().mutate(0)
1907
+ sage: sorted(A.cluster_variables_so_far(), key=str)
1908
+ [(x1 + 1)/x0, x0, x1]
1909
+ """
1910
+ return [self.cluster_variable(v) for v in self.g_vectors_so_far()]
1911
+
1912
+ def F_polynomials_so_far(self) -> list:
1913
+ r"""
1914
+ Return a list of the F-polynomials encountered so far.
1915
+
1916
+ EXAMPLES::
1917
+
1918
+ sage: A = ClusterAlgebra(['A', 2])
1919
+ sage: A.clear_computed_data()
1920
+ sage: A.current_seed().mutate(0)
1921
+ sage: sorted(A.F_polynomials_so_far(), key=str)
1922
+ [1, 1, u0 + 1]
1923
+ """
1924
+ return list(self._F_poly_dict.values())
1925
+
1926
+ @cached_method(key=lambda a, b: tuple(b))
1927
+ def cluster_variable(self, g_vector):
1928
+ r"""
1929
+ Return the cluster variable with g-vector ``g_vector`` if it has
1930
+ been found.
1931
+
1932
+ INPUT:
1933
+
1934
+ - ``g_vector`` -- tuple; the g-vector of the cluster variable to return
1935
+
1936
+ ALGORITHM:
1937
+
1938
+ This function computes cluster variables from their g-vectors and
1939
+ F-polynomials using the "separation of additions" formula of
1940
+ Theorem 3.7 in [FZ2007]_.
1941
+
1942
+ EXAMPLES::
1943
+
1944
+ sage: A = ClusterAlgebra(['A', 2])
1945
+ sage: A.initial_seed().mutate(0)
1946
+ sage: A.cluster_variable((-1, 1))
1947
+ (x1 + 1)/x0
1948
+ """
1949
+ g_vector = tuple(g_vector)
1950
+ F = self.F_polynomial(g_vector)
1951
+ F_std = F.subs(self._yhat)
1952
+ g_mon = prod(self.ambient().gen(i) ** g_vector[i] for i in range(self.rank()))
1953
+ F_trop = self.ambient()(F.subs(self._y))._fraction_pair()[1]
1954
+ return self.retract(g_mon * F_std * F_trop)
1955
+
1956
+ def F_polynomial(self, g_vector):
1957
+ r"""
1958
+ Return the F-polynomial with g-vector ``g_vector`` if it has
1959
+ been found.
1960
+
1961
+ INPUT:
1962
+
1963
+ - ``g_vector`` -- tuple; the g-vector of the F-polynomial to return
1964
+
1965
+ EXAMPLES::
1966
+
1967
+ sage: A = ClusterAlgebra(['A', 2])
1968
+ sage: A.clear_computed_data()
1969
+ sage: A.F_polynomial((-1, 1))
1970
+ Traceback (most recent call last):
1971
+ ...
1972
+ KeyError: 'the g-vector (-1, 1) has not been found yet'
1973
+ sage: A.initial_seed().mutate(0, mutating_F=False)
1974
+ sage: A.F_polynomial((-1, 1))
1975
+ Traceback (most recent call last):
1976
+ ...
1977
+ KeyError: 'the F-polynomial with g-vector (-1, 1) has not been computed yet;
1978
+ you can compute it by mutating from the initial seed along the sequence [0]'
1979
+ sage: A.initial_seed().mutate(0)
1980
+ sage: A.F_polynomial((-1, 1))
1981
+ u0 + 1
1982
+ """
1983
+ g_vector = tuple(g_vector)
1984
+ try:
1985
+ return self._F_poly_dict[g_vector]
1986
+ except KeyError:
1987
+ if g_vector in self._path_dict:
1988
+ msg = "the F-polynomial with g-vector {} has not been computed yet; ".format(g_vector)
1989
+ msg += "you can compute it by mutating from the initial seed along the sequence "
1990
+ msg += str(self._path_dict[g_vector])
1991
+ raise KeyError(msg)
1992
+ else:
1993
+ raise KeyError("the g-vector %s has not been found yet" % str(g_vector))
1994
+
1995
+ def find_g_vector(self, g_vector, depth=infinity):
1996
+ r"""
1997
+ Return a mutation sequence to obtain a seed containing the g-vector
1998
+ ``g_vector`` from the initial seed.
1999
+
2000
+ INPUT:
2001
+
2002
+ - ``g_vector`` -- tuple; the g-vector to find
2003
+ - ``depth`` -- positive integer or infinity (default: ``infinity``);
2004
+ the maximum distance from ``self.current_seed`` to reach
2005
+
2006
+ OUTPUT:
2007
+
2008
+ This function returns a list of integers if it can find ``g_vector``,
2009
+ otherwise it returns ``None``. If the exploring iterator stops, it
2010
+ means that the algebra is of finite type and ``g_vector`` is not the
2011
+ g-vector of any cluster variable. In this case the function resets the
2012
+ iterator and raises an error.
2013
+
2014
+ EXAMPLES::
2015
+
2016
+ sage: A = ClusterAlgebra(['G', 2], principal_coefficients=True)
2017
+ sage: A.clear_computed_data()
2018
+ sage: A.find_g_vector((-2, 3), depth=2)
2019
+ sage: A.find_g_vector((-2, 3), depth=3)
2020
+ [0, 1, 0]
2021
+ sage: A.find_g_vector((1, 1), depth=3)
2022
+ sage: A.find_g_vector((1, 1), depth=4)
2023
+ Traceback (most recent call last):
2024
+ ...
2025
+ ValueError: (1, 1) is not the g-vector of any cluster variable of a
2026
+ Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1
2027
+ over Integer Ring
2028
+ """
2029
+ g_vector = tuple(g_vector)
2030
+ while g_vector not in self.g_vectors_so_far() and self._explored_depth <= depth:
2031
+ try:
2032
+ seed = next(self._sd_iter)
2033
+ if isinstance(seed, ClusterAlgebraSeed):
2034
+ self._explored_depth = seed.depth()
2035
+ else:
2036
+ # We got an exception because self._sd_iter caught a KeyboardInterrupt, let's raise it again
2037
+ raise seed
2038
+ except StopIteration:
2039
+ # Unless self._sd_iter has been manually altered, we checked
2040
+ # all the seeds of self and did not find g_vector.
2041
+ # Do some house cleaning before failing
2042
+ self.reset_exploring_iterator()
2043
+ raise ValueError("%s is not the g-vector of any cluster variable of a %s" % (str(g_vector), str(self)[2:]))
2044
+ return copy(self._path_dict.get(g_vector, None))
2045
+
2046
+ def ambient(self):
2047
+ r"""
2048
+ Return the Laurent polynomial ring containing ``self``.
2049
+
2050
+ EXAMPLES::
2051
+
2052
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True)
2053
+ sage: A.ambient()
2054
+ Multivariate Laurent Polynomial Ring in x0, x1, y0, y1 over Integer Ring
2055
+ """
2056
+ return self._ambient
2057
+
2058
+ def scalars(self):
2059
+ r"""
2060
+ Return the ring of scalars over which ``self`` is defined.
2061
+
2062
+ EXAMPLES::
2063
+
2064
+ sage: A = ClusterAlgebra(['A', 2])
2065
+ sage: A.scalars()
2066
+ Integer Ring
2067
+ """
2068
+ return self.base().base()
2069
+
2070
+ def lift(self, x):
2071
+ r"""
2072
+ Return ``x`` as an element of :meth:`ambient`.
2073
+
2074
+ EXAMPLES::
2075
+
2076
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True)
2077
+ sage: x = A.cluster_variable((1, 0))
2078
+ sage: A.lift(x).parent()
2079
+ Multivariate Laurent Polynomial Ring in x0, x1, y0, y1 over Integer Ring
2080
+ """
2081
+ return self.ambient()(x.value)
2082
+
2083
+ def retract(self, x):
2084
+ r"""
2085
+ Return ``x`` as an element of ``self``.
2086
+
2087
+ EXAMPLES::
2088
+
2089
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True)
2090
+ sage: L = A.ambient()
2091
+ sage: x = L.gen(0)
2092
+ sage: A.retract(x).parent()
2093
+ A Cluster Algebra with cluster variables x0, x1 and coefficients y0, y1 over Integer Ring
2094
+ """
2095
+ return self(x)
2096
+
2097
+ @cached_method
2098
+ def gens(self) -> tuple:
2099
+ r"""
2100
+ Return the list of initial cluster variables and coefficients of ``self``.
2101
+
2102
+ EXAMPLES::
2103
+
2104
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True)
2105
+ sage: A.gens()
2106
+ (x0, x1, y0, y1)
2107
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True, coefficient_prefix='x')
2108
+ sage: A.gens()
2109
+ (x0, x1, x2, x3)
2110
+ """
2111
+ return tuple(map(self.retract, self.ambient().gens()))
2112
+
2113
+ def coefficient(self, j):
2114
+ r"""
2115
+ Return the ``j``-th coefficient of ``self``.
2116
+
2117
+ INPUT:
2118
+
2119
+ - ``j`` -- integer in ``range(self.parent().rank())``;
2120
+ the index of the coefficient to return
2121
+
2122
+ EXAMPLES::
2123
+
2124
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True)
2125
+ sage: A.coefficient(0)
2126
+ y0
2127
+ """
2128
+ if not isinstance(self.base(), LaurentPolynomialRing_generic):
2129
+ raise ValueError("generator not defined")
2130
+ return self.retract(self.base().gen(j))
2131
+
2132
+ @cached_method
2133
+ def coefficients(self) -> tuple:
2134
+ r"""
2135
+ Return the list of coefficients of ``self``.
2136
+
2137
+ EXAMPLES::
2138
+
2139
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True)
2140
+ sage: A.coefficients()
2141
+ (y0, y1)
2142
+ sage: A1 = ClusterAlgebra(['B', 2])
2143
+ sage: A1.coefficients()
2144
+ ()
2145
+ """
2146
+ if isinstance(self.base(), LaurentPolynomialRing_generic):
2147
+ return tuple(map(self.retract, self.base().gens()))
2148
+ else:
2149
+ return ()
2150
+
2151
+ def coefficient_names(self) -> tuple:
2152
+ r"""
2153
+ Return the list of coefficient names.
2154
+
2155
+ EXAMPLES::
2156
+
2157
+ sage: A = ClusterAlgebra(['A', 3])
2158
+ sage: A.coefficient_names()
2159
+ ()
2160
+ sage: A1 = ClusterAlgebra(['B', 2], principal_coefficients=True)
2161
+ sage: A1.coefficient_names()
2162
+ ('y0', 'y1')
2163
+ sage: A2 = ClusterAlgebra(['C', 3], principal_coefficients=True, coefficient_prefix='x')
2164
+ sage: A2.coefficient_names()
2165
+ ('x3', 'x4', 'x5')
2166
+ """
2167
+ return self.variable_names()[self.rank():]
2168
+
2169
+ def initial_cluster_variable(self, j):
2170
+ r"""
2171
+ Return the ``j``-th initial cluster variable of ``self``.
2172
+
2173
+ INPUT:
2174
+
2175
+ - ``j`` -- integer in ``range(self.parent().rank())``;
2176
+ the index of the cluster variable to return
2177
+
2178
+ EXAMPLES::
2179
+
2180
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True)
2181
+ sage: A.initial_cluster_variable(0)
2182
+ x0
2183
+ """
2184
+ return self.retract(self.ambient().gen(j))
2185
+
2186
+ @cached_method
2187
+ def initial_cluster_variables(self) -> tuple:
2188
+ r"""
2189
+ Return the list of initial cluster variables of ``self``.
2190
+
2191
+ EXAMPLES::
2192
+
2193
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True)
2194
+ sage: A.initial_cluster_variables()
2195
+ (x0, x1)
2196
+ """
2197
+ return tuple(map(self.retract, self.ambient().gens()[:self.rank()]))
2198
+
2199
+ def initial_cluster_variable_names(self) -> tuple:
2200
+ r"""
2201
+ Return the list of initial cluster variable names.
2202
+
2203
+ EXAMPLES::
2204
+
2205
+ sage: A = ClusterAlgebra(['A', 2], principal_coefficients=True)
2206
+ sage: A.initial_cluster_variable_names()
2207
+ ('x0', 'x1')
2208
+ sage: A1 = ClusterAlgebra(['B', 2], cluster_variable_prefix='a')
2209
+ sage: A1.initial_cluster_variable_names()
2210
+ ('a0', 'a1')
2211
+ """
2212
+ return self.variable_names()[:self.rank()]
2213
+
2214
+ def seeds(self, **kwargs):
2215
+ r"""
2216
+ Return an iterator running over seeds of ``self``.
2217
+
2218
+ INPUT:
2219
+
2220
+ - ``from_current_seed`` -- boolean (default: ``False``); whether to start
2221
+ the iterator from :meth:`current_seed` or :meth:`initial_seed`
2222
+
2223
+ - ``mutating_F`` -- boolean (default: ``True``); whether to compute
2224
+ F-polynomials also; disable this for speed considerations
2225
+
2226
+ - ``allowed_directions`` -- iterable of integers
2227
+ (default: ``range(self.rank())``); the directions in which to mutate
2228
+
2229
+ - ``depth`` -- positive integer or infinity (default: ``infinity``);
2230
+ the maximum depth at which to stop searching
2231
+
2232
+ - ``catch_KeyboardInterrupt`` -- boolean (default: ``False``); whether to
2233
+ catch ``KeyboardInterrupt`` and return it rather then raising an
2234
+ exception -- this allows the iterator returned by this method to be
2235
+ resumed after being interrupted
2236
+
2237
+ ALGORITHM:
2238
+
2239
+ This function traverses the exchange graph in a breadth-first search.
2240
+
2241
+ EXAMPLES::
2242
+
2243
+ sage: A = ClusterAlgebra(['A', 4])
2244
+ sage: A.clear_computed_data()
2245
+ sage: seeds = A.seeds(allowed_directions=[3, 0, 1])
2246
+ sage: _ = list(seeds)
2247
+ sage: sorted(A.g_vectors_so_far())
2248
+ [(-1, 0, 0, 0),
2249
+ (-1, 1, 0, 0),
2250
+ (0, -1, 0, 0),
2251
+ (0, 0, 0, -1),
2252
+ (0, 0, 0, 1),
2253
+ (0, 0, 1, 0),
2254
+ (0, 1, 0, 0),
2255
+ (1, 0, 0, 0)]
2256
+ """
2257
+ # should we begin from the current seed?
2258
+ if kwargs.get('from_current_seed', False):
2259
+ seed = copy(self.current_seed())
2260
+ else:
2261
+ seed = self.initial_seed()
2262
+
2263
+ # yield first seed
2264
+ yield seed
2265
+
2266
+ # keep track of depth
2267
+ depth_counter = 0
2268
+
2269
+ # do we mutate F-polynomials?
2270
+ mutating_F = kwargs.get('mutating_F', True)
2271
+
2272
+ # which directions are we allowed to mutate into
2273
+ allowed_dirs = sorted(kwargs.get('allowed_directions',
2274
+ range(self.rank())))
2275
+
2276
+ # setup seeds storage
2277
+ cl = frozenset(seed.g_vectors())
2278
+ clusters = {}
2279
+ clusters[cl] = [seed, copy(allowed_dirs)]
2280
+
2281
+ # ready, set, go!
2282
+ gets_bigger = True
2283
+ while gets_bigger and depth_counter < kwargs.get('depth', infinity):
2284
+ # remember if we got a new seed
2285
+ gets_bigger = False
2286
+
2287
+ for cl in list(clusters):
2288
+ sd, directions = clusters[cl]
2289
+ while directions:
2290
+ try:
2291
+ # we can mutate in some direction
2292
+ i = directions.pop()
2293
+ new_sd = sd.mutate(i, inplace=False, mutating_F=mutating_F)
2294
+ new_cl = frozenset(new_sd.g_vectors())
2295
+ if new_cl in clusters:
2296
+ # we already had new_sd, make sure it does not mutate to sd during next round
2297
+ j = clusters[new_cl][0].g_vectors().index(new_sd.g_vector(i))
2298
+ try:
2299
+ clusters[new_cl][1].remove(j)
2300
+ except ValueError:
2301
+ pass
2302
+ else:
2303
+ # we got a new seed
2304
+ gets_bigger = True
2305
+ # next round do not mutate back to sd and make sure we only walk three sides of squares
2306
+ new_directions = [j for j in allowed_dirs if j > i or new_sd.b_matrix()[j, i] != 0]
2307
+ clusters[new_cl] = [new_sd, new_directions]
2308
+ yield new_sd
2309
+ except KeyboardInterrupt as e:
2310
+ if kwargs.get('catch_KeyboardInterrupt', False):
2311
+ print("caught a KeyboardInterrupt; cleaning up before returning")
2312
+ # mutation in direction i was not completed; put it back in for next round
2313
+ directions.append(i)
2314
+ yield e
2315
+ continue
2316
+ else:
2317
+ raise e
2318
+ # we went one step deeper
2319
+ depth_counter += 1
2320
+
2321
+ def reset_exploring_iterator(self, mutating_F=True):
2322
+ r"""
2323
+ Reset the iterator used to explore ``self``.
2324
+
2325
+ INPUT:
2326
+
2327
+ - ``mutating_F`` -- boolean (default: ``True``); whether to also compute
2328
+ F-polynomials; disable this for speed considerations
2329
+
2330
+ EXAMPLES::
2331
+
2332
+ sage: A = ClusterAlgebra(['A', 4])
2333
+ sage: A.clear_computed_data()
2334
+ sage: A.reset_exploring_iterator(mutating_F=False)
2335
+ sage: A.explore_to_depth(infinity)
2336
+ sage: len(A.g_vectors_so_far())
2337
+ 14
2338
+ sage: len(A.F_polynomials_so_far())
2339
+ 4
2340
+ """
2341
+ self._sd_iter = self.seeds(mutating_F=mutating_F, catch_KeyboardInterrupt=True)
2342
+ self._explored_depth = 0
2343
+
2344
+ def explore_to_depth(self, depth):
2345
+ r"""
2346
+ Explore the exchange graph of ``self`` up to distance ``depth``
2347
+ from the initial seed.
2348
+
2349
+ INPUT:
2350
+
2351
+ - ``depth`` -- positive integer or infinity; the maximum depth
2352
+ at which to stop searching
2353
+
2354
+ EXAMPLES::
2355
+
2356
+ sage: A = ClusterAlgebra(['A', 4])
2357
+ sage: A.explore_to_depth(infinity)
2358
+ sage: len(A.g_vectors_so_far())
2359
+ 14
2360
+ """
2361
+ while self._explored_depth <= depth:
2362
+ try:
2363
+ seed = next(self._sd_iter)
2364
+ if isinstance(seed, ClusterAlgebraSeed):
2365
+ self._explored_depth = seed.depth()
2366
+ else:
2367
+ # We got an exception because self._sd_iter caught a KeyboardInterrupt, let's raise it again
2368
+ raise seed
2369
+ except StopIteration:
2370
+ break
2371
+
2372
+ def cluster_fan(self, depth=infinity):
2373
+ r"""
2374
+ Return the cluster fan (the fan of g-vectors) of ``self``.
2375
+
2376
+ INPUT:
2377
+
2378
+ - ``depth`` -- positive integer or infinity (default: ``infinity``);
2379
+ the maximum depth at which to compute
2380
+
2381
+ EXAMPLES::
2382
+
2383
+ sage: A = ClusterAlgebra(['A', 2])
2384
+ sage: A.cluster_fan() # needs sage.geometry.polyhedron
2385
+ Rational polyhedral fan in 2-d lattice N
2386
+ """
2387
+ seeds = self.seeds(depth=depth, mutating_F=False)
2388
+ cones = [Cone(S.g_vectors()) for S in seeds]
2389
+ return Fan(cones)
2390
+
2391
+ def mutate_initial(self, direction, **kwargs):
2392
+ r"""
2393
+ Return the cluster algebra obtained by mutating ``self`` at
2394
+ the initial seed.
2395
+
2396
+ .. WARNING::
2397
+
2398
+ This method is significantly slower than :meth:`ClusterAlgebraSeed.mutate`.
2399
+ It is therefore advisable to use the latter for exploration purposes.
2400
+
2401
+ INPUT:
2402
+
2403
+ - ``direction`` -- in which direction(s) to mutate, it can be:
2404
+
2405
+ * an integer in ``range(self.rank())`` to mutate in one direction only
2406
+ * an iterable of such integers to mutate along a sequence
2407
+ * a string "sinks" or "sources" to mutate at all sinks or sources simultaneously
2408
+
2409
+ - ``mutating_F`` -- boolean (default: ``True``); whether to compute
2410
+ F-polynomials while mutating
2411
+
2412
+ .. NOTE::
2413
+
2414
+ While knowing F-polynomials is essential to computing
2415
+ cluster variables, the process of mutating them is quite slow.
2416
+ If you care only about combinatorial data like g-vectors and
2417
+ c-vectors, setting ``mutating_F=False`` yields significant
2418
+ benefits in terms of speed.
2419
+
2420
+ ALGORITHM:
2421
+
2422
+ This function computes data for the new algebra from known data for
2423
+ the old algebra using Equation (4.2) from [NZ2012]_ for g-vectors, and
2424
+ Equation (6.21) from [FZ2007]_ for F-polynomials. The exponent `h`
2425
+ in the formula for F-polynomials is ``-min(0, old_g_vect[k])``
2426
+ due to [NZ2012]_ Proposition 4.2.
2427
+
2428
+ EXAMPLES::
2429
+
2430
+ sage: A = ClusterAlgebra(['F', 4])
2431
+ sage: A.explore_to_depth(infinity)
2432
+ sage: B = A.b_matrix()
2433
+ sage: B.mutate(0)
2434
+ sage: A1 = ClusterAlgebra(B)
2435
+ sage: A1.explore_to_depth(infinity)
2436
+ sage: A2 = A1.mutate_initial(0)
2437
+ sage: A2._F_poly_dict == A._F_poly_dict
2438
+ True
2439
+
2440
+ Check that we did not mess up the original algebra because of :class:`UniqueRepresentation`::
2441
+
2442
+ sage: A = ClusterAlgebra(['A',2])
2443
+ sage: A.mutate_initial(0) is A
2444
+ False
2445
+
2446
+ A faster example without recomputing F-polynomials::
2447
+
2448
+ sage: A = ClusterAlgebra(matrix([[0,5],[-5,0]]))
2449
+ sage: A.mutate_initial([0,1]*10, mutating_F=False)
2450
+ A Cluster Algebra with cluster variables x20, x21 and no coefficients over Integer Ring
2451
+
2452
+ Check that :issue:`28176` is fixed::
2453
+
2454
+ sage: A = ClusterAlgebra( matrix(5,[0,1,-1,1,-1]), cluster_variable_names=['p13'], coefficient_names=['p12','p23','p34','p41']); A
2455
+ A Cluster Algebra with cluster variable p13 and coefficients p12, p23, p34, p41 over Integer Ring
2456
+ sage: A.mutate_initial(0)
2457
+ A Cluster Algebra with cluster variable x0 and coefficients p12, p23, p34, p41 over Integer Ring
2458
+
2459
+ sage: A1 = ClusterAlgebra(['A',[2,1],1])
2460
+ sage: A2 = A1.mutate_initial([0,1,0])
2461
+ sage: len(A2.g_vectors_so_far()) == len(A2.F_polynomials_so_far())
2462
+ True
2463
+ sage: all(parent(f) == A2._U for f in A2.F_polynomials_so_far())
2464
+ True
2465
+ sage: A2.find_g_vector((0,0,1)) == []
2466
+ True
2467
+ """
2468
+ n = self.rank()
2469
+
2470
+ # construct mutation sequence
2471
+ # if you change this be considerate and change also :class:`ClusterAlgebraSeed`.mutate
2472
+ if direction == "sinks":
2473
+ B = self.b_matrix()
2474
+ seq = [i for i in range(n) if all(x <= 0 for x in B.column(i))]
2475
+ elif direction == "sources":
2476
+ B = self.b_matrix()
2477
+ seq = [i for i in range(n) if all(x >= 0 for x in B.column(i))]
2478
+ else:
2479
+ try:
2480
+ seq = iter(direction)
2481
+ except TypeError:
2482
+ seq = iter((direction, ))
2483
+
2484
+ # setup
2485
+ path_dict = copy(self._path_dict)
2486
+ path_to_current = copy(self.current_seed().path_from_initial_seed())
2487
+ B0 = copy(self._B0)
2488
+ cv_names = list(self.initial_cluster_variable_names())
2489
+ nfi = self._next_free_index
2490
+ I = identity_matrix(n)
2491
+ initial_g_vectors = frozenset(map(tuple, I.columns()))
2492
+
2493
+ # go
2494
+ for k in seq:
2495
+ if k not in range(n):
2496
+ raise ValueError('cannot mutate in direction ' + str(k))
2497
+
2498
+ # clear storage
2499
+ tmp_path_dict = {}
2500
+
2501
+ # mutate B-matrix
2502
+ B0.mutate(k)
2503
+
2504
+ for old_g_vect in path_dict:
2505
+ # compute new g-vector
2506
+ J = copy(I)
2507
+ old_g = vector(ZZ, old_g_vect)
2508
+ minus_eps = -old_g[k].sign()
2509
+ for j in range(n):
2510
+ # here we have -eps*B0 rather than eps*B0
2511
+ # because we want the k-th column of the old B0
2512
+ J[j, k] += max(0, minus_eps * B0[j, k])
2513
+ J[k, k] = -1
2514
+ new_g_vect = tuple(J * old_g)
2515
+
2516
+ # compute new path
2517
+ if new_g_vect in initial_g_vectors:
2518
+ tmp_path_dict[new_g_vect] = []
2519
+ else:
2520
+ new_path = path_dict[old_g_vect]
2521
+ new_path = ([k] + new_path[:1] if new_path[:1] != [k] else []) + new_path[1:]
2522
+ tmp_path_dict[new_g_vect] = new_path
2523
+
2524
+ # update storage
2525
+ initial_g = (0,) * (k) + (1,) + (0,) * (n - k - 1)
2526
+ tmp_path_dict[initial_g] = []
2527
+ path_dict = tmp_path_dict
2528
+ path_to_current = ([k] + path_to_current[:1] if path_to_current[:1] != [k] else []) + path_to_current[1:]
2529
+
2530
+ # name the new cluster variable
2531
+ cv_names[k] = self._cluster_variable_prefix + str(nfi)
2532
+ nfi += 1
2533
+
2534
+ # create new algebra
2535
+ coeff_names = self.coefficient_names()
2536
+ scalars = self.scalars()
2537
+ A = ClusterAlgebra(B0, cluster_variable_names=cv_names,
2538
+ next_free_index=nfi,
2539
+ coefficient_names=coeff_names, scalars=scalars)
2540
+
2541
+ # store computed data
2542
+ A._path_dict.update(path_dict)
2543
+
2544
+ # reset self.current_seed() to the previous location
2545
+ S = A.initial_seed()
2546
+ S.mutate(path_to_current, mutating_F=False)
2547
+ A.set_current_seed(S)
2548
+
2549
+ # recompute F-polynomials
2550
+ # We use forward mutation of F-polynomials because it is much faster
2551
+ # than backward mutation. Moreover the number of needed mutation is
2552
+ # linear in len(seq) rather than quadratic.
2553
+ if kwargs.get('mutating_F', True):
2554
+ for p in A._path_dict.values():
2555
+ A.initial_seed().mutate(p)
2556
+
2557
+ return A
2558
+
2559
+ def greedy_element(self, d_vector):
2560
+ r"""
2561
+ Return the greedy element with denominator vector ``d_vector``.
2562
+
2563
+ INPUT:
2564
+
2565
+ - ``d_vector`` -- tuple of 2 integers; the denominator vector of
2566
+ the element to compute
2567
+
2568
+ ALGORITHM:
2569
+
2570
+ This implements greedy elements of a rank 2 cluster algebra using
2571
+ Equation (1.5) from [LLZ2014]_.
2572
+
2573
+ EXAMPLES::
2574
+
2575
+ sage: A = ClusterAlgebra(['A', [1, 1], 1])
2576
+ sage: A.greedy_element((1, 1))
2577
+ (x0^2 + x1^2 + 1)/(x0*x1)
2578
+ """
2579
+ if self.rank() != 2:
2580
+ raise ValueError('greedy elements are only defined in rank 2')
2581
+
2582
+ return self.theta_basis_element(self.d_vector_to_g_vector(d_vector))
2583
+
2584
+ @cached_method(key=lambda a, b: tuple(b))
2585
+ def theta_basis_element(self, g_vector):
2586
+ r"""
2587
+ Return the element of the theta basis of ``self`` with g-vector ``g_vector``.
2588
+
2589
+ INPUT:
2590
+
2591
+ - ``g_vector`` -- tuple; the g-vector of the element to compute
2592
+
2593
+ EXAMPLES::
2594
+
2595
+ sage: A = ClusterAlgebra(matrix([[0,-3],[2,0]]), principal_coefficients=True)
2596
+ sage: A.theta_basis_element((-1,-1))
2597
+ (x1^8*y0^4*y1 + 4*x1^6*y0^3*y1 + 6*x1^4*y0^2*y1 + x0^3*x1^2*y0 + 4*x1^2*y0*y1 + x0^3 + y1)/(x0^4*x1)
2598
+
2599
+ sage: A = ClusterAlgebra(['F', 4])
2600
+ sage: A.theta_basis_element((1, 0, 0, 0))
2601
+ Traceback (most recent call last):
2602
+ ...
2603
+ NotImplementedError: currently only implemented for cluster algebras of rank 2
2604
+
2605
+ .. NOTE::
2606
+
2607
+ Elements of the theta basis correspond with the associated cluster
2608
+ monomial only for appropriate coefficient choices. For example::
2609
+
2610
+ sage: A = ClusterAlgebra(matrix([[0,-1],[1,0],[-1,0]]))
2611
+ sage: A.theta_basis_element((-1,0))
2612
+ (x1 + y0)/(x0*y0)
2613
+
2614
+ while::
2615
+
2616
+ sage: _ = A.find_g_vector((-1,0));
2617
+ sage: A.cluster_variable((-1,0))
2618
+ (x1 + y0)/x0
2619
+
2620
+ In particular theta basis elements do not satisfy a separation of additions formula.
2621
+
2622
+ .. WARNING::
2623
+
2624
+ Currently only cluster algebras of rank 2 are supported
2625
+
2626
+ .. SEEALSO::
2627
+
2628
+ :meth:`sage.algebras.cluster_algebra.theta_basis_F_polynomial`
2629
+ """
2630
+ g_vector = tuple(g_vector)
2631
+ F = self.theta_basis_F_polynomial(g_vector).subs(self._yhat)
2632
+ g_mon = prod(self.ambient().gen(i) ** g_vector[i] for i in range(self.rank()))
2633
+ # we only return the monomal g_mon times the evaluated F-polynomial because this is how
2634
+ # theta basis elements behave.
2635
+ return self.retract(g_mon * F)
2636
+
2637
+ @cached_method(key=lambda a, b: tuple(b))
2638
+ def theta_basis_F_polynomial(self, g_vector):
2639
+ r"""
2640
+ Return the F-polynomial of the element of the theta basis of ``self`` with g-vector ``g_vector``.
2641
+
2642
+ INPUT:
2643
+
2644
+ - ``g_vector`` -- tuple; the g-vector of the F-polynomial to compute
2645
+
2646
+ .. WARNING::
2647
+
2648
+ Elements of the theta basis do not satisfy a separation of additions formula.
2649
+ See the implementation of :meth:`sage.algebras.cluster_algebra.theta_basis_F_polynomial`
2650
+ for further details.
2651
+
2652
+ ALGORITHM:
2653
+
2654
+ This method uses the fact that the greedy basis and the theta basis
2655
+ coincide in rank 2 and uses the former defining recursion (Equation
2656
+ (1.5) from [LLZ2014]_) to compute.
2657
+
2658
+ EXAMPLES::
2659
+
2660
+ sage: A = ClusterAlgebra(matrix([[0,-3],[2,0]]), principal_coefficients=True)
2661
+ sage: A.theta_basis_F_polynomial((-1,-1))
2662
+ u0^4*u1 + 4*u0^3*u1 + 6*u0^2*u1 + 4*u0*u1 + u0 + u1 + 1
2663
+
2664
+ sage: A = ClusterAlgebra(['F', 4])
2665
+ sage: A.theta_basis_F_polynomial((1, 0, 0, 0))
2666
+ Traceback (most recent call last):
2667
+ ...
2668
+ NotImplementedError: currently only implemented for cluster algebras of rank 2
2669
+ """
2670
+ if self.rank() != 2:
2671
+ raise NotImplementedError("currently only implemented for cluster algebras of rank 2")
2672
+
2673
+ # extract the part of g_vector not coming from the initial cluster
2674
+ d = tuple(max(x, 0) for x in self.g_vector_to_d_vector(g_vector))
2675
+ g = self.d_vector_to_g_vector(d)
2676
+
2677
+ shifts = ((d[0] + g[0]) / self._B0[0, 1], (d[1] + g[1]) / self._B0[1, 0])
2678
+ signs = (self._B0[0, 1].sign(), self._B0[1, 0].sign())
2679
+
2680
+ u = list(self._U.gens())
2681
+ output = self._U.zero()
2682
+ for p in range(d[1] + 1):
2683
+ for q in range(d[0] + 1):
2684
+ output += self._greedy_coefficient(d, p, q) * u[1] ** (signs[0] * p - shifts[0]) * u[0] ** (signs[1] * q - shifts[1])
2685
+ return output
2686
+
2687
+ @cached_method
2688
+ def _greedy_coefficient(self, d_vector, p, q):
2689
+ r"""
2690
+ Return the coefficient of the monomial ``x1 ** (b * p) * x2 ** (c * q)``
2691
+ in the numerator of the greedy element with denominator vector ``d_vector``.
2692
+
2693
+ EXAMPLES::
2694
+
2695
+ sage: A = ClusterAlgebra(['A', [1, 1], 1])
2696
+ sage: A.greedy_element((1, 1))
2697
+ (x0^2 + x1^2 + 1)/(x0*x1)
2698
+ sage: A._greedy_coefficient((1, 1), 0, 0)
2699
+ 1
2700
+ sage: A._greedy_coefficient((1, 1), 1, 0)
2701
+ 1
2702
+ """
2703
+ b = abs(self._B0[0, 1])
2704
+ c = abs(self._B0[1, 0])
2705
+ a1, a2 = d_vector
2706
+ p = Integer(p)
2707
+ q = Integer(q)
2708
+ if p == 0 and q == 0:
2709
+ return ZZ.one()
2710
+ sum1 = 0
2711
+ for k in range(1, p + 1):
2712
+ bino = 0
2713
+ if a2 - c * q + k - 1 >= k:
2714
+ bino = binomial(a2 - c * q + k - 1, k)
2715
+ sum1 += (-1) ** (k - 1) * self._greedy_coefficient(d_vector, p - k, q) * bino
2716
+ sum2 = 0
2717
+ for l in range(1, q + 1):
2718
+ bino = 0
2719
+ if a1 - b * p + l - 1 >= l:
2720
+ bino = binomial(a1 - b * p + l - 1, l)
2721
+ sum2 += (-1) ** (l - 1) * self._greedy_coefficient(d_vector, p, q - l) * bino
2722
+ return Integer(max(sum1, sum2))
2723
+
2724
+ # DESIDERATA
2725
+ # Some of these are probably unrealistic
2726
+ def upper_cluster_algebra(self):
2727
+ r"""
2728
+ Return the upper cluster algebra associated to ``self``.
2729
+
2730
+ EXAMPLES::
2731
+
2732
+ sage: A = ClusterAlgebra(['F', 4])
2733
+ sage: A.upper_cluster_algebra()
2734
+ Traceback (most recent call last):
2735
+ ...
2736
+ NotImplementedError: not implemented yet
2737
+ """
2738
+ raise NotImplementedError("not implemented yet")
2739
+
2740
+ def upper_bound(self):
2741
+ r"""
2742
+ Return the upper bound associated to ``self``.
2743
+
2744
+ EXAMPLES::
2745
+
2746
+ sage: A = ClusterAlgebra(['F', 4])
2747
+ sage: A.upper_bound()
2748
+ Traceback (most recent call last):
2749
+ ...
2750
+ NotImplementedError: not implemented yet
2751
+ """
2752
+ raise NotImplementedError("not implemented yet")
2753
+
2754
+ def lower_bound(self):
2755
+ r"""
2756
+ Return the lower bound associated to ``self``.
2757
+
2758
+ EXAMPLES::
2759
+
2760
+ sage: A = ClusterAlgebra(['F', 4])
2761
+ sage: A.lower_bound()
2762
+ Traceback (most recent call last):
2763
+ ...
2764
+ NotImplementedError: not implemented yet
2765
+ """
2766
+ raise NotImplementedError("not implemented yet")