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.
- passagemath_combinat/__init__.py +3 -0
- passagemath_combinat-10.6.42.dist-info/METADATA +160 -0
- passagemath_combinat-10.6.42.dist-info/RECORD +400 -0
- passagemath_combinat-10.6.42.dist-info/WHEEL +5 -0
- passagemath_combinat-10.6.42.dist-info/top_level.txt +3 -0
- passagemath_combinat.libs/libgmp-0e7fc84e.so.10.5.0 +0 -0
- passagemath_combinat.libs/libsymmetrica-81fe8739.so.3.0.0 +0 -0
- sage/algebras/affine_nil_temperley_lieb.py +263 -0
- sage/algebras/all.py +24 -0
- sage/algebras/all__sagemath_combinat.py +35 -0
- sage/algebras/askey_wilson.py +935 -0
- sage/algebras/associated_graded.py +345 -0
- sage/algebras/cellular_basis.py +350 -0
- sage/algebras/cluster_algebra.py +2766 -0
- sage/algebras/down_up_algebra.py +860 -0
- sage/algebras/free_algebra.py +1698 -0
- sage/algebras/free_algebra_element.py +345 -0
- sage/algebras/free_algebra_quotient.py +405 -0
- sage/algebras/free_algebra_quotient_element.py +295 -0
- sage/algebras/free_zinbiel_algebra.py +885 -0
- sage/algebras/hall_algebra.py +783 -0
- sage/algebras/hecke_algebras/all.py +4 -0
- sage/algebras/hecke_algebras/ariki_koike_algebra.py +1796 -0
- sage/algebras/hecke_algebras/ariki_koike_specht_modules.py +475 -0
- sage/algebras/hecke_algebras/cubic_hecke_algebra.py +3520 -0
- sage/algebras/hecke_algebras/cubic_hecke_base_ring.py +1473 -0
- sage/algebras/hecke_algebras/cubic_hecke_matrix_rep.py +1079 -0
- sage/algebras/iwahori_hecke_algebra.py +3095 -0
- sage/algebras/jordan_algebra.py +1773 -0
- sage/algebras/lie_conformal_algebras/abelian_lie_conformal_algebra.py +113 -0
- sage/algebras/lie_conformal_algebras/affine_lie_conformal_algebra.py +156 -0
- sage/algebras/lie_conformal_algebras/all.py +18 -0
- sage/algebras/lie_conformal_algebras/bosonic_ghosts_lie_conformal_algebra.py +134 -0
- sage/algebras/lie_conformal_algebras/examples.py +43 -0
- sage/algebras/lie_conformal_algebras/fermionic_ghosts_lie_conformal_algebra.py +131 -0
- sage/algebras/lie_conformal_algebras/finitely_freely_generated_lca.py +139 -0
- sage/algebras/lie_conformal_algebras/free_bosons_lie_conformal_algebra.py +174 -0
- sage/algebras/lie_conformal_algebras/free_fermions_lie_conformal_algebra.py +167 -0
- sage/algebras/lie_conformal_algebras/freely_generated_lie_conformal_algebra.py +107 -0
- sage/algebras/lie_conformal_algebras/graded_lie_conformal_algebra.py +135 -0
- sage/algebras/lie_conformal_algebras/lie_conformal_algebra.py +353 -0
- sage/algebras/lie_conformal_algebras/lie_conformal_algebra_element.py +236 -0
- sage/algebras/lie_conformal_algebras/lie_conformal_algebra_with_basis.py +78 -0
- sage/algebras/lie_conformal_algebras/lie_conformal_algebra_with_structure_coefs.py +328 -0
- sage/algebras/lie_conformal_algebras/n2_lie_conformal_algebra.py +117 -0
- sage/algebras/lie_conformal_algebras/neveu_schwarz_lie_conformal_algebra.py +86 -0
- sage/algebras/lie_conformal_algebras/virasoro_lie_conformal_algebra.py +82 -0
- sage/algebras/lie_conformal_algebras/weyl_lie_conformal_algebra.py +205 -0
- sage/algebras/nil_coxeter_algebra.py +191 -0
- sage/algebras/q_commuting_polynomials.py +673 -0
- sage/algebras/q_system.py +608 -0
- sage/algebras/quantum_clifford.py +959 -0
- sage/algebras/quantum_groups/ace_quantum_onsager.py +693 -0
- sage/algebras/quantum_groups/all.py +9 -0
- sage/algebras/quantum_groups/fock_space.py +2219 -0
- sage/algebras/quantum_groups/q_numbers.py +207 -0
- sage/algebras/quantum_groups/quantum_group_gap.py +2695 -0
- sage/algebras/quantum_groups/representations.py +591 -0
- sage/algebras/quantum_matrix_coordinate_algebra.py +1006 -0
- sage/algebras/quantum_oscillator.py +623 -0
- sage/algebras/quaternion_algebra.py +20 -0
- sage/algebras/quaternion_algebra_element.py +55 -0
- sage/algebras/rational_cherednik_algebra.py +525 -0
- sage/algebras/schur_algebra.py +670 -0
- sage/algebras/shuffle_algebra.py +1011 -0
- sage/algebras/splitting_algebra.py +779 -0
- sage/algebras/tensor_algebra.py +709 -0
- sage/algebras/yangian.py +1082 -0
- sage/algebras/yokonuma_hecke_algebra.py +1018 -0
- sage/all__sagemath_combinat.py +35 -0
- sage/combinat/SJT.py +255 -0
- sage/combinat/affine_permutation.py +2405 -0
- sage/combinat/algebraic_combinatorics.py +55 -0
- sage/combinat/all.py +53 -0
- sage/combinat/all__sagemath_combinat.py +195 -0
- sage/combinat/alternating_sign_matrix.py +2063 -0
- sage/combinat/baxter_permutations.py +346 -0
- sage/combinat/bijectionist.py +3220 -0
- sage/combinat/binary_recurrence_sequences.py +1180 -0
- sage/combinat/blob_algebra.py +685 -0
- sage/combinat/catalog_partitions.py +27 -0
- sage/combinat/chas/all.py +23 -0
- sage/combinat/chas/fsym.py +1180 -0
- sage/combinat/chas/wqsym.py +2601 -0
- sage/combinat/cluster_complex.py +326 -0
- sage/combinat/colored_permutations.py +2039 -0
- sage/combinat/colored_permutations_representations.py +964 -0
- sage/combinat/composition_signed.py +142 -0
- sage/combinat/composition_tableau.py +855 -0
- sage/combinat/constellation.py +1729 -0
- sage/combinat/core.py +751 -0
- sage/combinat/counting.py +12 -0
- sage/combinat/crystals/affine.py +742 -0
- sage/combinat/crystals/affine_factorization.py +518 -0
- sage/combinat/crystals/affinization.py +331 -0
- sage/combinat/crystals/alcove_path.py +2013 -0
- sage/combinat/crystals/all.py +22 -0
- sage/combinat/crystals/bkk_crystals.py +141 -0
- sage/combinat/crystals/catalog.py +115 -0
- sage/combinat/crystals/catalog_elementary_crystals.py +18 -0
- sage/combinat/crystals/catalog_infinity_crystals.py +33 -0
- sage/combinat/crystals/catalog_kirillov_reshetikhin.py +18 -0
- sage/combinat/crystals/crystals.py +257 -0
- sage/combinat/crystals/direct_sum.py +260 -0
- sage/combinat/crystals/elementary_crystals.py +1251 -0
- sage/combinat/crystals/fast_crystals.py +441 -0
- sage/combinat/crystals/fully_commutative_stable_grothendieck.py +1205 -0
- sage/combinat/crystals/generalized_young_walls.py +1076 -0
- sage/combinat/crystals/highest_weight_crystals.py +436 -0
- sage/combinat/crystals/induced_structure.py +695 -0
- sage/combinat/crystals/infinity_crystals.py +730 -0
- sage/combinat/crystals/kac_modules.py +863 -0
- sage/combinat/crystals/kirillov_reshetikhin.py +4196 -0
- sage/combinat/crystals/kyoto_path_model.py +497 -0
- sage/combinat/crystals/letters.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/crystals/letters.pxd +79 -0
- sage/combinat/crystals/letters.pyx +3056 -0
- sage/combinat/crystals/littelmann_path.py +1518 -0
- sage/combinat/crystals/monomial_crystals.py +1262 -0
- sage/combinat/crystals/multisegments.py +462 -0
- sage/combinat/crystals/mv_polytopes.py +467 -0
- sage/combinat/crystals/pbw_crystal.py +511 -0
- sage/combinat/crystals/pbw_datum.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/crystals/pbw_datum.pxd +4 -0
- sage/combinat/crystals/pbw_datum.pyx +487 -0
- sage/combinat/crystals/polyhedral_realization.py +372 -0
- sage/combinat/crystals/spins.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/crystals/spins.pxd +21 -0
- sage/combinat/crystals/spins.pyx +756 -0
- sage/combinat/crystals/star_crystal.py +290 -0
- sage/combinat/crystals/subcrystal.py +464 -0
- sage/combinat/crystals/tensor_product.py +1177 -0
- sage/combinat/crystals/tensor_product_element.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/crystals/tensor_product_element.pxd +35 -0
- sage/combinat/crystals/tensor_product_element.pyx +1870 -0
- sage/combinat/crystals/virtual_crystal.py +420 -0
- sage/combinat/cyclic_sieving_phenomenon.py +204 -0
- sage/combinat/debruijn_sequence.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/debruijn_sequence.pyx +355 -0
- sage/combinat/decorated_permutation.py +270 -0
- sage/combinat/degree_sequences.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/degree_sequences.pyx +588 -0
- sage/combinat/derangements.py +527 -0
- sage/combinat/descent_algebra.py +1008 -0
- sage/combinat/diagram.py +1551 -0
- sage/combinat/diagram_algebras.py +5886 -0
- sage/combinat/dyck_word.py +4349 -0
- sage/combinat/e_one_star.py +1623 -0
- sage/combinat/enumerated_sets.py +123 -0
- sage/combinat/expnums.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/expnums.pyx +148 -0
- sage/combinat/fast_vector_partitions.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/fast_vector_partitions.pyx +346 -0
- sage/combinat/fqsym.py +1977 -0
- sage/combinat/free_dendriform_algebra.py +954 -0
- sage/combinat/free_prelie_algebra.py +1141 -0
- sage/combinat/fully_commutative_elements.py +1077 -0
- sage/combinat/fully_packed_loop.py +1523 -0
- sage/combinat/gelfand_tsetlin_patterns.py +1409 -0
- sage/combinat/gray_codes.py +311 -0
- sage/combinat/grossman_larson_algebras.py +667 -0
- sage/combinat/growth.py +4352 -0
- sage/combinat/hall_polynomial.py +188 -0
- sage/combinat/hillman_grassl.py +866 -0
- sage/combinat/integer_matrices.py +329 -0
- sage/combinat/integer_vectors_mod_permgroup.py +1238 -0
- sage/combinat/k_tableau.py +4564 -0
- sage/combinat/kazhdan_lusztig.py +215 -0
- sage/combinat/key_polynomial.py +885 -0
- sage/combinat/knutson_tao_puzzles.py +2286 -0
- sage/combinat/lr_tableau.py +311 -0
- sage/combinat/matrices/all.py +24 -0
- sage/combinat/matrices/hadamard_matrix.py +3790 -0
- sage/combinat/matrices/latin.py +2912 -0
- sage/combinat/misc.py +401 -0
- sage/combinat/multiset_partition_into_sets_ordered.py +3541 -0
- sage/combinat/ncsf_qsym/all.py +21 -0
- sage/combinat/ncsf_qsym/combinatorics.py +317 -0
- sage/combinat/ncsf_qsym/generic_basis_code.py +1427 -0
- sage/combinat/ncsf_qsym/ncsf.py +5637 -0
- sage/combinat/ncsf_qsym/qsym.py +4053 -0
- sage/combinat/ncsf_qsym/tutorial.py +447 -0
- sage/combinat/ncsym/all.py +21 -0
- sage/combinat/ncsym/bases.py +855 -0
- sage/combinat/ncsym/dual.py +593 -0
- sage/combinat/ncsym/ncsym.py +2076 -0
- sage/combinat/necklace.py +551 -0
- sage/combinat/non_decreasing_parking_function.py +634 -0
- sage/combinat/nu_dyck_word.py +1474 -0
- sage/combinat/output.py +861 -0
- sage/combinat/parallelogram_polyomino.py +4326 -0
- sage/combinat/parking_functions.py +1602 -0
- sage/combinat/partition_algebra.py +1998 -0
- sage/combinat/partition_kleshchev.py +1982 -0
- sage/combinat/partition_shifting_algebras.py +584 -0
- sage/combinat/partition_tuple.py +3114 -0
- sage/combinat/path_tableaux/all.py +13 -0
- sage/combinat/path_tableaux/catalog.py +29 -0
- sage/combinat/path_tableaux/dyck_path.py +380 -0
- sage/combinat/path_tableaux/frieze.py +476 -0
- sage/combinat/path_tableaux/path_tableau.py +728 -0
- sage/combinat/path_tableaux/semistandard.py +510 -0
- sage/combinat/perfect_matching.py +779 -0
- sage/combinat/plane_partition.py +3300 -0
- sage/combinat/q_bernoulli.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/q_bernoulli.pyx +128 -0
- sage/combinat/quickref.py +81 -0
- sage/combinat/recognizable_series.py +2051 -0
- sage/combinat/regular_sequence.py +4316 -0
- sage/combinat/regular_sequence_bounded.py +543 -0
- sage/combinat/restricted_growth.py +81 -0
- sage/combinat/ribbon.py +20 -0
- sage/combinat/ribbon_shaped_tableau.py +489 -0
- sage/combinat/ribbon_tableau.py +1180 -0
- sage/combinat/rigged_configurations/all.py +46 -0
- sage/combinat/rigged_configurations/bij_abstract_class.py +548 -0
- sage/combinat/rigged_configurations/bij_infinity.py +370 -0
- sage/combinat/rigged_configurations/bij_type_A.py +163 -0
- sage/combinat/rigged_configurations/bij_type_A2_dual.py +338 -0
- sage/combinat/rigged_configurations/bij_type_A2_even.py +218 -0
- sage/combinat/rigged_configurations/bij_type_A2_odd.py +199 -0
- sage/combinat/rigged_configurations/bij_type_B.py +900 -0
- sage/combinat/rigged_configurations/bij_type_C.py +267 -0
- sage/combinat/rigged_configurations/bij_type_D.py +771 -0
- sage/combinat/rigged_configurations/bij_type_D_tri.py +392 -0
- sage/combinat/rigged_configurations/bij_type_D_twisted.py +576 -0
- sage/combinat/rigged_configurations/bij_type_E67.py +402 -0
- sage/combinat/rigged_configurations/bijection.py +143 -0
- sage/combinat/rigged_configurations/kleber_tree.py +1475 -0
- sage/combinat/rigged_configurations/kr_tableaux.py +1898 -0
- sage/combinat/rigged_configurations/rc_crystal.py +461 -0
- sage/combinat/rigged_configurations/rc_infinity.py +540 -0
- sage/combinat/rigged_configurations/rigged_configuration_element.py +2403 -0
- sage/combinat/rigged_configurations/rigged_configurations.py +1918 -0
- sage/combinat/rigged_configurations/rigged_partition.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/rigged_configurations/rigged_partition.pxd +15 -0
- sage/combinat/rigged_configurations/rigged_partition.pyx +680 -0
- sage/combinat/rigged_configurations/tensor_product_kr_tableaux.py +499 -0
- sage/combinat/rigged_configurations/tensor_product_kr_tableaux_element.py +428 -0
- sage/combinat/rsk.py +3438 -0
- sage/combinat/schubert_polynomial.py +508 -0
- sage/combinat/set_partition.py +3318 -0
- sage/combinat/set_partition_iterator.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/set_partition_iterator.pyx +136 -0
- sage/combinat/set_partition_ordered.py +1590 -0
- sage/combinat/sf/abreu_nigro.py +346 -0
- sage/combinat/sf/all.py +52 -0
- sage/combinat/sf/character.py +576 -0
- sage/combinat/sf/classical.py +319 -0
- sage/combinat/sf/dual.py +996 -0
- sage/combinat/sf/elementary.py +549 -0
- sage/combinat/sf/hall_littlewood.py +1028 -0
- sage/combinat/sf/hecke.py +336 -0
- sage/combinat/sf/homogeneous.py +464 -0
- sage/combinat/sf/jack.py +1428 -0
- sage/combinat/sf/k_dual.py +1458 -0
- sage/combinat/sf/kfpoly.py +447 -0
- sage/combinat/sf/llt.py +789 -0
- sage/combinat/sf/macdonald.py +2019 -0
- sage/combinat/sf/monomial.py +525 -0
- sage/combinat/sf/multiplicative.py +113 -0
- sage/combinat/sf/new_kschur.py +1786 -0
- sage/combinat/sf/ns_macdonald.py +964 -0
- sage/combinat/sf/orthogonal.py +246 -0
- sage/combinat/sf/orthotriang.py +355 -0
- sage/combinat/sf/powersum.py +963 -0
- sage/combinat/sf/schur.py +880 -0
- sage/combinat/sf/sf.py +1653 -0
- sage/combinat/sf/sfa.py +7053 -0
- sage/combinat/sf/symplectic.py +253 -0
- sage/combinat/sf/witt.py +721 -0
- sage/combinat/shifted_primed_tableau.py +2735 -0
- sage/combinat/shuffle.py +830 -0
- sage/combinat/sidon_sets.py +146 -0
- sage/combinat/similarity_class_type.py +1721 -0
- sage/combinat/sine_gordon.py +618 -0
- sage/combinat/six_vertex_model.py +784 -0
- sage/combinat/skew_partition.py +2053 -0
- sage/combinat/skew_tableau.py +2989 -0
- sage/combinat/sloane_functions.py +8935 -0
- sage/combinat/specht_module.py +1403 -0
- sage/combinat/species/all.py +48 -0
- sage/combinat/species/characteristic_species.py +321 -0
- sage/combinat/species/composition_species.py +273 -0
- sage/combinat/species/cycle_species.py +284 -0
- sage/combinat/species/empty_species.py +155 -0
- sage/combinat/species/functorial_composition_species.py +148 -0
- sage/combinat/species/generating_series.py +673 -0
- sage/combinat/species/library.py +148 -0
- sage/combinat/species/linear_order_species.py +169 -0
- sage/combinat/species/misc.py +83 -0
- sage/combinat/species/partition_species.py +290 -0
- sage/combinat/species/permutation_species.py +268 -0
- sage/combinat/species/product_species.py +423 -0
- sage/combinat/species/recursive_species.py +476 -0
- sage/combinat/species/set_species.py +192 -0
- sage/combinat/species/species.py +820 -0
- sage/combinat/species/structure.py +539 -0
- sage/combinat/species/subset_species.py +243 -0
- sage/combinat/species/sum_species.py +225 -0
- sage/combinat/subword.py +564 -0
- sage/combinat/subword_complex.py +2122 -0
- sage/combinat/subword_complex_c.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/subword_complex_c.pyx +119 -0
- sage/combinat/super_tableau.py +821 -0
- sage/combinat/superpartition.py +1154 -0
- sage/combinat/symmetric_group_algebra.py +3774 -0
- sage/combinat/symmetric_group_representations.py +1830 -0
- sage/combinat/t_sequences.py +877 -0
- sage/combinat/tableau.py +9506 -0
- sage/combinat/tableau_residues.py +860 -0
- sage/combinat/tableau_tuple.py +5353 -0
- sage/combinat/tiling.py +2432 -0
- sage/combinat/triangles_FHM.py +777 -0
- sage/combinat/tutorial.py +1857 -0
- sage/combinat/vector_partition.py +337 -0
- sage/combinat/words/abstract_word.py +1722 -0
- sage/combinat/words/all.py +59 -0
- sage/combinat/words/alphabet.py +268 -0
- sage/combinat/words/finite_word.py +7201 -0
- sage/combinat/words/infinite_word.py +113 -0
- sage/combinat/words/lyndon_word.py +652 -0
- sage/combinat/words/morphic.py +351 -0
- sage/combinat/words/morphism.py +3878 -0
- sage/combinat/words/paths.py +2932 -0
- sage/combinat/words/shuffle_product.py +278 -0
- sage/combinat/words/suffix_trees.py +1873 -0
- sage/combinat/words/word.py +769 -0
- sage/combinat/words/word_char.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/words/word_char.pyx +847 -0
- sage/combinat/words/word_datatypes.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/words/word_datatypes.pxd +4 -0
- sage/combinat/words/word_datatypes.pyx +1067 -0
- sage/combinat/words/word_generators.py +2026 -0
- sage/combinat/words/word_infinite_datatypes.py +1218 -0
- sage/combinat/words/word_options.py +99 -0
- sage/combinat/words/words.py +2396 -0
- sage/data_structures/all__sagemath_combinat.py +1 -0
- sage/databases/all__sagemath_combinat.py +13 -0
- sage/databases/findstat.py +4897 -0
- sage/databases/oeis.py +2058 -0
- sage/databases/sloane.py +393 -0
- sage/dynamics/all__sagemath_combinat.py +14 -0
- sage/dynamics/cellular_automata/all.py +7 -0
- sage/dynamics/cellular_automata/catalog.py +34 -0
- sage/dynamics/cellular_automata/elementary.py +612 -0
- sage/dynamics/cellular_automata/glca.py +477 -0
- sage/dynamics/cellular_automata/solitons.py +1463 -0
- sage/dynamics/finite_dynamical_system.py +1249 -0
- sage/dynamics/finite_dynamical_system_catalog.py +382 -0
- sage/games/all.py +7 -0
- sage/games/hexad.py +704 -0
- sage/games/quantumino.py +591 -0
- sage/games/sudoku.py +889 -0
- sage/games/sudoku_backtrack.cpython-314-x86_64-linux-musl.so +0 -0
- sage/games/sudoku_backtrack.pyx +189 -0
- sage/groups/all__sagemath_combinat.py +1 -0
- sage/groups/indexed_free_group.py +489 -0
- sage/libs/all__sagemath_combinat.py +6 -0
- sage/libs/lrcalc/__init__.py +1 -0
- sage/libs/lrcalc/lrcalc.py +525 -0
- sage/libs/symmetrica/__init__.py +7 -0
- sage/libs/symmetrica/all.py +101 -0
- sage/libs/symmetrica/kostka.pxi +168 -0
- sage/libs/symmetrica/part.pxi +193 -0
- sage/libs/symmetrica/plet.pxi +42 -0
- sage/libs/symmetrica/sab.pxi +196 -0
- sage/libs/symmetrica/sb.pxi +332 -0
- sage/libs/symmetrica/sc.pxi +192 -0
- sage/libs/symmetrica/schur.pxi +956 -0
- sage/libs/symmetrica/symmetrica.cpython-314-x86_64-linux-musl.so +0 -0
- sage/libs/symmetrica/symmetrica.pxi +1172 -0
- sage/libs/symmetrica/symmetrica.pyx +39 -0
- sage/monoids/all.py +13 -0
- sage/monoids/automatic_semigroup.py +1054 -0
- sage/monoids/free_abelian_monoid.py +315 -0
- sage/monoids/free_abelian_monoid_element.cpython-314-x86_64-linux-musl.so +0 -0
- sage/monoids/free_abelian_monoid_element.pxd +16 -0
- sage/monoids/free_abelian_monoid_element.pyx +397 -0
- sage/monoids/free_monoid.py +335 -0
- sage/monoids/free_monoid_element.py +431 -0
- sage/monoids/hecke_monoid.py +65 -0
- sage/monoids/string_monoid.py +817 -0
- sage/monoids/string_monoid_element.py +547 -0
- sage/monoids/string_ops.py +143 -0
- sage/monoids/trace_monoid.py +972 -0
- sage/rings/all__sagemath_combinat.py +2 -0
- sage/sat/all.py +4 -0
- sage/sat/boolean_polynomials.py +405 -0
- sage/sat/converters/__init__.py +6 -0
- sage/sat/converters/anf2cnf.py +14 -0
- sage/sat/converters/polybori.py +611 -0
- sage/sat/solvers/__init__.py +5 -0
- sage/sat/solvers/cryptominisat.py +287 -0
- sage/sat/solvers/dimacs.py +783 -0
- sage/sat/solvers/picosat.py +228 -0
- sage/sat/solvers/sat_lp.py +156 -0
- sage/sat/solvers/satsolver.cpython-314-x86_64-linux-musl.so +0 -0
- sage/sat/solvers/satsolver.pxd +3 -0
- 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")
|