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,3878 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-combinat
|
|
2
|
+
r"""
|
|
3
|
+
Word morphisms/substitutions
|
|
4
|
+
|
|
5
|
+
This module implements morphisms over finite and infinite words.
|
|
6
|
+
|
|
7
|
+
AUTHORS:
|
|
8
|
+
|
|
9
|
+
- Sébastien Labbé (2007-06-01): initial version
|
|
10
|
+
- Sébastien Labbé (2008-07-01): merged into sage-words
|
|
11
|
+
- Sébastien Labbé (2008-12-17): merged into sage
|
|
12
|
+
- Sébastien Labbé (2009-02-03): words next generation
|
|
13
|
+
- Sébastien Labbé (2009-11-20): allowing the choice of the
|
|
14
|
+
datatype of the image. Doc improvements.
|
|
15
|
+
- Stepan Starosta (2012-11-09): growing letters
|
|
16
|
+
|
|
17
|
+
EXAMPLES:
|
|
18
|
+
|
|
19
|
+
Creation of a morphism from a dictionary or a string::
|
|
20
|
+
|
|
21
|
+
sage: n = WordMorphism({0:[0,2,2,1],1:[0,2],2:[2,2,1]})
|
|
22
|
+
|
|
23
|
+
::
|
|
24
|
+
|
|
25
|
+
sage: m = WordMorphism('x->xyxsxss,s->xyss,y->ys')
|
|
26
|
+
|
|
27
|
+
::
|
|
28
|
+
|
|
29
|
+
sage: n
|
|
30
|
+
WordMorphism: 0->0221, 1->02, 2->221
|
|
31
|
+
sage: m
|
|
32
|
+
WordMorphism: s->xyss, x->xyxsxss, y->ys
|
|
33
|
+
|
|
34
|
+
The codomain may be specified::
|
|
35
|
+
|
|
36
|
+
sage: WordMorphism({0:[0,2,2,1],1:[0,2],2:[2,2,1]}, codomain=Words([0,1,2,3,4]))
|
|
37
|
+
WordMorphism: 0->0221, 1->02, 2->221
|
|
38
|
+
|
|
39
|
+
Power of a morphism::
|
|
40
|
+
|
|
41
|
+
sage: n^2
|
|
42
|
+
WordMorphism: 0->022122122102, 1->0221221, 2->22122102
|
|
43
|
+
|
|
44
|
+
Image under a morphism::
|
|
45
|
+
|
|
46
|
+
sage: m('y')
|
|
47
|
+
word: ys
|
|
48
|
+
sage: m('xxxsy')
|
|
49
|
+
word: xyxsxssxyxsxssxyxsxssxyssys
|
|
50
|
+
|
|
51
|
+
Iterated image under a morphism::
|
|
52
|
+
|
|
53
|
+
sage: m('y', 3)
|
|
54
|
+
word: ysxyssxyxsxssysxyssxyss
|
|
55
|
+
|
|
56
|
+
See more examples in the documentation of the call method
|
|
57
|
+
(``m.__call__?``).
|
|
58
|
+
|
|
59
|
+
Infinite fixed point of morphism::
|
|
60
|
+
|
|
61
|
+
sage: fix = m.fixed_point('x')
|
|
62
|
+
sage: fix
|
|
63
|
+
word: xyxsxssysxyxsxssxyssxyxsxssxyssxyssysxys...
|
|
64
|
+
sage: fix.length()
|
|
65
|
+
+Infinity
|
|
66
|
+
|
|
67
|
+
Incidence matrix::
|
|
68
|
+
|
|
69
|
+
sage: matrix(m) # needs sage.modules
|
|
70
|
+
[2 3 1]
|
|
71
|
+
[1 3 0]
|
|
72
|
+
[1 1 1]
|
|
73
|
+
|
|
74
|
+
Many other functionalities...::
|
|
75
|
+
|
|
76
|
+
sage: m.is_identity()
|
|
77
|
+
False
|
|
78
|
+
sage: m.is_endomorphism()
|
|
79
|
+
True
|
|
80
|
+
"""
|
|
81
|
+
# ****************************************************************************
|
|
82
|
+
# Copyright (C) 2008 Sebastien Labbe <slabqc@gmail.com>
|
|
83
|
+
# 2018 Vincent Delecroix <20100.delecroix@gmail.com>
|
|
84
|
+
#
|
|
85
|
+
# This program is free software: you can redistribute it and/or modify
|
|
86
|
+
# it under the terms of the GNU General Public License as published by
|
|
87
|
+
# the Free Software Foundation, either version 2 of the License, or
|
|
88
|
+
# (at your option) any later version.
|
|
89
|
+
# https://www.gnu.org/licenses/
|
|
90
|
+
# ****************************************************************************
|
|
91
|
+
|
|
92
|
+
from collections.abc import Iterable
|
|
93
|
+
from sage.misc.callable_dict import CallableDict
|
|
94
|
+
from sage.structure.sage_object import SageObject
|
|
95
|
+
from sage.misc.cachefunc import cached_method
|
|
96
|
+
from sage.misc.lazy_import import lazy_import
|
|
97
|
+
from sage.misc.lazy_list import lazy_list
|
|
98
|
+
from sage.sets.set import Set
|
|
99
|
+
from sage.rings.rational_field import QQ
|
|
100
|
+
from sage.rings.infinity import Infinity
|
|
101
|
+
from sage.rings.integer_ring import IntegerRing
|
|
102
|
+
from sage.rings.integer import Integer
|
|
103
|
+
from sage.combinat.words.word import FiniteWord_class
|
|
104
|
+
from sage.combinat.words.words import FiniteWords, FiniteOrInfiniteWords
|
|
105
|
+
|
|
106
|
+
lazy_import('sage.modules.free_module_element', 'vector')
|
|
107
|
+
lazy_import('sage.matrix.constructor', 'Matrix')
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def get_cycles(f, domain):
|
|
111
|
+
r"""
|
|
112
|
+
Return the list of cycles of the function ``f`` contained in ``domain``.
|
|
113
|
+
|
|
114
|
+
INPUT:
|
|
115
|
+
|
|
116
|
+
- ``f`` -- function
|
|
117
|
+
|
|
118
|
+
- ``domain`` -- iterable, a subdomain of the domain of definition of ``f``
|
|
119
|
+
|
|
120
|
+
EXAMPLES::
|
|
121
|
+
|
|
122
|
+
sage: from sage.combinat.words.morphism import get_cycles
|
|
123
|
+
sage: get_cycles(lambda i: (i+1)%3, [0,1,2])
|
|
124
|
+
[(0, 1, 2)]
|
|
125
|
+
sage: get_cycles(lambda i: [0,0,0][i], [0,1,2])
|
|
126
|
+
[(0,)]
|
|
127
|
+
sage: get_cycles(lambda i: [1,1,1][i], [0,1,2])
|
|
128
|
+
[(1,)]
|
|
129
|
+
sage: get_cycles(lambda i: [2,3,0][i], [0,1,2])
|
|
130
|
+
[(0, 2)]
|
|
131
|
+
sage: d = {'a': 'a', 'b': 'b'}
|
|
132
|
+
sage: get_cycles(d.__getitem__, 'ba')
|
|
133
|
+
[('b',), ('a',)]
|
|
134
|
+
"""
|
|
135
|
+
cycles = []
|
|
136
|
+
not_seen = set(domain)
|
|
137
|
+
for a in domain:
|
|
138
|
+
if a not in not_seen:
|
|
139
|
+
continue
|
|
140
|
+
cycle = [a]
|
|
141
|
+
b = f(a)
|
|
142
|
+
not_seen.remove(a)
|
|
143
|
+
while b in not_seen:
|
|
144
|
+
not_seen.remove(b)
|
|
145
|
+
cycle.append(b)
|
|
146
|
+
b = f(b)
|
|
147
|
+
if b in cycle:
|
|
148
|
+
cycles.append(tuple(cycle[cycle.index(b):]))
|
|
149
|
+
|
|
150
|
+
return cycles
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class PeriodicPointIterator:
|
|
154
|
+
r"""
|
|
155
|
+
(Lazy) constructor of the periodic points of a word morphism.
|
|
156
|
+
|
|
157
|
+
This class is mainly used in :class:`WordMorphism.periodic_point` and
|
|
158
|
+
:class:`WordMorphism.periodic_points`.
|
|
159
|
+
|
|
160
|
+
EXAMPLES::
|
|
161
|
+
|
|
162
|
+
sage: from sage.combinat.words.morphism import PeriodicPointIterator
|
|
163
|
+
sage: s = WordMorphism('a->bacca,b->cba,c->aab')
|
|
164
|
+
sage: p = PeriodicPointIterator(s, ['a','b','c'])
|
|
165
|
+
sage: p._cache[0]
|
|
166
|
+
lazy list ['a', 'a', 'b', ...]
|
|
167
|
+
sage: p._cache[1]
|
|
168
|
+
lazy list ['b', 'a', 'c', ...]
|
|
169
|
+
sage: p._cache[2]
|
|
170
|
+
lazy list ['c', 'b', 'a', ...]
|
|
171
|
+
"""
|
|
172
|
+
def __init__(self, m, cycle):
|
|
173
|
+
r"""
|
|
174
|
+
INPUT:
|
|
175
|
+
|
|
176
|
+
- ``m`` -- a word morphism
|
|
177
|
+
|
|
178
|
+
- ``cycle`` -- a cycle of letters under the morphism
|
|
179
|
+
|
|
180
|
+
TESTS::
|
|
181
|
+
|
|
182
|
+
sage: from sage.combinat.words.morphism import PeriodicPointIterator
|
|
183
|
+
sage: s = WordMorphism('a->bacca,b->cba,c->aab')
|
|
184
|
+
sage: p = PeriodicPointIterator(s, ['a','b','c'])
|
|
185
|
+
sage: pp = loads(dumps(p))
|
|
186
|
+
sage: pp._cache[0]
|
|
187
|
+
lazy list ['a', 'a', 'b', ...]
|
|
188
|
+
"""
|
|
189
|
+
self._m = m # for pickling only
|
|
190
|
+
self._image = m.image
|
|
191
|
+
self._cycle = tuple(cycle)
|
|
192
|
+
self._cache = [lazy_list(self.get_iterator(i)) for i in range(len(cycle))]
|
|
193
|
+
|
|
194
|
+
def __reduce__(self):
|
|
195
|
+
r"""
|
|
196
|
+
TESTS::
|
|
197
|
+
|
|
198
|
+
sage: from sage.combinat.words.morphism import PeriodicPointIterator
|
|
199
|
+
sage: s = WordMorphism('a->bacca,b->cba,c->aab')
|
|
200
|
+
sage: p = PeriodicPointIterator(s, ['a','b','c'])
|
|
201
|
+
sage: p.__reduce__()
|
|
202
|
+
(<class 'sage.combinat.words.morphism.PeriodicPointIterator'>,
|
|
203
|
+
(WordMorphism: a->bacca, b->cba, c->aab, ('a', 'b', 'c')))
|
|
204
|
+
"""
|
|
205
|
+
return PeriodicPointIterator, (self._m, self._cycle)
|
|
206
|
+
|
|
207
|
+
@cached_method
|
|
208
|
+
def get_iterator(self, i):
|
|
209
|
+
r"""
|
|
210
|
+
Internal method.
|
|
211
|
+
|
|
212
|
+
EXAMPLES::
|
|
213
|
+
|
|
214
|
+
sage: from sage.combinat.words.morphism import PeriodicPointIterator
|
|
215
|
+
sage: s = WordMorphism('a->bacca,b->cba,c->aab')
|
|
216
|
+
sage: p = PeriodicPointIterator(s, ['a','b','c'])
|
|
217
|
+
sage: p.get_iterator(0)
|
|
218
|
+
<generator object ...get_iterator at ...>
|
|
219
|
+
"""
|
|
220
|
+
j = (i - 1) % len(self._cycle)
|
|
221
|
+
for a in self._image(self._cycle[j]):
|
|
222
|
+
yield a
|
|
223
|
+
u = iter(self._cache[j])
|
|
224
|
+
next(u)
|
|
225
|
+
while True:
|
|
226
|
+
for a in self._image(next(u)):
|
|
227
|
+
yield a
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class WordMorphism(SageObject):
|
|
231
|
+
r"""
|
|
232
|
+
WordMorphism class.
|
|
233
|
+
|
|
234
|
+
INPUT:
|
|
235
|
+
|
|
236
|
+
- ``data`` -- dictionary or string or an instance of WordMorphism, the map
|
|
237
|
+
giving the image of letters
|
|
238
|
+
- ``domain`` -- (optional:``None``) set of words over a given
|
|
239
|
+
alphabet. If ``None``, the domain alphabet is computed from ``data``
|
|
240
|
+
and is *sorted*.
|
|
241
|
+
- ``codomain`` -- (optional:``None``) set of words over a given
|
|
242
|
+
alphabet. If ``None``, the codomain alphabet is computed from
|
|
243
|
+
``data`` and is *sorted*.
|
|
244
|
+
|
|
245
|
+
.. NOTE::
|
|
246
|
+
|
|
247
|
+
When the domain or the codomain are not explicitly given, it is
|
|
248
|
+
expected that the letters are comparable because the alphabets of
|
|
249
|
+
the domain and of the codomain are sorted.
|
|
250
|
+
|
|
251
|
+
EXAMPLES:
|
|
252
|
+
|
|
253
|
+
From a dictionary::
|
|
254
|
+
|
|
255
|
+
sage: n = WordMorphism({0:[0,2,2,1],1:[0,2],2:[2,2,1]})
|
|
256
|
+
sage: n
|
|
257
|
+
WordMorphism: 0->0221, 1->02, 2->221
|
|
258
|
+
|
|
259
|
+
From a string with ``'->'`` as separation::
|
|
260
|
+
|
|
261
|
+
sage: m = WordMorphism('x->xyxsxss,s->xyss,y->ys')
|
|
262
|
+
sage: m
|
|
263
|
+
WordMorphism: s->xyss, x->xyxsxss, y->ys
|
|
264
|
+
sage: m.domain()
|
|
265
|
+
Finite words over {'s', 'x', 'y'}
|
|
266
|
+
sage: m.codomain()
|
|
267
|
+
Finite words over {'s', 'x', 'y'}
|
|
268
|
+
|
|
269
|
+
Specifying the domain and codomain::
|
|
270
|
+
|
|
271
|
+
sage: W = FiniteWords([0,1,2])
|
|
272
|
+
sage: d = {0:[0,1], 1:[0,1,0], 2:[0]}
|
|
273
|
+
sage: m = WordMorphism(d, domain=W, codomain=W)
|
|
274
|
+
sage: m([0]).parent()
|
|
275
|
+
Finite words over {0, 1, 2}
|
|
276
|
+
|
|
277
|
+
When the alphabet is non-sortable, the domain and/or codomain must be
|
|
278
|
+
explicitly given::
|
|
279
|
+
|
|
280
|
+
sage: W = FiniteWords(['a',6])
|
|
281
|
+
sage: d = {'a':['a',6,'a'],6:[6,6,6,'a']}
|
|
282
|
+
sage: WordMorphism(d, domain=W, codomain=W)
|
|
283
|
+
WordMorphism: 6->666a, a->a6a
|
|
284
|
+
|
|
285
|
+
TESTS::
|
|
286
|
+
|
|
287
|
+
sage: wm = WordMorphism('a->ab,b->ba')
|
|
288
|
+
sage: wm == loads(dumps(wm))
|
|
289
|
+
True
|
|
290
|
+
"""
|
|
291
|
+
def __init__(self, data, domain=None, codomain=None):
|
|
292
|
+
r"""
|
|
293
|
+
Construction of the morphism.
|
|
294
|
+
|
|
295
|
+
EXAMPLES:
|
|
296
|
+
|
|
297
|
+
1. If data is a str::
|
|
298
|
+
|
|
299
|
+
sage: WordMorphism('a->ab,b->ba')
|
|
300
|
+
WordMorphism: a->ab, b->ba
|
|
301
|
+
sage: WordMorphism('a->ab,b->ba')
|
|
302
|
+
WordMorphism: a->ab, b->ba
|
|
303
|
+
sage: WordMorphism('a->abc,b->bca,c->cab')
|
|
304
|
+
WordMorphism: a->abc, b->bca, c->cab
|
|
305
|
+
sage: WordMorphism('a->abdsf,b->hahdad,c->asdhasd')
|
|
306
|
+
WordMorphism: a->abdsf, b->hahdad, c->asdhasd
|
|
307
|
+
sage: WordMorphism('(->(),)->)(')
|
|
308
|
+
WordMorphism: (->(), )->)(
|
|
309
|
+
sage: WordMorphism('a->53k,b->y5?,$->49i')
|
|
310
|
+
WordMorphism: $->49i, a->53k, b->y5?
|
|
311
|
+
|
|
312
|
+
An erasing morphism::
|
|
313
|
+
|
|
314
|
+
sage: WordMorphism('a->ab,b->')
|
|
315
|
+
WordMorphism: a->ab, b->
|
|
316
|
+
|
|
317
|
+
Use the arrows ('->') correctly::
|
|
318
|
+
|
|
319
|
+
sage: WordMorphism('a->ab,b-')
|
|
320
|
+
Traceback (most recent call last):
|
|
321
|
+
...
|
|
322
|
+
ValueError: the second and third characters must be '->' (not '-')
|
|
323
|
+
sage: WordMorphism('a->ab,b')
|
|
324
|
+
Traceback (most recent call last):
|
|
325
|
+
...
|
|
326
|
+
ValueError: the second and third characters must be '->' (not '')
|
|
327
|
+
sage: WordMorphism('a->ab,a-]asdfa')
|
|
328
|
+
Traceback (most recent call last):
|
|
329
|
+
...
|
|
330
|
+
ValueError: the second and third characters must be '->' (not '-]')
|
|
331
|
+
|
|
332
|
+
Each letter must be defined only once::
|
|
333
|
+
|
|
334
|
+
sage: WordMorphism('a->ab,a->ba')
|
|
335
|
+
Traceback (most recent call last):
|
|
336
|
+
...
|
|
337
|
+
ValueError: the image of 'a' is defined twice
|
|
338
|
+
|
|
339
|
+
2. From a dictionary::
|
|
340
|
+
|
|
341
|
+
sage: WordMorphism({"a":"ab","b":"ba"})
|
|
342
|
+
WordMorphism: a->ab, b->ba
|
|
343
|
+
sage: WordMorphism({2:[4,5,6],3:[1,2,3]})
|
|
344
|
+
WordMorphism: 2->456, 3->123
|
|
345
|
+
|
|
346
|
+
The image of a letter can be a set, but the order is not
|
|
347
|
+
preserved::
|
|
348
|
+
|
|
349
|
+
sage: WordMorphism({2:[4,5,6],3:set([4,1,8])}) # random results
|
|
350
|
+
WordMorphism: 2->456, 3->814
|
|
351
|
+
|
|
352
|
+
If the image of a letter is not iterable, it is considered as a
|
|
353
|
+
letter::
|
|
354
|
+
|
|
355
|
+
sage: WordMorphism({0:1, 1:0})
|
|
356
|
+
WordMorphism: 0->1, 1->0
|
|
357
|
+
sage: WordMorphism({0:123, 1:789})
|
|
358
|
+
WordMorphism: 0->123, 1->789
|
|
359
|
+
sage: WordMorphism({2:[4,5,6], 3:123})
|
|
360
|
+
WordMorphism: 2->456, 3->123
|
|
361
|
+
|
|
362
|
+
3. From a WordMorphism::
|
|
363
|
+
|
|
364
|
+
sage: WordMorphism(WordMorphism('a->ab,b->ba'))
|
|
365
|
+
WordMorphism: a->ab, b->ba
|
|
366
|
+
|
|
367
|
+
TESTS::
|
|
368
|
+
|
|
369
|
+
sage: WordMorphism(',,,a->ab,,,b->ba,,')
|
|
370
|
+
WordMorphism: a->ab, b->ba
|
|
371
|
+
|
|
372
|
+
sage: WordMorphism({(1,2):'ab', 'a': ['c', (1,2), 'a']})
|
|
373
|
+
WordMorphism: (1, 2)->ab, a->c,(1, 2),a
|
|
374
|
+
|
|
375
|
+
sage: WordMorphism({'a':'a'}, domain=FiniteWords('ab'))
|
|
376
|
+
Traceback (most recent call last):
|
|
377
|
+
...
|
|
378
|
+
ValueError: invalid input; the keys of the dictionary must coincide with the domain alphabet
|
|
379
|
+
sage: WordMorphism({'a':'a', 'b':'b'}, domain=FiniteWords('a'))
|
|
380
|
+
Traceback (most recent call last):
|
|
381
|
+
...
|
|
382
|
+
ValueError: invalid input; the keys of the dictionary must coincide with the domain alphabet
|
|
383
|
+
"""
|
|
384
|
+
if isinstance(data, WordMorphism):
|
|
385
|
+
self._domain = data._domain
|
|
386
|
+
self._codomain = data._codomain
|
|
387
|
+
self._morph = data._morph
|
|
388
|
+
else:
|
|
389
|
+
if isinstance(data, str):
|
|
390
|
+
data = self._build_dict(data)
|
|
391
|
+
elif not isinstance(data, dict):
|
|
392
|
+
raise NotImplementedError
|
|
393
|
+
|
|
394
|
+
if codomain is None:
|
|
395
|
+
codomain = self._build_codomain(data)
|
|
396
|
+
|
|
397
|
+
if isinstance(codomain, FiniteOrInfiniteWords):
|
|
398
|
+
codomain = codomain.finite_words()
|
|
399
|
+
elif not isinstance(codomain, FiniteWords):
|
|
400
|
+
raise TypeError("the codomain must be a set of finite words")
|
|
401
|
+
self._codomain = codomain
|
|
402
|
+
|
|
403
|
+
self._morph = {}
|
|
404
|
+
|
|
405
|
+
dom_alph = []
|
|
406
|
+
for key, val in data.items():
|
|
407
|
+
dom_alph.append(key)
|
|
408
|
+
if val in codomain.alphabet():
|
|
409
|
+
self._morph[key] = codomain([val])
|
|
410
|
+
else:
|
|
411
|
+
self._morph[key] = codomain(val)
|
|
412
|
+
|
|
413
|
+
if domain is not None:
|
|
414
|
+
if isinstance(domain, FiniteOrInfiniteWords):
|
|
415
|
+
domain = domain.finite_words()
|
|
416
|
+
elif not isinstance(domain, FiniteWords):
|
|
417
|
+
raise TypeError("the codomain must be a set of finite words")
|
|
418
|
+
A = domain.alphabet()
|
|
419
|
+
if len(self._morph) != A.cardinality() or not all(a in A for a in self._morph):
|
|
420
|
+
raise ValueError('invalid input; the keys of the dictionary must coincide with the domain alphabet')
|
|
421
|
+
else:
|
|
422
|
+
try:
|
|
423
|
+
dom_alph.sort()
|
|
424
|
+
except TypeError:
|
|
425
|
+
dom_alph.sort(key=str)
|
|
426
|
+
domain = FiniteWords(dom_alph)
|
|
427
|
+
self._domain = domain
|
|
428
|
+
|
|
429
|
+
def _build_dict(self, s):
|
|
430
|
+
r"""
|
|
431
|
+
Parse the string input to WordMorphism and build the dictionary
|
|
432
|
+
it represents.
|
|
433
|
+
|
|
434
|
+
TESTS::
|
|
435
|
+
|
|
436
|
+
sage: wm = WordMorphism('a->ab,b->ba')
|
|
437
|
+
sage: wm._build_dict('a->ab,b->ba') == {'a': 'ab', 'b': 'ba'}
|
|
438
|
+
True
|
|
439
|
+
sage: wm._build_dict('a->ab,a->ba')
|
|
440
|
+
Traceback (most recent call last):
|
|
441
|
+
...
|
|
442
|
+
ValueError: the image of 'a' is defined twice
|
|
443
|
+
sage: wm._build_dict('a->ab,b>ba')
|
|
444
|
+
Traceback (most recent call last):
|
|
445
|
+
...
|
|
446
|
+
ValueError: the second and third characters must be '->' (not '>b')
|
|
447
|
+
"""
|
|
448
|
+
tmp_dict = {}
|
|
449
|
+
for fleche in s.split(','):
|
|
450
|
+
if len(fleche) == 0:
|
|
451
|
+
continue
|
|
452
|
+
|
|
453
|
+
if len(fleche) < 3 or fleche[1:3] != '->':
|
|
454
|
+
raise ValueError("the second and third characters must be '->' (not '%s')" % fleche[1:3])
|
|
455
|
+
|
|
456
|
+
lettre = fleche[0]
|
|
457
|
+
image = fleche[3:]
|
|
458
|
+
|
|
459
|
+
if lettre in tmp_dict:
|
|
460
|
+
raise ValueError("the image of %r is defined twice" % lettre)
|
|
461
|
+
|
|
462
|
+
tmp_dict[lettre] = image
|
|
463
|
+
return tmp_dict
|
|
464
|
+
|
|
465
|
+
def _build_codomain(self, data):
|
|
466
|
+
r"""
|
|
467
|
+
Return a Words domain containing all the letters in the values of
|
|
468
|
+
data (which must be a dictionary).
|
|
469
|
+
|
|
470
|
+
TESTS:
|
|
471
|
+
|
|
472
|
+
If the image of all the letters are iterable::
|
|
473
|
+
|
|
474
|
+
sage: wm = WordMorphism('a->ab,b->ba')
|
|
475
|
+
sage: wm._build_codomain({'a': 'ab', 'b': 'ba'})
|
|
476
|
+
Finite words over {'a', 'b'}
|
|
477
|
+
sage: wm._build_codomain({'a': 'dcb', 'b': 'a'})
|
|
478
|
+
Finite words over {'a', 'b', 'c', 'd'}
|
|
479
|
+
sage: wm._build_codomain({2:[4,5,6],3:[1,2,3]})
|
|
480
|
+
Finite words over {1, 2, 3, 4, 5, 6}
|
|
481
|
+
sage: wm._build_codomain({2:[4,5,6],3:set([4,1,8])})
|
|
482
|
+
Finite words over {1, 4, 5, 6, 8}
|
|
483
|
+
|
|
484
|
+
If the image of a letter is not iterable, it is considered as
|
|
485
|
+
a letter::
|
|
486
|
+
|
|
487
|
+
sage: wm._build_codomain({2:[4,5,6],3:123})
|
|
488
|
+
Finite words over {4, 5, 6, 123}
|
|
489
|
+
sage: wm._build_codomain({0:1, 1:0, 2:2})
|
|
490
|
+
Finite words over {0, 1, 2}
|
|
491
|
+
"""
|
|
492
|
+
codom_alphabet = set()
|
|
493
|
+
for val in data.values():
|
|
494
|
+
try:
|
|
495
|
+
it = iter(val)
|
|
496
|
+
except TypeError:
|
|
497
|
+
it = [val]
|
|
498
|
+
codom_alphabet.update(it)
|
|
499
|
+
try:
|
|
500
|
+
codom_alphabet = sorted(codom_alphabet)
|
|
501
|
+
except TypeError:
|
|
502
|
+
codom_alphabet = sorted(codom_alphabet, key=str)
|
|
503
|
+
return FiniteWords(codom_alphabet)
|
|
504
|
+
|
|
505
|
+
@cached_method
|
|
506
|
+
def __hash__(self):
|
|
507
|
+
r"""
|
|
508
|
+
TESTS::
|
|
509
|
+
|
|
510
|
+
sage: hash(WordMorphism('a->ab,b->ba')) # random
|
|
511
|
+
7211091143079804375
|
|
512
|
+
"""
|
|
513
|
+
return hash(tuple((k, v) for k, v in self._morph.items())) ^ hash(self._codomain)
|
|
514
|
+
|
|
515
|
+
def __eq__(self, other):
|
|
516
|
+
r"""
|
|
517
|
+
Return ``True`` if ``self`` is equal to ``other``.
|
|
518
|
+
|
|
519
|
+
EXAMPLES::
|
|
520
|
+
|
|
521
|
+
sage: n = WordMorphism('a->a,b->aa,c->aaa')
|
|
522
|
+
sage: n**3 == n**1
|
|
523
|
+
True
|
|
524
|
+
sage: WordMorphism('b->ba,a->ab') == WordMorphism('a->ab,b->ba')
|
|
525
|
+
True
|
|
526
|
+
sage: WordMorphism('b->ba,a->ab') == WordMorphism({"a":"ab","b":"ba"})
|
|
527
|
+
True
|
|
528
|
+
sage: m = WordMorphism({0:[1,2,3],1:[4,5,6]}); m
|
|
529
|
+
WordMorphism: 0->123, 1->456
|
|
530
|
+
sage: o = WordMorphism('0->123,1->456'); o
|
|
531
|
+
WordMorphism: 0->123, 1->456
|
|
532
|
+
sage: m == o
|
|
533
|
+
False
|
|
534
|
+
|
|
535
|
+
TESTS:
|
|
536
|
+
|
|
537
|
+
Check that equality depends on the codomain::
|
|
538
|
+
|
|
539
|
+
sage: m = WordMorphism('a->a,b->aa,c->aaa')
|
|
540
|
+
sage: n = WordMorphism('a->a,b->aa,c->aaa', codomain=Words('abc'))
|
|
541
|
+
sage: m == n
|
|
542
|
+
False
|
|
543
|
+
"""
|
|
544
|
+
if not isinstance(other, WordMorphism):
|
|
545
|
+
return False
|
|
546
|
+
return self._morph == other._morph and self._codomain == other._codomain
|
|
547
|
+
|
|
548
|
+
def __ne__(self, other):
|
|
549
|
+
r"""
|
|
550
|
+
Return whether ``self`` is not equal to ``other``.
|
|
551
|
+
|
|
552
|
+
EXAMPLES::
|
|
553
|
+
|
|
554
|
+
sage: m = WordMorphism('a->ab,b->baba')
|
|
555
|
+
sage: n = WordMorphism('a->ab,b->baba')
|
|
556
|
+
sage: o = WordMorphism('a->ab,b->bab')
|
|
557
|
+
sage: m != n
|
|
558
|
+
False
|
|
559
|
+
sage: n != o
|
|
560
|
+
True
|
|
561
|
+
|
|
562
|
+
This solves :issue:`12475`::
|
|
563
|
+
|
|
564
|
+
sage: s = WordMorphism('1->121,2->131,3->4,4->1')
|
|
565
|
+
sage: s == s.reversal()
|
|
566
|
+
True
|
|
567
|
+
sage: s != s.reversal()
|
|
568
|
+
False
|
|
569
|
+
"""
|
|
570
|
+
return not self == other
|
|
571
|
+
|
|
572
|
+
def __repr__(self) -> str:
|
|
573
|
+
r"""
|
|
574
|
+
Return the string representation of the morphism.
|
|
575
|
+
|
|
576
|
+
EXAMPLES::
|
|
577
|
+
|
|
578
|
+
sage: WordMorphism('a->ab,b->ba')
|
|
579
|
+
WordMorphism: a->ab, b->ba
|
|
580
|
+
sage: WordMorphism({0:[0,1],1:[1,0]})
|
|
581
|
+
WordMorphism: 0->01, 1->10
|
|
582
|
+
|
|
583
|
+
TESTS::
|
|
584
|
+
|
|
585
|
+
sage: s = WordMorphism('a->ab,b->ba')
|
|
586
|
+
sage: repr(s)
|
|
587
|
+
'WordMorphism: a->ab, b->ba'
|
|
588
|
+
"""
|
|
589
|
+
return "WordMorphism: %s" % str(self)
|
|
590
|
+
|
|
591
|
+
def __str__(self) -> str:
|
|
592
|
+
r"""
|
|
593
|
+
Return the morphism in str.
|
|
594
|
+
|
|
595
|
+
EXAMPLES::
|
|
596
|
+
|
|
597
|
+
sage: print(WordMorphism('a->ab,b->ba'))
|
|
598
|
+
a->ab, b->ba
|
|
599
|
+
sage: print(WordMorphism({0:[0,1],1:[1,0]}))
|
|
600
|
+
0->01, 1->10
|
|
601
|
+
|
|
602
|
+
The output is sorted to make it unique::
|
|
603
|
+
|
|
604
|
+
sage: print(WordMorphism('b->ba,a->ab'))
|
|
605
|
+
a->ab, b->ba
|
|
606
|
+
|
|
607
|
+
The str method is used for string formatting::
|
|
608
|
+
|
|
609
|
+
sage: s = WordMorphism('a->ab,b->ba')
|
|
610
|
+
sage: "Here is a map : %s" % s
|
|
611
|
+
'Here is a map : a->ab, b->ba'
|
|
612
|
+
|
|
613
|
+
::
|
|
614
|
+
|
|
615
|
+
sage: s = WordMorphism({1:[1,2],2:[1]})
|
|
616
|
+
sage: s.dual_map() # needs sage.modules
|
|
617
|
+
E_1^*(1->12, 2->1)
|
|
618
|
+
|
|
619
|
+
TESTS::
|
|
620
|
+
|
|
621
|
+
sage: s = WordMorphism('a->ab,b->ba')
|
|
622
|
+
sage: str(s)
|
|
623
|
+
'a->ab, b->ba'
|
|
624
|
+
"""
|
|
625
|
+
L = [str(lettre) + '->' + image.string_rep()
|
|
626
|
+
for lettre, image in self._morph.items()]
|
|
627
|
+
return ', '.join(sorted(L))
|
|
628
|
+
|
|
629
|
+
def __call__(self, w, order=1):
|
|
630
|
+
r"""
|
|
631
|
+
Return the image of ``w`` under ``self`` to the given order.
|
|
632
|
+
|
|
633
|
+
INPUT:
|
|
634
|
+
|
|
635
|
+
- ``w`` -- word or sequence in the domain of self
|
|
636
|
+
|
|
637
|
+
- ``order`` -- integer or plus ``Infinity`` (default: 1)
|
|
638
|
+
|
|
639
|
+
OUTPUT: ``word`` -- ``order``-th iterated image under ``self`` of ``w``
|
|
640
|
+
|
|
641
|
+
EXAMPLES:
|
|
642
|
+
|
|
643
|
+
The image of a word under a morphism:
|
|
644
|
+
|
|
645
|
+
1. The image of a finite word under a morphism::
|
|
646
|
+
|
|
647
|
+
sage: tm = WordMorphism ('a->ab,b->ba')
|
|
648
|
+
sage: tm('a')
|
|
649
|
+
word: ab
|
|
650
|
+
sage: tm('aabababb')
|
|
651
|
+
word: ababbaabbaabbaba
|
|
652
|
+
|
|
653
|
+
2. The iterated image of a word::
|
|
654
|
+
|
|
655
|
+
sage: tm('a', 2)
|
|
656
|
+
word: abba
|
|
657
|
+
sage: tm('aba', 3)
|
|
658
|
+
word: abbabaabbaababbaabbabaab
|
|
659
|
+
|
|
660
|
+
3. The infinitely iterated image of a letter::
|
|
661
|
+
|
|
662
|
+
sage: tm('a', oo)
|
|
663
|
+
word: abbabaabbaababbabaababbaabbabaabbaababba...
|
|
664
|
+
|
|
665
|
+
4. The image of an infinite word::
|
|
666
|
+
|
|
667
|
+
sage: t = words.ThueMorseWord()
|
|
668
|
+
sage: n = WordMorphism({0:[0, 1], 1:[1, 0]})
|
|
669
|
+
sage: n(t)
|
|
670
|
+
word: 0110100110010110100101100110100110010110...
|
|
671
|
+
sage: n(t, 3)
|
|
672
|
+
word: 0110100110010110100101100110100110010110...
|
|
673
|
+
sage: n(t)[:1000] == t[:1000]
|
|
674
|
+
True
|
|
675
|
+
|
|
676
|
+
The Fibonacci word::
|
|
677
|
+
|
|
678
|
+
sage: w = words.FibonacciWord()
|
|
679
|
+
sage: m = WordMorphism({0:'a', 1:'b'})
|
|
680
|
+
sage: m(w)
|
|
681
|
+
word: abaababaabaababaababaabaababaabaababaaba...
|
|
682
|
+
sage: f = words.FibonacciWord('ab')
|
|
683
|
+
sage: f[:1000] == m(w)[:1000]
|
|
684
|
+
True
|
|
685
|
+
|
|
686
|
+
::
|
|
687
|
+
|
|
688
|
+
sage: w = words.FibonacciWord("ab")
|
|
689
|
+
sage: m = WordMorphism('a->01,b->101')
|
|
690
|
+
sage: m(w)
|
|
691
|
+
word: 0110101011010110101011010101101011010101...
|
|
692
|
+
|
|
693
|
+
The word must be in the domain of self::
|
|
694
|
+
|
|
695
|
+
sage: tm('0021')
|
|
696
|
+
Traceback (most recent call last):
|
|
697
|
+
...
|
|
698
|
+
ValueError: 0 not in alphabet
|
|
699
|
+
|
|
700
|
+
The order must be a nonnegative integer or plus Infinity::
|
|
701
|
+
|
|
702
|
+
sage: tm('a', -1)
|
|
703
|
+
Traceback (most recent call last):
|
|
704
|
+
...
|
|
705
|
+
TypeError: order (-1) must be a nonnegative integer or plus Infinity
|
|
706
|
+
sage: tm('a', 6.7)
|
|
707
|
+
Traceback (most recent call last):
|
|
708
|
+
...
|
|
709
|
+
TypeError: order (6.7...) must be a nonnegative integer or plus Infinity
|
|
710
|
+
|
|
711
|
+
Only the first letter is considered for infinitely iterated image of
|
|
712
|
+
a word under a morphism::
|
|
713
|
+
|
|
714
|
+
sage: tm('aba',oo)
|
|
715
|
+
word: abbabaabbaababbabaababbaabbabaabbaababba...
|
|
716
|
+
|
|
717
|
+
The morphism ``self`` must be prolongable on the given letter for infinitely
|
|
718
|
+
iterated image::
|
|
719
|
+
|
|
720
|
+
sage: m = WordMorphism('a->ba,b->ab')
|
|
721
|
+
sage: m('a', oo)
|
|
722
|
+
Traceback (most recent call last):
|
|
723
|
+
...
|
|
724
|
+
TypeError: self must be prolongable on a
|
|
725
|
+
|
|
726
|
+
The empty word is fixed by any morphism for all natural
|
|
727
|
+
powers::
|
|
728
|
+
|
|
729
|
+
sage: phi = WordMorphism('a->ab,b->a')
|
|
730
|
+
sage: phi(Word())
|
|
731
|
+
word:
|
|
732
|
+
sage: phi(Word(), oo)
|
|
733
|
+
word:
|
|
734
|
+
sage: it = iter([])
|
|
735
|
+
sage: phi(it, oo)
|
|
736
|
+
word:
|
|
737
|
+
|
|
738
|
+
TESTS::
|
|
739
|
+
|
|
740
|
+
sage: for i in range(6):
|
|
741
|
+
....: tm('a', i)
|
|
742
|
+
word: a
|
|
743
|
+
word: ab
|
|
744
|
+
word: abba
|
|
745
|
+
word: abbabaab
|
|
746
|
+
word: abbabaabbaababba
|
|
747
|
+
word: abbabaabbaababbabaababbaabbabaab
|
|
748
|
+
sage: m = WordMorphism('a->,b->')
|
|
749
|
+
sage: m('')
|
|
750
|
+
word:
|
|
751
|
+
|
|
752
|
+
When the input is a finite word, the output is another
|
|
753
|
+
finite word::
|
|
754
|
+
|
|
755
|
+
sage: w = m('aabb')
|
|
756
|
+
sage: type(w)
|
|
757
|
+
<class 'sage.combinat.words.word.FiniteWord_char'>
|
|
758
|
+
|
|
759
|
+
sage: w == loads(dumps(w))
|
|
760
|
+
True
|
|
761
|
+
sage: import tempfile
|
|
762
|
+
sage: with tempfile.NamedTemporaryFile(suffix='.sobj') as f:
|
|
763
|
+
....: save(w, filename=f.name)
|
|
764
|
+
"""
|
|
765
|
+
if order == 1:
|
|
766
|
+
D = self.domain()
|
|
767
|
+
C = self.codomain()
|
|
768
|
+
if isinstance(w, (tuple, str, list)):
|
|
769
|
+
w = D(w)
|
|
770
|
+
|
|
771
|
+
if isinstance(w, FiniteWord_class):
|
|
772
|
+
im = C()
|
|
773
|
+
for a in w:
|
|
774
|
+
im += self._morph[a]
|
|
775
|
+
return im
|
|
776
|
+
|
|
777
|
+
if isinstance(w, Iterable):
|
|
778
|
+
pass
|
|
779
|
+
elif w in self._domain.alphabet():
|
|
780
|
+
return self._morph[w]
|
|
781
|
+
else:
|
|
782
|
+
raise TypeError("do not know how to handle an input (=%s) that is not iterable or not in the domain alphabet" % w)
|
|
783
|
+
|
|
784
|
+
# here we assume (maybe wrongly) that the length is infinite
|
|
785
|
+
parent = self.codomain().shift()
|
|
786
|
+
iterator = (x for y in w for x in self._morph[y])
|
|
787
|
+
parent = parent.shift()
|
|
788
|
+
return parent(iterator)
|
|
789
|
+
|
|
790
|
+
elif order is Infinity:
|
|
791
|
+
if isinstance(w, (tuple, str, list, FiniteWord_class)):
|
|
792
|
+
if len(w) == 0:
|
|
793
|
+
return self.codomain()()
|
|
794
|
+
else:
|
|
795
|
+
letter = w[0]
|
|
796
|
+
elif isinstance(w, Iterable):
|
|
797
|
+
try:
|
|
798
|
+
letter = next(w)
|
|
799
|
+
except StopIteration:
|
|
800
|
+
return self.codomain()()
|
|
801
|
+
elif w in self._domain.alphabet():
|
|
802
|
+
letter = w
|
|
803
|
+
else:
|
|
804
|
+
raise TypeError("do not know how to handle an input (=%s) that is not iterable or not in the domain alphabet" % w)
|
|
805
|
+
return self.fixed_point(letter=letter)
|
|
806
|
+
|
|
807
|
+
elif isinstance(order, (int, Integer)) and order > 1:
|
|
808
|
+
return self(self(w, order - 1))
|
|
809
|
+
|
|
810
|
+
elif order == 0:
|
|
811
|
+
return self._domain(w)
|
|
812
|
+
|
|
813
|
+
else:
|
|
814
|
+
raise TypeError("order (%s) must be a nonnegative integer or plus Infinity" % order)
|
|
815
|
+
|
|
816
|
+
def latex_layout(self, layout=None):
|
|
817
|
+
r"""
|
|
818
|
+
Get or set the actual latex layout (oneliner vs array).
|
|
819
|
+
|
|
820
|
+
INPUT:
|
|
821
|
+
|
|
822
|
+
- ``layout`` -- string (default: ``None``); can take one of the
|
|
823
|
+
following values:
|
|
824
|
+
|
|
825
|
+
- ``None`` -- returns the actual latex layout; by default, the
|
|
826
|
+
layout is ``'array'``
|
|
827
|
+
- ``'oneliner'`` -- set the layout to ``'oneliner'``
|
|
828
|
+
- ``'array'`` -- set the layout to ``'array'``
|
|
829
|
+
|
|
830
|
+
EXAMPLES::
|
|
831
|
+
|
|
832
|
+
sage: s = WordMorphism('a->ab,b->ba')
|
|
833
|
+
sage: s.latex_layout()
|
|
834
|
+
'array'
|
|
835
|
+
sage: s.latex_layout('oneliner')
|
|
836
|
+
sage: s.latex_layout()
|
|
837
|
+
'oneliner'
|
|
838
|
+
"""
|
|
839
|
+
if layout is None:
|
|
840
|
+
# return the layout
|
|
841
|
+
if not hasattr(self, '_latex_layout'):
|
|
842
|
+
self._latex_layout = 'array'
|
|
843
|
+
return self._latex_layout
|
|
844
|
+
else:
|
|
845
|
+
# change the layout
|
|
846
|
+
self._latex_layout = layout
|
|
847
|
+
|
|
848
|
+
def _latex_(self):
|
|
849
|
+
r"""
|
|
850
|
+
Return the latex representation of the morphism.
|
|
851
|
+
|
|
852
|
+
Use :meth:`latex_layout` to change latex layout (oneliner vs
|
|
853
|
+
array). The default is a latex array.
|
|
854
|
+
|
|
855
|
+
EXAMPLES::
|
|
856
|
+
|
|
857
|
+
sage: s = WordMorphism('a->ab,b->ba')
|
|
858
|
+
sage: s._latex_()
|
|
859
|
+
\begin{array}{l}
|
|
860
|
+
a \mapsto ab\\
|
|
861
|
+
b \mapsto ba
|
|
862
|
+
\end{array}
|
|
863
|
+
|
|
864
|
+
Change the latex layout to a one liner::
|
|
865
|
+
|
|
866
|
+
sage: s.latex_layout('oneliner')
|
|
867
|
+
sage: s._latex_()
|
|
868
|
+
a \mapsto ab,b \mapsto ba
|
|
869
|
+
|
|
870
|
+
TESTS:
|
|
871
|
+
|
|
872
|
+
Unknown latex style::
|
|
873
|
+
|
|
874
|
+
sage: s.latex_layout('tabular')
|
|
875
|
+
sage: s._latex_()
|
|
876
|
+
Traceback (most recent call last):
|
|
877
|
+
...
|
|
878
|
+
ValueError: unknown latex_layout(=tabular)
|
|
879
|
+
"""
|
|
880
|
+
from sage.misc.latex import LatexExpr
|
|
881
|
+
A = self.domain().alphabet()
|
|
882
|
+
latex_layout = self.latex_layout()
|
|
883
|
+
if latex_layout == 'oneliner':
|
|
884
|
+
lines = (fr"{a} \mapsto {self.image(a)}" for a in A)
|
|
885
|
+
return LatexExpr(r','.join(lines))
|
|
886
|
+
if latex_layout == 'array':
|
|
887
|
+
s = r"\begin{array}{l}" + '\n'
|
|
888
|
+
lines = (fr"{a} \mapsto {self.image(a)}" for a in A)
|
|
889
|
+
s += '\\\\\n'.join(lines)
|
|
890
|
+
s += '\n' + r"\end{array}"
|
|
891
|
+
return LatexExpr(s)
|
|
892
|
+
raise ValueError('unknown latex_layout(=%s)' % latex_layout)
|
|
893
|
+
|
|
894
|
+
def __mul__(self, other):
|
|
895
|
+
r"""
|
|
896
|
+
Return the morphism ``self``\*``other``.
|
|
897
|
+
|
|
898
|
+
EXAMPLES::
|
|
899
|
+
|
|
900
|
+
sage: m = WordMorphism('a->ab,b->ba')
|
|
901
|
+
sage: fibo = WordMorphism('a->ab,b->a')
|
|
902
|
+
sage: fibo*m
|
|
903
|
+
WordMorphism: a->aba, b->aab
|
|
904
|
+
sage: fibo*fibo
|
|
905
|
+
WordMorphism: a->aba, b->ab
|
|
906
|
+
sage: m*fibo
|
|
907
|
+
WordMorphism: a->abba, b->ab
|
|
908
|
+
|
|
909
|
+
::
|
|
910
|
+
|
|
911
|
+
sage: n = WordMorphism('a->a,b->aa,c->aaa')
|
|
912
|
+
sage: p1 = n*m
|
|
913
|
+
sage: p1
|
|
914
|
+
WordMorphism: a->aaa, b->aaa
|
|
915
|
+
sage: p1.domain()
|
|
916
|
+
Finite words over {'a', 'b'}
|
|
917
|
+
sage: p1.codomain()
|
|
918
|
+
Finite words over {'a'}
|
|
919
|
+
|
|
920
|
+
::
|
|
921
|
+
|
|
922
|
+
sage: p2 = m*n
|
|
923
|
+
sage: p2
|
|
924
|
+
WordMorphism: a->ab, b->abab, c->ababab
|
|
925
|
+
sage: p2.domain()
|
|
926
|
+
Finite words over {'a', 'b', 'c'}
|
|
927
|
+
sage: p2.codomain()
|
|
928
|
+
Finite words over {'a', 'b'}
|
|
929
|
+
|
|
930
|
+
::
|
|
931
|
+
|
|
932
|
+
sage: m = WordMorphism('0->a,1->b')
|
|
933
|
+
sage: n = WordMorphism('a->c,b->e',codomain=Words('abcde'))
|
|
934
|
+
sage: p = n * m
|
|
935
|
+
sage: p.codomain()
|
|
936
|
+
Finite words over {'a', 'b', 'c', 'd', 'e'}
|
|
937
|
+
|
|
938
|
+
TESTS::
|
|
939
|
+
|
|
940
|
+
sage: m = WordMorphism('a->b,b->c,c->a')
|
|
941
|
+
sage: WordMorphism('')*m
|
|
942
|
+
Traceback (most recent call last):
|
|
943
|
+
...
|
|
944
|
+
KeyError: 'b'
|
|
945
|
+
sage: m * WordMorphism('')
|
|
946
|
+
WordMorphism:
|
|
947
|
+
"""
|
|
948
|
+
return WordMorphism({key: self(w) for key, w in other._morph.items()},
|
|
949
|
+
codomain=self.codomain())
|
|
950
|
+
|
|
951
|
+
def __pow__(self, exp):
|
|
952
|
+
r"""
|
|
953
|
+
Return the power of ``self`` with exponent = ``exp``.
|
|
954
|
+
|
|
955
|
+
INPUT:
|
|
956
|
+
|
|
957
|
+
- ``exp`` -- positive integer
|
|
958
|
+
|
|
959
|
+
EXAMPLES::
|
|
960
|
+
|
|
961
|
+
sage: m = WordMorphism('a->ab,b->ba')
|
|
962
|
+
sage: m^1
|
|
963
|
+
WordMorphism: a->ab, b->ba
|
|
964
|
+
sage: m^2
|
|
965
|
+
WordMorphism: a->abba, b->baab
|
|
966
|
+
sage: m^3
|
|
967
|
+
WordMorphism: a->abbabaab, b->baababba
|
|
968
|
+
|
|
969
|
+
The exponent must be a positive integer::
|
|
970
|
+
|
|
971
|
+
sage: m^1.5
|
|
972
|
+
Traceback (most recent call last):
|
|
973
|
+
...
|
|
974
|
+
ValueError: exponent (1.5...) must be an integer
|
|
975
|
+
sage: m^-2
|
|
976
|
+
Traceback (most recent call last):
|
|
977
|
+
...
|
|
978
|
+
ValueError: exponent (-2) must be strictly positive
|
|
979
|
+
|
|
980
|
+
When ``self`` is not an endomorphism::
|
|
981
|
+
|
|
982
|
+
sage: n = WordMorphism('a->ba,b->abc')
|
|
983
|
+
sage: n^2
|
|
984
|
+
Traceback (most recent call last):
|
|
985
|
+
...
|
|
986
|
+
KeyError: 'c'
|
|
987
|
+
"""
|
|
988
|
+
# If exp is not an integer
|
|
989
|
+
if not isinstance(exp, (int, Integer)):
|
|
990
|
+
raise ValueError("exponent (%s) must be an integer" % exp)
|
|
991
|
+
|
|
992
|
+
# If exp is negative
|
|
993
|
+
elif exp <= 0:
|
|
994
|
+
raise ValueError("exponent (%s) must be strictly positive" % exp)
|
|
995
|
+
|
|
996
|
+
# Base of induction
|
|
997
|
+
elif exp == 1:
|
|
998
|
+
return self
|
|
999
|
+
|
|
1000
|
+
else:
|
|
1001
|
+
nexp = int(exp // 2)
|
|
1002
|
+
over = exp % 2
|
|
1003
|
+
res = (self * self)**nexp
|
|
1004
|
+
if over == 1:
|
|
1005
|
+
res *= self
|
|
1006
|
+
return res
|
|
1007
|
+
|
|
1008
|
+
def extend_by(self, other):
|
|
1009
|
+
r"""
|
|
1010
|
+
Return ``self`` extended by ``other``.
|
|
1011
|
+
|
|
1012
|
+
Let `\varphi_1:A^*\rightarrow B^*` and `\varphi_2:C^*\rightarrow D^*`
|
|
1013
|
+
be two morphisms. A morphism `\mu:(A\cup C)^*\rightarrow (B\cup D)^*`
|
|
1014
|
+
corresponds to `\varphi_1` *extended by* `\varphi_2` if
|
|
1015
|
+
`\mu(a)=\varphi_1(a)` if `a\in A` and `\mu(a)=\varphi_2(a)` otherwise.
|
|
1016
|
+
|
|
1017
|
+
INPUT:
|
|
1018
|
+
|
|
1019
|
+
- ``other`` -- a WordMorphism
|
|
1020
|
+
|
|
1021
|
+
OUTPUT: WordMorphism
|
|
1022
|
+
|
|
1023
|
+
EXAMPLES::
|
|
1024
|
+
|
|
1025
|
+
sage: m = WordMorphism('a->ab,b->ba')
|
|
1026
|
+
sage: n = WordMorphism({'0':'1','1':'0','a':'5'})
|
|
1027
|
+
sage: m.extend_by(n)
|
|
1028
|
+
WordMorphism: 0->1, 1->0, a->ab, b->ba
|
|
1029
|
+
sage: n.extend_by(m)
|
|
1030
|
+
WordMorphism: 0->1, 1->0, a->5, b->ba
|
|
1031
|
+
sage: m.extend_by(m)
|
|
1032
|
+
WordMorphism: a->ab, b->ba
|
|
1033
|
+
|
|
1034
|
+
TESTS::
|
|
1035
|
+
|
|
1036
|
+
sage: m.extend_by(WordMorphism({})) == m
|
|
1037
|
+
True
|
|
1038
|
+
sage: m.extend_by(WordMorphism('')) == m
|
|
1039
|
+
True
|
|
1040
|
+
|
|
1041
|
+
::
|
|
1042
|
+
|
|
1043
|
+
sage: m.extend_by(4)
|
|
1044
|
+
Traceback (most recent call last):
|
|
1045
|
+
...
|
|
1046
|
+
TypeError: other (=4) is not a WordMorphism
|
|
1047
|
+
"""
|
|
1048
|
+
if not isinstance(other, WordMorphism):
|
|
1049
|
+
raise TypeError("other (=%s) is not a WordMorphism" % other)
|
|
1050
|
+
|
|
1051
|
+
nv = dict(other._morph)
|
|
1052
|
+
nv.update(self._morph)
|
|
1053
|
+
return WordMorphism(nv)
|
|
1054
|
+
|
|
1055
|
+
def restrict_domain(self, alphabet):
|
|
1056
|
+
r"""
|
|
1057
|
+
Return a restriction of ``self`` to the given alphabet.
|
|
1058
|
+
|
|
1059
|
+
INPUT:
|
|
1060
|
+
|
|
1061
|
+
- ``alphabet`` -- an iterable
|
|
1062
|
+
|
|
1063
|
+
OUTPUT: WordMorphism
|
|
1064
|
+
|
|
1065
|
+
EXAMPLES::
|
|
1066
|
+
|
|
1067
|
+
sage: m = WordMorphism('a->b,b->a')
|
|
1068
|
+
sage: m.restrict_domain('a')
|
|
1069
|
+
WordMorphism: a->b
|
|
1070
|
+
sage: m.restrict_domain('')
|
|
1071
|
+
WordMorphism:
|
|
1072
|
+
sage: m.restrict_domain('A')
|
|
1073
|
+
WordMorphism:
|
|
1074
|
+
sage: m.restrict_domain('Aa')
|
|
1075
|
+
WordMorphism: a->b
|
|
1076
|
+
|
|
1077
|
+
The input alphabet must be iterable::
|
|
1078
|
+
|
|
1079
|
+
sage: m.restrict_domain(66)
|
|
1080
|
+
Traceback (most recent call last):
|
|
1081
|
+
...
|
|
1082
|
+
TypeError: 'sage.rings.integer.Integer' object is not iterable
|
|
1083
|
+
"""
|
|
1084
|
+
return WordMorphism({a: self(a) for a in alphabet
|
|
1085
|
+
if a in self.domain().alphabet()})
|
|
1086
|
+
|
|
1087
|
+
def _matrix_(self, R=None):
|
|
1088
|
+
r"""
|
|
1089
|
+
Return the incidence matrix of the morphism over the specified ring.
|
|
1090
|
+
|
|
1091
|
+
EXAMPLES::
|
|
1092
|
+
|
|
1093
|
+
sage: # needs sage.modules
|
|
1094
|
+
sage: fibo = WordMorphism('a->ab,b->a')
|
|
1095
|
+
sage: tm = WordMorphism('a->ab,b->ba')
|
|
1096
|
+
sage: Mfibo = matrix(fibo); Mfibo # indirect doctest
|
|
1097
|
+
[1 1]
|
|
1098
|
+
[1 0]
|
|
1099
|
+
sage: Mtm = matrix(tm); Mtm
|
|
1100
|
+
[1 1]
|
|
1101
|
+
[1 1]
|
|
1102
|
+
sage: Mtm * Mfibo == matrix(tm*fibo) # indirect doctest
|
|
1103
|
+
True
|
|
1104
|
+
sage: Mfibo * Mtm == matrix(fibo*tm) # indirect doctest
|
|
1105
|
+
True
|
|
1106
|
+
sage: Mfibo.parent()
|
|
1107
|
+
Full MatrixSpace of 2 by 2 dense matrices over Integer Ring
|
|
1108
|
+
sage: p = Mfibo.charpoly(); p
|
|
1109
|
+
x^2 - x - 1
|
|
1110
|
+
sage: p.roots(ring=RR, multiplicities=False) # needs numpy
|
|
1111
|
+
[-0.618033988749895, 1.61803398874989]
|
|
1112
|
+
"""
|
|
1113
|
+
if R is None:
|
|
1114
|
+
return self.incidence_matrix()
|
|
1115
|
+
else:
|
|
1116
|
+
return self.incidence_matrix().change_ring(R)
|
|
1117
|
+
|
|
1118
|
+
def incidence_matrix(self):
|
|
1119
|
+
r"""
|
|
1120
|
+
Return the incidence matrix of the morphism. The order of the rows
|
|
1121
|
+
and column are given by the order defined on the alphabet of the
|
|
1122
|
+
domain and the codomain.
|
|
1123
|
+
|
|
1124
|
+
The matrix returned is over the integers. If a different ring is
|
|
1125
|
+
desired, use either the ``change_ring`` function or the ``matrix``
|
|
1126
|
+
function.
|
|
1127
|
+
|
|
1128
|
+
EXAMPLES::
|
|
1129
|
+
|
|
1130
|
+
sage: m = WordMorphism('a->abc,b->a,c->c')
|
|
1131
|
+
sage: m.incidence_matrix() # needs sage.modules
|
|
1132
|
+
[1 1 0]
|
|
1133
|
+
[1 0 0]
|
|
1134
|
+
[1 0 1]
|
|
1135
|
+
sage: m = WordMorphism('a->abc,b->a,c->c,d->abbccccabca,e->abc')
|
|
1136
|
+
sage: m.incidence_matrix() # needs sage.modules
|
|
1137
|
+
[1 1 0 3 1]
|
|
1138
|
+
[1 0 0 3 1]
|
|
1139
|
+
[1 0 1 5 1]
|
|
1140
|
+
"""
|
|
1141
|
+
L = []
|
|
1142
|
+
domain_alphabet = self.domain().alphabet()
|
|
1143
|
+
codomain_alphabet = self.codomain().alphabet()
|
|
1144
|
+
for b in domain_alphabet:
|
|
1145
|
+
w = self._morph[b]
|
|
1146
|
+
ev_dict = w.evaluation_dict()
|
|
1147
|
+
L.append([ev_dict.get(a, 0) for a in codomain_alphabet])
|
|
1148
|
+
M = Matrix(IntegerRing(), L).transpose()
|
|
1149
|
+
return M
|
|
1150
|
+
|
|
1151
|
+
def domain(self):
|
|
1152
|
+
r"""
|
|
1153
|
+
Return domain of ``self``.
|
|
1154
|
+
|
|
1155
|
+
EXAMPLES::
|
|
1156
|
+
|
|
1157
|
+
sage: WordMorphism('a->ab,b->a').domain()
|
|
1158
|
+
Finite words over {'a', 'b'}
|
|
1159
|
+
sage: WordMorphism('b->ba,a->ab').domain()
|
|
1160
|
+
Finite words over {'a', 'b'}
|
|
1161
|
+
sage: WordMorphism('6->ab,y->5,0->asd').domain()
|
|
1162
|
+
Finite words over {'0', '6', 'y'}
|
|
1163
|
+
"""
|
|
1164
|
+
return self._domain
|
|
1165
|
+
|
|
1166
|
+
def codomain(self):
|
|
1167
|
+
r"""
|
|
1168
|
+
Return the codomain of ``self``.
|
|
1169
|
+
|
|
1170
|
+
EXAMPLES::
|
|
1171
|
+
|
|
1172
|
+
sage: WordMorphism('a->ab,b->a').codomain()
|
|
1173
|
+
Finite words over {'a', 'b'}
|
|
1174
|
+
sage: WordMorphism('6->ab,y->5,0->asd').codomain()
|
|
1175
|
+
Finite words over {'5', 'a', 'b', 'd', 's'}
|
|
1176
|
+
"""
|
|
1177
|
+
return self._codomain
|
|
1178
|
+
|
|
1179
|
+
def is_endomorphism(self):
|
|
1180
|
+
r"""
|
|
1181
|
+
Return whether ``self`` is an endomorphism, that is if the
|
|
1182
|
+
domain coincide with the codomain.
|
|
1183
|
+
|
|
1184
|
+
EXAMPLES::
|
|
1185
|
+
|
|
1186
|
+
sage: WordMorphism('a->ab,b->a').is_endomorphism()
|
|
1187
|
+
True
|
|
1188
|
+
sage: WordMorphism('6->ab,y->5,0->asd').is_endomorphism()
|
|
1189
|
+
False
|
|
1190
|
+
sage: WordMorphism('a->a,b->aa,c->aaa').is_endomorphism()
|
|
1191
|
+
False
|
|
1192
|
+
sage: Wabc = Words('abc')
|
|
1193
|
+
sage: m = WordMorphism('a->a,b->aa,c->aaa',codomain = Wabc)
|
|
1194
|
+
sage: m.is_endomorphism()
|
|
1195
|
+
True
|
|
1196
|
+
|
|
1197
|
+
We check that :issue:`8674` is fixed::
|
|
1198
|
+
|
|
1199
|
+
sage: P = WordPaths('abcd') # needs sage.modules
|
|
1200
|
+
sage: m = WordMorphism('a->adab,b->ab,c->cbcd,d->cd', # needs sage.modules
|
|
1201
|
+
....: domain=P, codomain=P)
|
|
1202
|
+
sage: m.is_endomorphism() # needs sage.modules
|
|
1203
|
+
True
|
|
1204
|
+
"""
|
|
1205
|
+
return self.codomain() == self.domain()
|
|
1206
|
+
|
|
1207
|
+
def is_self_composable(self):
|
|
1208
|
+
r"""
|
|
1209
|
+
Return whether the codomain of ``self`` is contained in the domain.
|
|
1210
|
+
|
|
1211
|
+
EXAMPLES::
|
|
1212
|
+
|
|
1213
|
+
sage: f = WordMorphism('a->a,b->a')
|
|
1214
|
+
sage: f.is_endomorphism()
|
|
1215
|
+
False
|
|
1216
|
+
sage: f.is_self_composable()
|
|
1217
|
+
True
|
|
1218
|
+
"""
|
|
1219
|
+
Adom = self.domain().alphabet()
|
|
1220
|
+
Acodom = self.codomain().alphabet()
|
|
1221
|
+
if Adom == Acodom:
|
|
1222
|
+
return True
|
|
1223
|
+
if Adom.cardinality() < Acodom.cardinality():
|
|
1224
|
+
return False
|
|
1225
|
+
if Adom.cardinality() == Infinity:
|
|
1226
|
+
raise NotImplementedError
|
|
1227
|
+
return all(a in Adom for a in Acodom)
|
|
1228
|
+
|
|
1229
|
+
def image(self, letter):
|
|
1230
|
+
r"""
|
|
1231
|
+
Return the image of a letter.
|
|
1232
|
+
|
|
1233
|
+
INPUT:
|
|
1234
|
+
|
|
1235
|
+
- ``letter`` -- a letter in the domain alphabet
|
|
1236
|
+
|
|
1237
|
+
OUTPUT: word
|
|
1238
|
+
|
|
1239
|
+
.. NOTE::
|
|
1240
|
+
|
|
1241
|
+
The letter is assumed to be in the domain alphabet
|
|
1242
|
+
(no check done). Hence, this method is faster
|
|
1243
|
+
than the ``__call__`` method suitable for words input.
|
|
1244
|
+
|
|
1245
|
+
EXAMPLES::
|
|
1246
|
+
|
|
1247
|
+
sage: m = WordMorphism('a->ab,b->ac,c->a')
|
|
1248
|
+
sage: m.image('b')
|
|
1249
|
+
word: ac
|
|
1250
|
+
|
|
1251
|
+
::
|
|
1252
|
+
|
|
1253
|
+
sage: s = WordMorphism({('a', 1):[('a', 1), ('a', 2)], ('a', 2):[('a', 1)]})
|
|
1254
|
+
sage: s.image(('a',1))
|
|
1255
|
+
word: ('a', 1),('a', 2)
|
|
1256
|
+
|
|
1257
|
+
::
|
|
1258
|
+
|
|
1259
|
+
sage: s = WordMorphism({'b':[1,2], 'a':(2,3,4), 'z':[9,8,7]})
|
|
1260
|
+
sage: s.image('b')
|
|
1261
|
+
word: 12
|
|
1262
|
+
sage: s.image('a')
|
|
1263
|
+
word: 234
|
|
1264
|
+
sage: s.image('z')
|
|
1265
|
+
word: 987
|
|
1266
|
+
"""
|
|
1267
|
+
return self._morph[letter]
|
|
1268
|
+
|
|
1269
|
+
def images(self) -> list:
|
|
1270
|
+
r"""
|
|
1271
|
+
Return the list of all the images of the letters of the alphabet
|
|
1272
|
+
under ``self``.
|
|
1273
|
+
|
|
1274
|
+
EXAMPLES::
|
|
1275
|
+
|
|
1276
|
+
sage: sorted(WordMorphism('a->ab,b->a').images())
|
|
1277
|
+
[word: a, word: ab]
|
|
1278
|
+
sage: sorted(WordMorphism('6->ab,y->5,0->asd').images())
|
|
1279
|
+
[word: 5, word: ab, word: asd]
|
|
1280
|
+
"""
|
|
1281
|
+
return list(self._morph.values())
|
|
1282
|
+
|
|
1283
|
+
def reversal(self):
|
|
1284
|
+
r"""
|
|
1285
|
+
Return the reversal of ``self``.
|
|
1286
|
+
|
|
1287
|
+
EXAMPLES::
|
|
1288
|
+
|
|
1289
|
+
sage: WordMorphism('6->ab,y->5,0->asd').reversal()
|
|
1290
|
+
WordMorphism: 0->dsa, 6->ba, y->5
|
|
1291
|
+
sage: WordMorphism('a->ab,b->a').reversal()
|
|
1292
|
+
WordMorphism: a->ba, b->a
|
|
1293
|
+
"""
|
|
1294
|
+
return WordMorphism({key: w.reversal()
|
|
1295
|
+
for key, w in self._morph.items()},
|
|
1296
|
+
codomain=self._codomain)
|
|
1297
|
+
|
|
1298
|
+
def is_empty(self):
|
|
1299
|
+
r"""
|
|
1300
|
+
Return ``True`` if the cardinality of the domain is zero and
|
|
1301
|
+
``False`` otherwise.
|
|
1302
|
+
|
|
1303
|
+
EXAMPLES::
|
|
1304
|
+
|
|
1305
|
+
sage: WordMorphism('').is_empty()
|
|
1306
|
+
True
|
|
1307
|
+
sage: WordMorphism('a->a').is_empty()
|
|
1308
|
+
False
|
|
1309
|
+
"""
|
|
1310
|
+
return len(self._morph) == 0
|
|
1311
|
+
|
|
1312
|
+
def is_erasing(self):
|
|
1313
|
+
r"""
|
|
1314
|
+
Return ``True`` if ``self`` is an erasing morphism, i.e. the image of a
|
|
1315
|
+
letter is the empty word.
|
|
1316
|
+
|
|
1317
|
+
EXAMPLES::
|
|
1318
|
+
|
|
1319
|
+
sage: WordMorphism('a->ab,b->a').is_erasing()
|
|
1320
|
+
False
|
|
1321
|
+
sage: WordMorphism('6->ab,y->5,0->asd').is_erasing()
|
|
1322
|
+
False
|
|
1323
|
+
sage: WordMorphism('6->ab,y->5,0->asd,7->').is_erasing()
|
|
1324
|
+
True
|
|
1325
|
+
sage: WordMorphism('').is_erasing()
|
|
1326
|
+
False
|
|
1327
|
+
"""
|
|
1328
|
+
for image in self.images():
|
|
1329
|
+
if image.is_empty():
|
|
1330
|
+
return True
|
|
1331
|
+
return False
|
|
1332
|
+
|
|
1333
|
+
def is_identity(self):
|
|
1334
|
+
r"""
|
|
1335
|
+
Return ``True`` if ``self`` is the identity morphism.
|
|
1336
|
+
|
|
1337
|
+
EXAMPLES::
|
|
1338
|
+
|
|
1339
|
+
sage: m = WordMorphism('a->a,b->b,c->c,d->e')
|
|
1340
|
+
sage: m.is_identity()
|
|
1341
|
+
False
|
|
1342
|
+
sage: WordMorphism('a->a,b->b,c->c').is_identity()
|
|
1343
|
+
True
|
|
1344
|
+
sage: WordMorphism('a->a,b->b,c->cb').is_identity()
|
|
1345
|
+
False
|
|
1346
|
+
sage: m = WordMorphism('a->b,b->c,c->a')
|
|
1347
|
+
sage: (m^2).is_identity()
|
|
1348
|
+
False
|
|
1349
|
+
sage: (m^3).is_identity()
|
|
1350
|
+
True
|
|
1351
|
+
sage: (m^4).is_identity()
|
|
1352
|
+
False
|
|
1353
|
+
sage: WordMorphism('').is_identity()
|
|
1354
|
+
True
|
|
1355
|
+
sage: WordMorphism({0:[0],1:[1]}).is_identity()
|
|
1356
|
+
True
|
|
1357
|
+
|
|
1358
|
+
We check that :issue:`8618` is fixed::
|
|
1359
|
+
|
|
1360
|
+
sage: t = WordMorphism({'a1':['a2'], 'a2':['a1']})
|
|
1361
|
+
sage: (t*t).is_identity()
|
|
1362
|
+
True
|
|
1363
|
+
"""
|
|
1364
|
+
if self.domain() != self.codomain():
|
|
1365
|
+
return False
|
|
1366
|
+
|
|
1367
|
+
for letter in self.domain().alphabet():
|
|
1368
|
+
img = self.image(letter)
|
|
1369
|
+
if img.length() != 1:
|
|
1370
|
+
return False
|
|
1371
|
+
elif img[0] != letter:
|
|
1372
|
+
return False
|
|
1373
|
+
return True
|
|
1374
|
+
|
|
1375
|
+
def partition_of_domain_alphabet(self):
|
|
1376
|
+
r"""
|
|
1377
|
+
Return a partition of the domain alphabet.
|
|
1378
|
+
|
|
1379
|
+
Let `\varphi:\Sigma^*\rightarrow\Sigma^*` be an involution. There
|
|
1380
|
+
exists a triple of sets `(A, B, C)` such that
|
|
1381
|
+
|
|
1382
|
+
- `A \cup B \cup C =\Sigma`;
|
|
1383
|
+
- `A`, `B` and `C` are mutually disjoint and
|
|
1384
|
+
- `\varphi(A)= B`, `\varphi(B)= A`, `\varphi(C)= C`.
|
|
1385
|
+
|
|
1386
|
+
These sets are not unique.
|
|
1387
|
+
|
|
1388
|
+
INPUT:
|
|
1389
|
+
|
|
1390
|
+
- ``self`` -- an involution
|
|
1391
|
+
|
|
1392
|
+
OUTPUT: a tuple of three sets
|
|
1393
|
+
|
|
1394
|
+
EXAMPLES::
|
|
1395
|
+
|
|
1396
|
+
sage: m = WordMorphism('a->b,b->a')
|
|
1397
|
+
sage: m.partition_of_domain_alphabet() # random ordering
|
|
1398
|
+
({'a'}, {'b'}, {})
|
|
1399
|
+
sage: m = WordMorphism('a->b,b->a,c->c')
|
|
1400
|
+
sage: m.partition_of_domain_alphabet() # random ordering
|
|
1401
|
+
({'a'}, {'b'}, {'c'})
|
|
1402
|
+
sage: m = WordMorphism('a->a,b->b,c->c')
|
|
1403
|
+
sage: m.partition_of_domain_alphabet() # random ordering
|
|
1404
|
+
({}, {}, {'a', 'c', 'b'})
|
|
1405
|
+
sage: m = WordMorphism('A->T,T->A,C->G,G->C')
|
|
1406
|
+
sage: m.partition_of_domain_alphabet() # random ordering
|
|
1407
|
+
({'A', 'C'}, {'T', 'G'}, {})
|
|
1408
|
+
sage: I = WordMorphism({0:oo,oo:0,1:-1,-1:1,2:-2,-2:2,3:-3,-3:3})
|
|
1409
|
+
sage: I.partition_of_domain_alphabet() # random ordering
|
|
1410
|
+
({0, -1, -3, -2}, {1, 2, 3, +Infinity}, {})
|
|
1411
|
+
|
|
1412
|
+
TESTS::
|
|
1413
|
+
|
|
1414
|
+
sage: m = WordMorphism('a->b,b->a,c->a')
|
|
1415
|
+
sage: m.partition_of_domain_alphabet()
|
|
1416
|
+
Traceback (most recent call last):
|
|
1417
|
+
...
|
|
1418
|
+
TypeError: self (=a->b, b->a, c->a) is not an endomorphism
|
|
1419
|
+
"""
|
|
1420
|
+
if not self.is_involution():
|
|
1421
|
+
raise TypeError("self is not an involution")
|
|
1422
|
+
|
|
1423
|
+
A = set()
|
|
1424
|
+
B = set()
|
|
1425
|
+
C = set()
|
|
1426
|
+
for a in self.domain().alphabet():
|
|
1427
|
+
if a == self(a)[0]:
|
|
1428
|
+
C.add(a)
|
|
1429
|
+
elif not (a in A or a in B):
|
|
1430
|
+
A.add(a)
|
|
1431
|
+
B.add(self(a)[0])
|
|
1432
|
+
|
|
1433
|
+
return Set(A), Set(B), Set(C)
|
|
1434
|
+
|
|
1435
|
+
def is_involution(self):
|
|
1436
|
+
r"""
|
|
1437
|
+
Return ``True`` if ``self`` is an involution, i.e. its square
|
|
1438
|
+
is the identity.
|
|
1439
|
+
|
|
1440
|
+
INPUT:
|
|
1441
|
+
|
|
1442
|
+
- ``self`` -- an endomorphism
|
|
1443
|
+
|
|
1444
|
+
EXAMPLES::
|
|
1445
|
+
|
|
1446
|
+
sage: WordMorphism('a->b,b->a').is_involution()
|
|
1447
|
+
True
|
|
1448
|
+
sage: WordMorphism('a->b,b->ba').is_involution()
|
|
1449
|
+
False
|
|
1450
|
+
sage: WordMorphism({0:[1],1:[0]}).is_involution()
|
|
1451
|
+
True
|
|
1452
|
+
|
|
1453
|
+
TESTS::
|
|
1454
|
+
|
|
1455
|
+
sage: WordMorphism('').is_involution()
|
|
1456
|
+
True
|
|
1457
|
+
sage: WordMorphism({0:1,1:0,2:3}).is_involution()
|
|
1458
|
+
Traceback (most recent call last):
|
|
1459
|
+
...
|
|
1460
|
+
TypeError: self (=0->1, 1->0, 2->3) is not an endomorphism
|
|
1461
|
+
"""
|
|
1462
|
+
if not self.is_endomorphism():
|
|
1463
|
+
raise TypeError("self (=%s) is not an endomorphism" % self)
|
|
1464
|
+
|
|
1465
|
+
return (self * self).is_identity()
|
|
1466
|
+
|
|
1467
|
+
def pisot_eigenvector_right(self):
|
|
1468
|
+
r"""
|
|
1469
|
+
Return the right eigenvector of the incidence matrix associated
|
|
1470
|
+
to the largest eigenvalue (in absolute value).
|
|
1471
|
+
|
|
1472
|
+
Unicity of the result is guaranteed when the multiplicity of the
|
|
1473
|
+
largest eigenvalue is one, for example when ``self`` is a Pisot
|
|
1474
|
+
irreductible substitution.
|
|
1475
|
+
|
|
1476
|
+
A substitution is Pisot irreducible if the characteristic
|
|
1477
|
+
polynomial of its incidence matrix is irreducible over `\QQ` and
|
|
1478
|
+
has all roots, except one, of modulus strictly smaller than 1.
|
|
1479
|
+
|
|
1480
|
+
INPUT:
|
|
1481
|
+
|
|
1482
|
+
- ``self`` -- a Pisot irreducible substitution
|
|
1483
|
+
|
|
1484
|
+
EXAMPLES::
|
|
1485
|
+
|
|
1486
|
+
sage: m = WordMorphism('a->aaaabbc,b->aaabbc,c->aabc')
|
|
1487
|
+
sage: matrix(m) # needs sage.modules
|
|
1488
|
+
[4 3 2]
|
|
1489
|
+
[2 2 1]
|
|
1490
|
+
[1 1 1]
|
|
1491
|
+
sage: m.pisot_eigenvector_right() # needs sage.modules sage.rings.number_field
|
|
1492
|
+
(1, 0.5436890126920763?, 0.2955977425220848?)
|
|
1493
|
+
"""
|
|
1494
|
+
eig = self.incidence_matrix().eigenvectors_right()
|
|
1495
|
+
return max(eig, key=lambda x: abs(x[0]))[1][0]
|
|
1496
|
+
|
|
1497
|
+
def pisot_eigenvector_left(self):
|
|
1498
|
+
r"""
|
|
1499
|
+
Return the left eigenvector of the incidence matrix associated
|
|
1500
|
+
to the largest eigenvalue (in absolute value).
|
|
1501
|
+
|
|
1502
|
+
Unicity of the result is guaranteed when the multiplicity of the
|
|
1503
|
+
largest eigenvalue is one, for example when ``self`` is a Pisot
|
|
1504
|
+
irreductible substitution.
|
|
1505
|
+
|
|
1506
|
+
A substitution is Pisot irreducible if the characteristic
|
|
1507
|
+
polynomial of its incidence matrix is irreducible over `\QQ` and
|
|
1508
|
+
has all roots, except one, of modulus strictly smaller than 1.
|
|
1509
|
+
|
|
1510
|
+
INPUT:
|
|
1511
|
+
|
|
1512
|
+
- ``self`` -- a Pisot irreducible substitution
|
|
1513
|
+
|
|
1514
|
+
EXAMPLES::
|
|
1515
|
+
|
|
1516
|
+
sage: m = WordMorphism('a->aaaabbc,b->aaabbc,c->aabc')
|
|
1517
|
+
sage: matrix(m) # needs sage.modules
|
|
1518
|
+
[4 3 2]
|
|
1519
|
+
[2 2 1]
|
|
1520
|
+
[1 1 1]
|
|
1521
|
+
sage: m.pisot_eigenvector_left() # needs sage.modules sage.rings.number_field
|
|
1522
|
+
(1, 0.8392867552141611?, 0.5436890126920763?)
|
|
1523
|
+
"""
|
|
1524
|
+
eig = self.incidence_matrix().eigenvectors_left()
|
|
1525
|
+
return max(eig, key=lambda x: abs(x[0]))[1][0]
|
|
1526
|
+
|
|
1527
|
+
def _check_primitive(self):
|
|
1528
|
+
r"""
|
|
1529
|
+
Return ``True`` if all the letters of the domain appear in all the
|
|
1530
|
+
images of letters of the domain.
|
|
1531
|
+
|
|
1532
|
+
INPUT:
|
|
1533
|
+
|
|
1534
|
+
- ``self`` -- the codomain must be an instance of Words
|
|
1535
|
+
|
|
1536
|
+
EXAMPLES::
|
|
1537
|
+
|
|
1538
|
+
sage: m = WordMorphism('a->ab,b->ba')
|
|
1539
|
+
sage: m._check_primitive()
|
|
1540
|
+
True
|
|
1541
|
+
sage: fibo = WordMorphism('a->ab,b->a')
|
|
1542
|
+
sage: fibo._check_primitive()
|
|
1543
|
+
False
|
|
1544
|
+
sage: WordMorphism({2:[4,5,6],3:[4,1,8]})
|
|
1545
|
+
WordMorphism: 2->456, 3->418
|
|
1546
|
+
sage: WordMorphism({2:[4,5,6],3:[4,1,8]})._check_primitive()
|
|
1547
|
+
False
|
|
1548
|
+
"""
|
|
1549
|
+
dom_alphabet = set(self.domain().alphabet())
|
|
1550
|
+
|
|
1551
|
+
for image in self.images():
|
|
1552
|
+
if not dom_alphabet <= set(image):
|
|
1553
|
+
return False
|
|
1554
|
+
return True
|
|
1555
|
+
|
|
1556
|
+
def is_primitive(self):
|
|
1557
|
+
r"""
|
|
1558
|
+
Return ``True`` if ``self`` is primitive.
|
|
1559
|
+
|
|
1560
|
+
A morphism `\varphi` is *primitive* if there exists
|
|
1561
|
+
an positive integer `k` such that for all `\alpha\in\Sigma`,
|
|
1562
|
+
`\varphi^k(\alpha)` contains all the letters of `\Sigma`.
|
|
1563
|
+
|
|
1564
|
+
INPUT:
|
|
1565
|
+
|
|
1566
|
+
- ``self`` -- an endomorphism
|
|
1567
|
+
|
|
1568
|
+
ALGORITHM:
|
|
1569
|
+
|
|
1570
|
+
Exercices 8.7.8, p.281 in [1]:
|
|
1571
|
+
(c) Let `y(M)` be the least integer `e` such that `M^e` has all
|
|
1572
|
+
positive entries. Prove that, for all primitive matrices `M`,
|
|
1573
|
+
we have `y(M) \leq (d-1)^2 + 1`.
|
|
1574
|
+
(d) Prove that the bound `y(M)\leq (d-1)^2+1` is best possible.
|
|
1575
|
+
|
|
1576
|
+
EXAMPLES::
|
|
1577
|
+
|
|
1578
|
+
sage: tm = WordMorphism('a->ab,b->ba')
|
|
1579
|
+
sage: tm.is_primitive() # needs sage.modules
|
|
1580
|
+
True
|
|
1581
|
+
sage: fibo = WordMorphism('a->ab,b->a')
|
|
1582
|
+
sage: fibo.is_primitive() # needs sage.modules
|
|
1583
|
+
True
|
|
1584
|
+
sage: m = WordMorphism('a->bb,b->aa')
|
|
1585
|
+
sage: m.is_primitive() # needs sage.modules
|
|
1586
|
+
False
|
|
1587
|
+
sage: f = WordMorphism({0:[1],1:[0]})
|
|
1588
|
+
sage: f.is_primitive() # needs sage.modules
|
|
1589
|
+
False
|
|
1590
|
+
|
|
1591
|
+
::
|
|
1592
|
+
|
|
1593
|
+
sage: s = WordMorphism('a->b,b->c,c->ab')
|
|
1594
|
+
sage: s.is_primitive() # needs sage.modules
|
|
1595
|
+
True
|
|
1596
|
+
sage: s = WordMorphism('a->b,b->c,c->d,d->e,e->f,f->g,g->h,h->ab')
|
|
1597
|
+
sage: s.is_primitive() # needs sage.modules
|
|
1598
|
+
True
|
|
1599
|
+
|
|
1600
|
+
TESTS::
|
|
1601
|
+
|
|
1602
|
+
sage: m = WordMorphism('a->bb,b->aac')
|
|
1603
|
+
sage: m.is_primitive()
|
|
1604
|
+
Traceback (most recent call last):
|
|
1605
|
+
...
|
|
1606
|
+
TypeError: self (=a->bb, b->aac) is not an endomorphism
|
|
1607
|
+
sage: m = WordMorphism('a->,b->', codomain=Words('ab'))
|
|
1608
|
+
sage: m.is_primitive() # needs sage.modules
|
|
1609
|
+
False
|
|
1610
|
+
sage: m = WordMorphism('a->,b->')
|
|
1611
|
+
sage: m.is_primitive()
|
|
1612
|
+
Traceback (most recent call last):
|
|
1613
|
+
...
|
|
1614
|
+
TypeError: self (=a->, b->) is not an endomorphism
|
|
1615
|
+
|
|
1616
|
+
REFERENCES:
|
|
1617
|
+
|
|
1618
|
+
- [1] Jean-Paul Allouche and Jeffrey Shallit, Automatic Sequences:
|
|
1619
|
+
Theory, Applications, Generalizations, Cambridge University Press,
|
|
1620
|
+
2003.
|
|
1621
|
+
"""
|
|
1622
|
+
if not self.is_endomorphism():
|
|
1623
|
+
raise TypeError("self (=%s) is not an endomorphism" % self)
|
|
1624
|
+
return self.incidence_matrix().is_primitive()
|
|
1625
|
+
|
|
1626
|
+
def is_prolongable(self, letter):
|
|
1627
|
+
r"""
|
|
1628
|
+
Return ``True`` if ``self`` is prolongable on ``letter``.
|
|
1629
|
+
|
|
1630
|
+
A morphism `\varphi` is prolongable on a letter `a`
|
|
1631
|
+
if `a` is a prefix of `\varphi(a)`.
|
|
1632
|
+
|
|
1633
|
+
INPUT:
|
|
1634
|
+
|
|
1635
|
+
- ``self`` -- its codomain must be an instance of Words
|
|
1636
|
+
- ``letter`` -- a letter in the domain alphabet
|
|
1637
|
+
|
|
1638
|
+
OUTPUT: boolean
|
|
1639
|
+
|
|
1640
|
+
EXAMPLES::
|
|
1641
|
+
|
|
1642
|
+
sage: WordMorphism('a->ab,b->a').is_prolongable(letter='a')
|
|
1643
|
+
True
|
|
1644
|
+
sage: WordMorphism('a->ab,b->a').is_prolongable(letter='b')
|
|
1645
|
+
False
|
|
1646
|
+
sage: WordMorphism('a->ba,b->ab').is_prolongable(letter='b')
|
|
1647
|
+
False
|
|
1648
|
+
sage: (WordMorphism('a->ba,b->ab')^2).is_prolongable(letter='b')
|
|
1649
|
+
True
|
|
1650
|
+
sage: WordMorphism('a->ba,b->').is_prolongable(letter='b')
|
|
1651
|
+
False
|
|
1652
|
+
sage: WordMorphism('a->bb,b->aac').is_prolongable(letter='a')
|
|
1653
|
+
False
|
|
1654
|
+
|
|
1655
|
+
We check that :issue:`8595` is fixed::
|
|
1656
|
+
|
|
1657
|
+
sage: s = WordMorphism({('a', 1) : [('a', 1), ('a', 2)], ('a', 2) : [('a', 1)]})
|
|
1658
|
+
sage: s.is_prolongable(('a',1))
|
|
1659
|
+
True
|
|
1660
|
+
|
|
1661
|
+
TESTS::
|
|
1662
|
+
|
|
1663
|
+
sage: WordMorphism('a->ab,b->b,c->ba').is_prolongable(letter='d')
|
|
1664
|
+
Traceback (most recent call last):
|
|
1665
|
+
...
|
|
1666
|
+
TypeError: letter (=d) is not in the domain alphabet (={'a', 'b', 'c'})
|
|
1667
|
+
|
|
1668
|
+
::
|
|
1669
|
+
|
|
1670
|
+
sage: n0, n1 = matrix(2,[1,1,1,0]), matrix(2,[2,1,1,0]) # needs sage.modules
|
|
1671
|
+
sage: n = {'a':n0, 'b':n1} # needs sage.modules
|
|
1672
|
+
sage: WordMorphism(n).is_prolongable(letter='a') # not implemented, needs sage.modules
|
|
1673
|
+
Traceback (most recent call last):
|
|
1674
|
+
...
|
|
1675
|
+
TypeError: codomain of self must be an instance of Words
|
|
1676
|
+
"""
|
|
1677
|
+
if letter not in self.domain().alphabet():
|
|
1678
|
+
raise TypeError("letter (={}) is not in the domain alphabet (={})".format(letter, self.domain().alphabet()))
|
|
1679
|
+
image = self.image(letter)
|
|
1680
|
+
return not image.is_empty() and letter == image[0]
|
|
1681
|
+
|
|
1682
|
+
def is_uniform(self, k=None):
|
|
1683
|
+
r"""
|
|
1684
|
+
Return ``True`` if ``self`` is a `k`-uniform morphism.
|
|
1685
|
+
|
|
1686
|
+
Let `k` be a positive integer. A morphism `\phi` is called `k`-uniform
|
|
1687
|
+
if for every letter `\alpha`, we have `|\phi(\alpha)| = k`. In other
|
|
1688
|
+
words, all images have length `k`. A morphism is called uniform if it
|
|
1689
|
+
is `k`-uniform for some positive integer `k`.
|
|
1690
|
+
|
|
1691
|
+
INPUT:
|
|
1692
|
+
|
|
1693
|
+
- ``k`` -- positive integer or ``None``. If set to a positive integer,
|
|
1694
|
+
then the function return ``True`` if ``self`` is `k`-uniform.
|
|
1695
|
+
If set to ``None``, then the function return ``True`` if ``self``
|
|
1696
|
+
is uniform.
|
|
1697
|
+
|
|
1698
|
+
EXAMPLES::
|
|
1699
|
+
|
|
1700
|
+
sage: phi = WordMorphism('a->ab,b->a')
|
|
1701
|
+
sage: phi.is_uniform()
|
|
1702
|
+
False
|
|
1703
|
+
sage: phi.is_uniform(k=1)
|
|
1704
|
+
False
|
|
1705
|
+
sage: tau = WordMorphism('a->ab,b->ba')
|
|
1706
|
+
sage: tau.is_uniform()
|
|
1707
|
+
True
|
|
1708
|
+
sage: tau.is_uniform(k=1)
|
|
1709
|
+
False
|
|
1710
|
+
sage: tau.is_uniform(k=2)
|
|
1711
|
+
True
|
|
1712
|
+
|
|
1713
|
+
TESTS::
|
|
1714
|
+
|
|
1715
|
+
sage: phi = WordMorphism('')
|
|
1716
|
+
sage: phi.is_uniform()
|
|
1717
|
+
True
|
|
1718
|
+
"""
|
|
1719
|
+
if k is None:
|
|
1720
|
+
try:
|
|
1721
|
+
k = self.images()[0].length()
|
|
1722
|
+
except IndexError:
|
|
1723
|
+
return True
|
|
1724
|
+
return all(w.length() == k for w in self.images())
|
|
1725
|
+
|
|
1726
|
+
def fixed_point(self, letter):
|
|
1727
|
+
r"""
|
|
1728
|
+
Return the fixed point of ``self`` beginning by the given ``letter``.
|
|
1729
|
+
|
|
1730
|
+
A fixed point of morphism `\varphi` is a word `w` such that
|
|
1731
|
+
`\varphi(w) = w`.
|
|
1732
|
+
|
|
1733
|
+
INPUT:
|
|
1734
|
+
|
|
1735
|
+
- ``self`` -- an endomorphism (or more generally a self-composable
|
|
1736
|
+
morphism), must be prolongable on ``letter``
|
|
1737
|
+
|
|
1738
|
+
- ``letter`` -- in the domain of ``self``, the first letter
|
|
1739
|
+
of the fixed point
|
|
1740
|
+
|
|
1741
|
+
OUTPUT: ``word`` -- the fixed point of ``self`` beginning with ``letter``
|
|
1742
|
+
|
|
1743
|
+
EXAMPLES::
|
|
1744
|
+
|
|
1745
|
+
sage: W = FiniteWords('abc')
|
|
1746
|
+
|
|
1747
|
+
1. Infinite fixed point::
|
|
1748
|
+
|
|
1749
|
+
sage: WordMorphism('a->ab,b->ba').fixed_point(letter='a')
|
|
1750
|
+
word: abbabaabbaababbabaababbaabbabaabbaababba...
|
|
1751
|
+
sage: WordMorphism('a->ab,b->a').fixed_point(letter='a')
|
|
1752
|
+
word: abaababaabaababaababaabaababaabaababaaba...
|
|
1753
|
+
sage: WordMorphism('a->ab,b->b,c->ba', codomain=W).fixed_point(letter='a')
|
|
1754
|
+
word: abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb...
|
|
1755
|
+
|
|
1756
|
+
2. Infinite fixed point of an erasing morphism::
|
|
1757
|
+
|
|
1758
|
+
sage: WordMorphism('a->ab,b->,c->ba', codomain=W).fixed_point(letter='a')
|
|
1759
|
+
word: ab
|
|
1760
|
+
|
|
1761
|
+
3. Finite fixed point::
|
|
1762
|
+
|
|
1763
|
+
sage: WordMorphism('a->ab,b->b,c->ba', codomain=W).fixed_point(letter='b')
|
|
1764
|
+
word: b
|
|
1765
|
+
sage: _.parent()
|
|
1766
|
+
Finite words over {'a', 'b', 'c'}
|
|
1767
|
+
|
|
1768
|
+
sage: WordMorphism('a->ab,b->cc,c->', codomain=W).fixed_point(letter='a')
|
|
1769
|
+
word: abcc
|
|
1770
|
+
sage: _.parent()
|
|
1771
|
+
Finite words over {'a', 'b', 'c'}
|
|
1772
|
+
|
|
1773
|
+
sage: m = WordMorphism('a->abc,b->,c->')
|
|
1774
|
+
sage: fp = m.fixed_point('a'); fp
|
|
1775
|
+
word: abc
|
|
1776
|
+
|
|
1777
|
+
sage: m = WordMorphism('a->ba,b->')
|
|
1778
|
+
sage: m('ba')
|
|
1779
|
+
word: ba
|
|
1780
|
+
sage: m.fixed_point('a') #todo: not implemented
|
|
1781
|
+
word: ba
|
|
1782
|
+
|
|
1783
|
+
5. Fixed point of a power of a morphism::
|
|
1784
|
+
|
|
1785
|
+
sage: m = WordMorphism('a->ba,b->ab')
|
|
1786
|
+
sage: (m^2).fixed_point(letter='a')
|
|
1787
|
+
word: abbabaabbaababbabaababbaabbabaabbaababba...
|
|
1788
|
+
|
|
1789
|
+
6. With a self-composable but not endomorphism
|
|
1790
|
+
|
|
1791
|
+
sage: m = WordMorphism('a->cbc,b->bc,c->b')
|
|
1792
|
+
sage: m.is_endomorphism()
|
|
1793
|
+
False
|
|
1794
|
+
sage: m.fixed_point('b')
|
|
1795
|
+
word: bcbbcbcbbcbbcbcbbcbcbbcbbcbcbbcbbcbcbbcb...
|
|
1796
|
+
|
|
1797
|
+
TESTS::
|
|
1798
|
+
|
|
1799
|
+
sage: WordMorphism('a->ab,b->,c->ba', codomain=W).fixed_point(letter='b')
|
|
1800
|
+
Traceback (most recent call last):
|
|
1801
|
+
...
|
|
1802
|
+
TypeError: self must be prolongable on b
|
|
1803
|
+
sage: WordMorphism('a->ab,b->,c->ba', codomain=W).fixed_point(letter='c')
|
|
1804
|
+
Traceback (most recent call last):
|
|
1805
|
+
...
|
|
1806
|
+
TypeError: self must be prolongable on c
|
|
1807
|
+
sage: WordMorphism('a->ab,b->,c->ba', codomain=W).fixed_point(letter='d')
|
|
1808
|
+
Traceback (most recent call last):
|
|
1809
|
+
...
|
|
1810
|
+
TypeError: letter (=d) is not in the domain alphabet (={'a', 'b', 'c'})
|
|
1811
|
+
sage: WordMorphism('a->aa,b->aac').fixed_point(letter='a')
|
|
1812
|
+
Traceback (most recent call last):
|
|
1813
|
+
...
|
|
1814
|
+
TypeError: self (=a->aa, b->aac) is not self-composable
|
|
1815
|
+
"""
|
|
1816
|
+
if not self.is_self_composable():
|
|
1817
|
+
raise TypeError("self (=%s) is not self-composable" % self)
|
|
1818
|
+
|
|
1819
|
+
if not self.is_prolongable(letter=letter):
|
|
1820
|
+
raise TypeError("self must be prolongable on %s" % letter)
|
|
1821
|
+
|
|
1822
|
+
parent = self.codomain()
|
|
1823
|
+
if self.is_growing(letter):
|
|
1824
|
+
from sage.combinat.words.word import InfiniteWord_morphic
|
|
1825
|
+
return InfiniteWord_morphic(parent.shift(), self, letter,
|
|
1826
|
+
coding=None, length=Infinity)
|
|
1827
|
+
else:
|
|
1828
|
+
from sage.combinat.words.word import FiniteWord_morphic
|
|
1829
|
+
w = FiniteWord_morphic(parent, self, letter,
|
|
1830
|
+
coding=None, length='finite')
|
|
1831
|
+
# since FiniteWord_morphic uses the method __getitem__
|
|
1832
|
+
# from FiniteWord_callable, the length must be precomputed
|
|
1833
|
+
# for __getitem__ to work properly
|
|
1834
|
+
w.length()
|
|
1835
|
+
return w
|
|
1836
|
+
|
|
1837
|
+
def fixed_points(self):
|
|
1838
|
+
r"""
|
|
1839
|
+
Return the list of all fixed points of ``self``.
|
|
1840
|
+
|
|
1841
|
+
EXAMPLES::
|
|
1842
|
+
|
|
1843
|
+
sage: f = WordMorphism('a->ab,b->ba')
|
|
1844
|
+
sage: for w in f.fixed_points(): print(w)
|
|
1845
|
+
abbabaabbaababbabaababbaabbabaabbaababba...
|
|
1846
|
+
baababbaabbabaababbabaabbaababbaabbabaab...
|
|
1847
|
+
|
|
1848
|
+
sage: f = WordMorphism('a->ab,b->c,c->a')
|
|
1849
|
+
sage: for w in f.fixed_points(): print(w)
|
|
1850
|
+
abcaababcabcaabcaababcaababcabcaababcabc...
|
|
1851
|
+
|
|
1852
|
+
sage: f = WordMorphism('a->ab,b->cab,c->bcc')
|
|
1853
|
+
sage: for w in f.fixed_points(): print(w)
|
|
1854
|
+
abcabbccabcabcabbccbccabcabbccabcabbccab...
|
|
1855
|
+
|
|
1856
|
+
This shows that issue :issue:`13668` has been resolved::
|
|
1857
|
+
|
|
1858
|
+
sage: d = {1:[1,2],2:[2,3],3:[4],4:[5],5:[6],6:[7],7:[8],8:[9],9:[10],10:[1]}
|
|
1859
|
+
sage: s = WordMorphism(d)
|
|
1860
|
+
sage: s7 = s^7
|
|
1861
|
+
sage: s7.fixed_points()
|
|
1862
|
+
[word: 12232342..., word: 2,3,4,5,6,7,8...]
|
|
1863
|
+
sage: s7r = s7.reversal()
|
|
1864
|
+
sage: s7r.periodic_point(2)
|
|
1865
|
+
word: 2,1,1,10,9,8,7,6,5,4,3,2,1,10,9,8,7,6,5,4,3,2,10,9,8,7,6,5,4,3,2,9,8,7,6,5,4,3,2,8,...
|
|
1866
|
+
|
|
1867
|
+
This shows that issue :issue:`13668` has been resolved::
|
|
1868
|
+
|
|
1869
|
+
sage: s = "1->321331332133133,2->133321331332133133,3->2133133133321331332133133"
|
|
1870
|
+
sage: s = WordMorphism(s)
|
|
1871
|
+
sage: (s^2).fixed_points()
|
|
1872
|
+
[]
|
|
1873
|
+
"""
|
|
1874
|
+
return [self.fixed_point(letter=letter)
|
|
1875
|
+
for letter in self.domain().alphabet()
|
|
1876
|
+
if self.is_prolongable(letter=letter)]
|
|
1877
|
+
|
|
1878
|
+
def periodic_point(self, letter):
|
|
1879
|
+
r"""
|
|
1880
|
+
Return the periodic point of ``self`` that starts with ``letter``.
|
|
1881
|
+
|
|
1882
|
+
EXAMPLES::
|
|
1883
|
+
|
|
1884
|
+
sage: f = WordMorphism('a->bab,b->ab')
|
|
1885
|
+
sage: f.periodic_point('a')
|
|
1886
|
+
word: abbababbababbabababbababbabababbababbaba...
|
|
1887
|
+
sage: f.fixed_point('a')
|
|
1888
|
+
Traceback (most recent call last):
|
|
1889
|
+
...
|
|
1890
|
+
TypeError: self must be prolongable on a
|
|
1891
|
+
|
|
1892
|
+
Make sure that :issue:`31759` is fixed::
|
|
1893
|
+
|
|
1894
|
+
sage: WordMorphism('a->b,b->a').periodic_point('a')
|
|
1895
|
+
word: a
|
|
1896
|
+
"""
|
|
1897
|
+
if not self.is_growing(letter):
|
|
1898
|
+
w = self.domain()(letter)
|
|
1899
|
+
prev = set()
|
|
1900
|
+
while w not in prev:
|
|
1901
|
+
prev.add(w)
|
|
1902
|
+
w = self(w)
|
|
1903
|
+
return w
|
|
1904
|
+
|
|
1905
|
+
elif self.is_erasing():
|
|
1906
|
+
raise NotImplementedError("self should be non erasing")
|
|
1907
|
+
|
|
1908
|
+
else:
|
|
1909
|
+
cycle = [letter]
|
|
1910
|
+
a = self(letter)[0]
|
|
1911
|
+
while a not in cycle:
|
|
1912
|
+
cycle.append(a)
|
|
1913
|
+
a = self(a)[0]
|
|
1914
|
+
if a != letter:
|
|
1915
|
+
raise ValueError("there is no periodic point starting with letter (=%s)" % letter)
|
|
1916
|
+
|
|
1917
|
+
P = PeriodicPointIterator(self, cycle)
|
|
1918
|
+
return self.codomain().shift()(P._cache[0])
|
|
1919
|
+
|
|
1920
|
+
def periodic_points(self):
|
|
1921
|
+
r"""
|
|
1922
|
+
Return the periodic points of ``f`` as a list of tuples where each tuple is
|
|
1923
|
+
a periodic orbit of ``f``.
|
|
1924
|
+
|
|
1925
|
+
EXAMPLES::
|
|
1926
|
+
|
|
1927
|
+
sage: f = WordMorphism('a->aba,b->baa')
|
|
1928
|
+
sage: for p in f.periodic_points():
|
|
1929
|
+
....: print("{} , {}".format(len(p), p[0]))
|
|
1930
|
+
1 , ababaaababaaabaabaababaaababaaabaabaabab...
|
|
1931
|
+
1 , baaabaabaababaaabaababaaabaababaaababaaa...
|
|
1932
|
+
|
|
1933
|
+
sage: f = WordMorphism('a->bab,b->aa')
|
|
1934
|
+
sage: for p in f.periodic_points():
|
|
1935
|
+
....: print("{} , {}".format(len(p), p[0]))
|
|
1936
|
+
2 , aababaaaababaababbabaababaababbabaababaa...
|
|
1937
|
+
sage: f.fixed_points()
|
|
1938
|
+
[]
|
|
1939
|
+
|
|
1940
|
+
This shows that issue :issue:`13668` has been resolved::
|
|
1941
|
+
|
|
1942
|
+
sage: d = {1:[1,2],2:[2,3],3:[4],4:[5],5:[6],6:[7],7:[8],8:[9],9:[10],10:[1]}
|
|
1943
|
+
sage: s = WordMorphism(d)
|
|
1944
|
+
sage: s7 = s^7
|
|
1945
|
+
sage: s7r = s7.reversal()
|
|
1946
|
+
sage: for p in s7r.periodic_points(): p
|
|
1947
|
+
[word: 1,10,9,8,7,6,5,4,3,2,10,9,8,7,6,5,4,3,2,...,
|
|
1948
|
+
word: 8765432765432654325432432322176543265432...,
|
|
1949
|
+
word: 5,4,3,2,4,3,2,3,2,2,1,4,3,2,3,2,2,1,3,2,...,
|
|
1950
|
+
word: 2,1,1,10,9,8,7,6,5,4,3,2,1,10,9,8,7,6,5,...,
|
|
1951
|
+
word: 9876543287654327654326543254324323221876...,
|
|
1952
|
+
word: 6543254324323221543243232214323221322121...,
|
|
1953
|
+
word: 3,2,2,1,2,1,1,10,9,8,7,6,5,4,3,2,2,1,1,1...,
|
|
1954
|
+
word: 10,9,8,7,6,5,4,3,2,9,8,7,6,5,4,3,2,8,7,6...,
|
|
1955
|
+
word: 7654326543254324323221654325432432322154...,
|
|
1956
|
+
word: 4,3,2,3,2,2,1,3,2,2,1,2,1,1,10,9,8,7,6,5...]
|
|
1957
|
+
|
|
1958
|
+
Make sure that :issue:`31454` is fixed::
|
|
1959
|
+
|
|
1960
|
+
sage: WordMorphism('a->a,b->bb').periodic_points()
|
|
1961
|
+
[[word: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb...]]
|
|
1962
|
+
"""
|
|
1963
|
+
if not self.is_endomorphism():
|
|
1964
|
+
raise ValueError("f should be an endomorphism")
|
|
1965
|
+
|
|
1966
|
+
if self.is_erasing():
|
|
1967
|
+
raise NotImplementedError("f should be non erasing")
|
|
1968
|
+
|
|
1969
|
+
A = self.domain().alphabet()
|
|
1970
|
+
d = {letter: self(letter)[0] for letter in A}
|
|
1971
|
+
G = set(self.growing_letters())
|
|
1972
|
+
|
|
1973
|
+
res = []
|
|
1974
|
+
parent = self.codomain().shift()
|
|
1975
|
+
for cycle in get_cycles(CallableDict(d), A):
|
|
1976
|
+
if cycle[0] in G:
|
|
1977
|
+
P = PeriodicPointIterator(self, cycle)
|
|
1978
|
+
res.append([parent(P._cache[i]) for i in range(len(cycle))])
|
|
1979
|
+
|
|
1980
|
+
return res
|
|
1981
|
+
|
|
1982
|
+
def _language_naive(self, n, u):
|
|
1983
|
+
r"""
|
|
1984
|
+
Return all words of length less than ``n`` by naive substitution.
|
|
1985
|
+
|
|
1986
|
+
The language of the substitution is the DOL language which consist
|
|
1987
|
+
of factors of `s^n(u)`.
|
|
1988
|
+
|
|
1989
|
+
This method assumes this substitution is non-erasing.
|
|
1990
|
+
|
|
1991
|
+
INPUT:
|
|
1992
|
+
|
|
1993
|
+
- ``n`` -- nonnegative integer; length of the words in the language
|
|
1994
|
+
|
|
1995
|
+
- ``u`` -- a word used as a seed
|
|
1996
|
+
|
|
1997
|
+
OUTPUT: a Python set
|
|
1998
|
+
|
|
1999
|
+
TESTS::
|
|
2000
|
+
|
|
2001
|
+
sage: s = WordMorphism({0: [0,1], 1:[0]})
|
|
2002
|
+
sage: W = s.domain()
|
|
2003
|
+
sage: sorted(s._language_naive(3, W([0])))
|
|
2004
|
+
[word: 0, word: 00, word: 01, word: 1, word: 10]
|
|
2005
|
+
sage: sorted(s._language_naive(3, W([1])))
|
|
2006
|
+
[word: 0, word: 00, word: 01, word: 1, word: 10]
|
|
2007
|
+
|
|
2008
|
+
sage: s._language_naive(3, W())
|
|
2009
|
+
set()
|
|
2010
|
+
sage: W([1, 1]) in s._language_naive(3, W([1, 1]))
|
|
2011
|
+
True
|
|
2012
|
+
"""
|
|
2013
|
+
L = set()
|
|
2014
|
+
todo = []
|
|
2015
|
+
for i in range(len(u)):
|
|
2016
|
+
for j in range(i + 1, min(len(u) + 1, i + n)):
|
|
2017
|
+
f = u[i:j]
|
|
2018
|
+
if f not in L:
|
|
2019
|
+
todo.append(f)
|
|
2020
|
+
L.add(f)
|
|
2021
|
+
while todo:
|
|
2022
|
+
u = todo.pop()
|
|
2023
|
+
v = self(u)
|
|
2024
|
+
if u.length() == 1:
|
|
2025
|
+
for i in range(len(v)):
|
|
2026
|
+
for j in range(i + 1, min(len(v) + 1, i + n)):
|
|
2027
|
+
f = v[i:j]
|
|
2028
|
+
if f not in L:
|
|
2029
|
+
todo.append(f)
|
|
2030
|
+
L.add(f)
|
|
2031
|
+
else:
|
|
2032
|
+
l = self._morph[u[0]].length()
|
|
2033
|
+
r = self._morph[u[-1]].length()
|
|
2034
|
+
m = v.length() - l - r
|
|
2035
|
+
x = n - 1 - m
|
|
2036
|
+
for i in range(l - min(x - 1, l), l):
|
|
2037
|
+
for j in range(l + m + 1, l + m + 1 + min(x - l + i, r)):
|
|
2038
|
+
f = v[i:j]
|
|
2039
|
+
if f not in L:
|
|
2040
|
+
todo.append(f)
|
|
2041
|
+
L.add(f)
|
|
2042
|
+
return L
|
|
2043
|
+
|
|
2044
|
+
def language(self, n, u=None):
|
|
2045
|
+
r"""
|
|
2046
|
+
Return the words of length ``n`` in the language generated by this substitution.
|
|
2047
|
+
|
|
2048
|
+
Given a non-erasing substitution `s` and a word `u` the DOL-language
|
|
2049
|
+
generated by `s` and `u` is the union of the factors of `s^n(u)` where
|
|
2050
|
+
`n` is a nonnegative integer.
|
|
2051
|
+
|
|
2052
|
+
INPUT:
|
|
2053
|
+
|
|
2054
|
+
- ``n`` -- nonnegative integer; length of the words in the language
|
|
2055
|
+
|
|
2056
|
+
- ``u`` -- a word or ``None`` (default: ``None``); if set to
|
|
2057
|
+
``None`` some letter of the alphabet is used
|
|
2058
|
+
|
|
2059
|
+
OUTPUT: a Python set
|
|
2060
|
+
|
|
2061
|
+
EXAMPLES:
|
|
2062
|
+
|
|
2063
|
+
The fibonacci morphism::
|
|
2064
|
+
|
|
2065
|
+
sage: s = WordMorphism({0: [0,1], 1: [0]})
|
|
2066
|
+
sage: sorted(s.language(3)) # needs sage.modules
|
|
2067
|
+
[word: 001, word: 010, word: 100, word: 101]
|
|
2068
|
+
sage: len(s.language(1000)) # needs sage.modules
|
|
2069
|
+
1001
|
|
2070
|
+
sage: all(len(s.language(n)) == n+1 for n in range(100)) # needs sage.modules
|
|
2071
|
+
True
|
|
2072
|
+
|
|
2073
|
+
A growing but non-primitive example. The DOL-languages generated
|
|
2074
|
+
by 0 and 2 are different::
|
|
2075
|
+
|
|
2076
|
+
sage: s = WordMorphism({0: [0,1], 1: [0], 2: [2,0,2]})
|
|
2077
|
+
|
|
2078
|
+
sage: u = s.fixed_point(0)
|
|
2079
|
+
sage: A0 = u[:200].factor_set(5) # needs sage.modules
|
|
2080
|
+
sage: B0 = s.language(5, [0]) # needs sage.modules
|
|
2081
|
+
sage: set(A0) == B0 # needs sage.modules
|
|
2082
|
+
True
|
|
2083
|
+
|
|
2084
|
+
sage: v = s.fixed_point(2)
|
|
2085
|
+
sage: A2 = v[:200].factor_set(5) # needs sage.modules
|
|
2086
|
+
sage: B2 = s.language(5, [2]) # needs sage.modules
|
|
2087
|
+
sage: set(A2) == B2 # needs sage.modules
|
|
2088
|
+
True
|
|
2089
|
+
|
|
2090
|
+
sage: len(A0), len(A2) # needs sage.modules
|
|
2091
|
+
(6, 20)
|
|
2092
|
+
|
|
2093
|
+
The Chacon transformation (non-primitive)::
|
|
2094
|
+
|
|
2095
|
+
sage: s = WordMorphism({0: [0,0,1,0], 1:[1]})
|
|
2096
|
+
sage: sorted(s.language(10)) # needs sage.modules
|
|
2097
|
+
[word: 0001000101,
|
|
2098
|
+
word: 0001010010,
|
|
2099
|
+
...
|
|
2100
|
+
word: 1010010001,
|
|
2101
|
+
word: 1010010100]
|
|
2102
|
+
"""
|
|
2103
|
+
W = self.domain()
|
|
2104
|
+
if self.codomain() != W:
|
|
2105
|
+
raise ValueError('substitution not an endomorphism')
|
|
2106
|
+
|
|
2107
|
+
if n == 0:
|
|
2108
|
+
return [W()]
|
|
2109
|
+
|
|
2110
|
+
A = W.alphabet()
|
|
2111
|
+
if u is None:
|
|
2112
|
+
u = W([A.an_element()])
|
|
2113
|
+
else:
|
|
2114
|
+
u = W(u)
|
|
2115
|
+
|
|
2116
|
+
if n <= 2 or not self.is_growing():
|
|
2117
|
+
return [w for w in self._language_naive(n + 1, u) if len(w) == n]
|
|
2118
|
+
|
|
2119
|
+
# compute the right power
|
|
2120
|
+
M = m = self.incidence_matrix().transpose()
|
|
2121
|
+
p = 1
|
|
2122
|
+
d = m.nrows()
|
|
2123
|
+
while any(sum(M.row(j)) < n for j in range(d)):
|
|
2124
|
+
M *= m
|
|
2125
|
+
p += 1
|
|
2126
|
+
s = self**p
|
|
2127
|
+
im = {a: s.image(a) for a in A}
|
|
2128
|
+
|
|
2129
|
+
# build factors by considering concatenations of images
|
|
2130
|
+
# of two letter words
|
|
2131
|
+
L2 = (w for w in self._language_naive(3, u) if len(w) == 2)
|
|
2132
|
+
L = set()
|
|
2133
|
+
for u in L2:
|
|
2134
|
+
v = im[u[0]] + im[u[1]]
|
|
2135
|
+
for k in range(len(v) - n + 1):
|
|
2136
|
+
L.add(v[k:k + n])
|
|
2137
|
+
return L
|
|
2138
|
+
|
|
2139
|
+
def conjugate(self, pos):
|
|
2140
|
+
r"""
|
|
2141
|
+
Return the morphism where the image of the letter by ``self``
|
|
2142
|
+
is conjugated of parameter ``pos``.
|
|
2143
|
+
|
|
2144
|
+
INPUT:
|
|
2145
|
+
|
|
2146
|
+
- ``pos`` -- integer
|
|
2147
|
+
|
|
2148
|
+
EXAMPLES::
|
|
2149
|
+
|
|
2150
|
+
sage: m = WordMorphism('a->abcde')
|
|
2151
|
+
sage: m.conjugate(0) == m
|
|
2152
|
+
True
|
|
2153
|
+
sage: m.conjugate(1)
|
|
2154
|
+
WordMorphism: a->bcdea
|
|
2155
|
+
sage: m.conjugate(3)
|
|
2156
|
+
WordMorphism: a->deabc
|
|
2157
|
+
sage: WordMorphism('').conjugate(4)
|
|
2158
|
+
WordMorphism:
|
|
2159
|
+
sage: m = WordMorphism('a->abcde,b->xyz')
|
|
2160
|
+
sage: m.conjugate(2)
|
|
2161
|
+
WordMorphism: a->cdeab, b->zxy
|
|
2162
|
+
"""
|
|
2163
|
+
return WordMorphism({key: w.conjugate(pos)
|
|
2164
|
+
for (key, w) in self._morph.items()})
|
|
2165
|
+
|
|
2166
|
+
def has_left_conjugate(self) -> bool:
|
|
2167
|
+
r"""
|
|
2168
|
+
Return ``True`` if all the non empty images of ``self`` begins with
|
|
2169
|
+
the same letter.
|
|
2170
|
+
|
|
2171
|
+
EXAMPLES::
|
|
2172
|
+
|
|
2173
|
+
sage: m = WordMorphism('a->abcde,b->xyz')
|
|
2174
|
+
sage: m.has_left_conjugate()
|
|
2175
|
+
False
|
|
2176
|
+
sage: WordMorphism('b->xyz').has_left_conjugate()
|
|
2177
|
+
True
|
|
2178
|
+
sage: WordMorphism('').has_left_conjugate()
|
|
2179
|
+
True
|
|
2180
|
+
sage: WordMorphism('a->,b->xyz').has_left_conjugate()
|
|
2181
|
+
True
|
|
2182
|
+
sage: WordMorphism('a->abbab,b->abb').has_left_conjugate()
|
|
2183
|
+
True
|
|
2184
|
+
sage: WordMorphism('a->abbab,b->abb,c->').has_left_conjugate()
|
|
2185
|
+
True
|
|
2186
|
+
"""
|
|
2187
|
+
I = (w for w in self.images() if not FiniteWord_class.is_empty(w))
|
|
2188
|
+
|
|
2189
|
+
try:
|
|
2190
|
+
letter = next(I)[0]
|
|
2191
|
+
except StopIteration:
|
|
2192
|
+
return True
|
|
2193
|
+
|
|
2194
|
+
# Compare the first letter of all the non empty images
|
|
2195
|
+
return all(image[0] == letter for image in I)
|
|
2196
|
+
|
|
2197
|
+
def has_right_conjugate(self) -> bool:
|
|
2198
|
+
r"""
|
|
2199
|
+
Return ``True`` if all the non empty images of ``self`` ends with the
|
|
2200
|
+
same letter.
|
|
2201
|
+
|
|
2202
|
+
EXAMPLES::
|
|
2203
|
+
|
|
2204
|
+
sage: m = WordMorphism('a->abcde,b->xyz')
|
|
2205
|
+
sage: m.has_right_conjugate()
|
|
2206
|
+
False
|
|
2207
|
+
sage: WordMorphism('b->xyz').has_right_conjugate()
|
|
2208
|
+
True
|
|
2209
|
+
sage: WordMorphism('').has_right_conjugate()
|
|
2210
|
+
True
|
|
2211
|
+
sage: WordMorphism('a->,b->xyz').has_right_conjugate()
|
|
2212
|
+
True
|
|
2213
|
+
sage: WordMorphism('a->abbab,b->abb').has_right_conjugate()
|
|
2214
|
+
True
|
|
2215
|
+
sage: WordMorphism('a->abbab,b->abb,c->').has_right_conjugate()
|
|
2216
|
+
True
|
|
2217
|
+
"""
|
|
2218
|
+
return self.reversal().has_left_conjugate()
|
|
2219
|
+
|
|
2220
|
+
def list_of_conjugates(self):
|
|
2221
|
+
r"""
|
|
2222
|
+
Return the list of all the conjugate morphisms of ``self``.
|
|
2223
|
+
|
|
2224
|
+
DEFINITION:
|
|
2225
|
+
|
|
2226
|
+
Recall from Lothaire [1] (Section 2.3.4)
|
|
2227
|
+
that `\varphi` is *right conjugate* of `\varphi'`,
|
|
2228
|
+
noted `\varphi\triangleleft\varphi'`, if there exists
|
|
2229
|
+
`u \in \Sigma^*` such that
|
|
2230
|
+
|
|
2231
|
+
.. MATH::
|
|
2232
|
+
|
|
2233
|
+
\varphi(\alpha)u = u\varphi'(\alpha),
|
|
2234
|
+
|
|
2235
|
+
for all `\alpha \in \Sigma`, or equivalently that
|
|
2236
|
+
`\varphi(x)u = u\varphi'(x)`, for all words `x \in \Sigma^*`.
|
|
2237
|
+
Clearly, this relation is not
|
|
2238
|
+
symmetric so that we say that two morphisms `\varphi` and
|
|
2239
|
+
`\varphi'` are *conjugate*, noted
|
|
2240
|
+
`\varphi\bowtie\varphi'`, if
|
|
2241
|
+
`\varphi\triangleleft\varphi'` or
|
|
2242
|
+
`\varphi'\triangleleft\varphi`. It is easy to see that
|
|
2243
|
+
conjugacy of morphisms is an equivalence relation.
|
|
2244
|
+
|
|
2245
|
+
REFERENCES:
|
|
2246
|
+
|
|
2247
|
+
- [1] M. Lothaire, Algebraic Combinatorics on words, Cambridge
|
|
2248
|
+
University Press, 2002.
|
|
2249
|
+
|
|
2250
|
+
EXAMPLES::
|
|
2251
|
+
|
|
2252
|
+
sage: m = WordMorphism('a->abbab,b->abb')
|
|
2253
|
+
sage: m.list_of_conjugates()
|
|
2254
|
+
[WordMorphism: a->babba, b->bab,
|
|
2255
|
+
WordMorphism: a->abbab, b->abb,
|
|
2256
|
+
WordMorphism: a->bbaba, b->bba,
|
|
2257
|
+
WordMorphism: a->babab, b->bab,
|
|
2258
|
+
WordMorphism: a->ababb, b->abb,
|
|
2259
|
+
WordMorphism: a->babba, b->bba,
|
|
2260
|
+
WordMorphism: a->abbab, b->bab]
|
|
2261
|
+
sage: m = WordMorphism('a->aaa,b->aa')
|
|
2262
|
+
sage: m.list_of_conjugates()
|
|
2263
|
+
[WordMorphism: a->aaa, b->aa]
|
|
2264
|
+
sage: WordMorphism('').list_of_conjugates()
|
|
2265
|
+
[WordMorphism: ]
|
|
2266
|
+
sage: m = WordMorphism('a->aba,b->aba')
|
|
2267
|
+
sage: m.list_of_conjugates()
|
|
2268
|
+
[WordMorphism: a->baa, b->baa,
|
|
2269
|
+
WordMorphism: a->aab, b->aab,
|
|
2270
|
+
WordMorphism: a->aba, b->aba]
|
|
2271
|
+
sage: m = WordMorphism('a->abb,b->abbab,c->')
|
|
2272
|
+
sage: m.list_of_conjugates()
|
|
2273
|
+
[WordMorphism: a->bab, b->babba, c->,
|
|
2274
|
+
WordMorphism: a->abb, b->abbab, c->,
|
|
2275
|
+
WordMorphism: a->bba, b->bbaba, c->,
|
|
2276
|
+
WordMorphism: a->bab, b->babab, c->,
|
|
2277
|
+
WordMorphism: a->abb, b->ababb, c->,
|
|
2278
|
+
WordMorphism: a->bba, b->babba, c->,
|
|
2279
|
+
WordMorphism: a->bab, b->abbab, c->]
|
|
2280
|
+
"""
|
|
2281
|
+
if self.is_empty():
|
|
2282
|
+
return [self]
|
|
2283
|
+
|
|
2284
|
+
# Build the list c of conjugate morphisms
|
|
2285
|
+
c = []
|
|
2286
|
+
m = self
|
|
2287
|
+
c.append(m)
|
|
2288
|
+
while m.has_left_conjugate():
|
|
2289
|
+
m = m.conjugate(1)
|
|
2290
|
+
if m == self:
|
|
2291
|
+
break
|
|
2292
|
+
c.append(m)
|
|
2293
|
+
m = self
|
|
2294
|
+
while m.has_right_conjugate():
|
|
2295
|
+
m = m.conjugate(-1)
|
|
2296
|
+
if m == self:
|
|
2297
|
+
break
|
|
2298
|
+
c.insert(0, m)
|
|
2299
|
+
|
|
2300
|
+
# Build the list d of distinct morphisms
|
|
2301
|
+
d = []
|
|
2302
|
+
for m in c:
|
|
2303
|
+
if m not in d:
|
|
2304
|
+
d.append(m)
|
|
2305
|
+
return d
|
|
2306
|
+
|
|
2307
|
+
def is_in_classP(self, f=None):
|
|
2308
|
+
r"""
|
|
2309
|
+
Return ``True`` if ``self`` is in class `P` (or `f`-`P`).
|
|
2310
|
+
|
|
2311
|
+
DEFINITION : Let `A` be an alphabet. We say that a
|
|
2312
|
+
primitive substitution `S` is in the *class P* if there
|
|
2313
|
+
exists a palindrome `p` and for each `b\in A` a
|
|
2314
|
+
palindrome `q_b` such that `S(b)=pq_b` for all
|
|
2315
|
+
`b\in A`. [1]
|
|
2316
|
+
|
|
2317
|
+
Let `f` be an involution on `A`. "We say that a morphism
|
|
2318
|
+
`\varphi` is in class `f`-`P` if there exists an
|
|
2319
|
+
`f`-palindrome `p` and for each `\alpha \in A`
|
|
2320
|
+
there exists an `f`-palindrome `q_\alpha` such
|
|
2321
|
+
that `\varphi(\alpha)=pq_\alpha`. [2]
|
|
2322
|
+
|
|
2323
|
+
INPUT:
|
|
2324
|
+
|
|
2325
|
+
- ``f`` -- involution (default: ``None``) on the alphabet of ``self``;
|
|
2326
|
+
it must be callable on letters as well as words (e.g. WordMorphism)
|
|
2327
|
+
|
|
2328
|
+
REFERENCES:
|
|
2329
|
+
|
|
2330
|
+
- [1] Hof, A., O. Knill et B. Simon, Singular continuous
|
|
2331
|
+
spectrum for palindromic Schrödinger operators,
|
|
2332
|
+
Commun. Math. Phys. 174 (1995) 149-159.
|
|
2333
|
+
|
|
2334
|
+
- [2] Labbe, Sebastien. Proprietes combinatoires des
|
|
2335
|
+
`f`-palindromes, Memoire de maitrise en Mathematiques,
|
|
2336
|
+
Montreal, UQAM, 2008, 109 pages.
|
|
2337
|
+
|
|
2338
|
+
EXAMPLES::
|
|
2339
|
+
|
|
2340
|
+
sage: WordMorphism('a->bbaba,b->bba').is_in_classP()
|
|
2341
|
+
True
|
|
2342
|
+
sage: tm = WordMorphism('a->ab,b->ba')
|
|
2343
|
+
sage: tm.is_in_classP()
|
|
2344
|
+
False
|
|
2345
|
+
sage: f = WordMorphism('a->b,b->a')
|
|
2346
|
+
sage: tm.is_in_classP(f=f)
|
|
2347
|
+
True
|
|
2348
|
+
sage: (tm^2).is_in_classP()
|
|
2349
|
+
True
|
|
2350
|
+
sage: (tm^2).is_in_classP(f=f)
|
|
2351
|
+
False
|
|
2352
|
+
sage: fibo = WordMorphism('a->ab,b->a')
|
|
2353
|
+
sage: fibo.is_in_classP()
|
|
2354
|
+
True
|
|
2355
|
+
sage: fibo.is_in_classP(f=f)
|
|
2356
|
+
False
|
|
2357
|
+
sage: (fibo^2).is_in_classP()
|
|
2358
|
+
False
|
|
2359
|
+
sage: f = WordMorphism('a->b,b->a,c->c')
|
|
2360
|
+
sage: WordMorphism('a->acbcc,b->acbab,c->acbba').is_in_classP(f)
|
|
2361
|
+
True
|
|
2362
|
+
"""
|
|
2363
|
+
if self.is_empty():
|
|
2364
|
+
return True
|
|
2365
|
+
|
|
2366
|
+
# Compute the longest common prefix of all the images of letters
|
|
2367
|
+
images = self.images()
|
|
2368
|
+
lcp = images[0]
|
|
2369
|
+
for image in images:
|
|
2370
|
+
lcp = lcp.longest_common_prefix(image)
|
|
2371
|
+
|
|
2372
|
+
# Find a common palindrome prefix
|
|
2373
|
+
for i in range(lcp.length() + 1):
|
|
2374
|
+
if lcp[:i].is_palindrome(f=f):
|
|
2375
|
+
|
|
2376
|
+
# If all the suffixes are palindromes,
|
|
2377
|
+
for image in images:
|
|
2378
|
+
if not image[i:].is_palindrome(f=f):
|
|
2379
|
+
break
|
|
2380
|
+
else:
|
|
2381
|
+
return True
|
|
2382
|
+
|
|
2383
|
+
return False
|
|
2384
|
+
|
|
2385
|
+
def has_conjugate_in_classP(self, f=None) -> bool:
|
|
2386
|
+
r"""
|
|
2387
|
+
Return ``True`` if ``self`` has a conjugate in class `f`-`P`.
|
|
2388
|
+
|
|
2389
|
+
DEFINITION : Let `A` be an alphabet. We say that a
|
|
2390
|
+
primitive substitution `S` is in the *class P* if there
|
|
2391
|
+
exists a palindrome `p` and for each `b\in A` a
|
|
2392
|
+
palindrome `q_b` such that `S(b)=pq_b` for all
|
|
2393
|
+
`b\in A`. [1]
|
|
2394
|
+
|
|
2395
|
+
Let `f` be an involution on `A`. We say that a morphism
|
|
2396
|
+
`\varphi` is in class `f`-`P` if there exists an
|
|
2397
|
+
`f`-palindrome `p` and for each `\alpha \in A`
|
|
2398
|
+
there exists an `f`-palindrome `q_\alpha` such
|
|
2399
|
+
that `\varphi(\alpha)=pq_\alpha`. [2]
|
|
2400
|
+
|
|
2401
|
+
INPUT:
|
|
2402
|
+
|
|
2403
|
+
- ``f`` -- involution (default: ``None``) on the alphabet of ``self``;
|
|
2404
|
+
it must be callable on letters as well as words (e.g. WordMorphism)
|
|
2405
|
+
|
|
2406
|
+
REFERENCES:
|
|
2407
|
+
|
|
2408
|
+
- [1] Hof, A., O. Knill et B. Simon, Singular continuous
|
|
2409
|
+
spectrum for palindromic Schrödinger operators,
|
|
2410
|
+
Commun. Math. Phys. 174 (1995) 149-159.
|
|
2411
|
+
|
|
2412
|
+
- [2] Labbé, Sébastien. Propriétés combinatoires des
|
|
2413
|
+
`f`-palindromes, Mémoire de maitrise en Mathématiques,
|
|
2414
|
+
Montréal, UQAM, 2008, 109 pages.
|
|
2415
|
+
|
|
2416
|
+
EXAMPLES::
|
|
2417
|
+
|
|
2418
|
+
sage: fibo = WordMorphism('a->ab,b->a')
|
|
2419
|
+
sage: fibo.has_conjugate_in_classP()
|
|
2420
|
+
True
|
|
2421
|
+
sage: (fibo^2).is_in_classP()
|
|
2422
|
+
False
|
|
2423
|
+
sage: (fibo^2).has_conjugate_in_classP()
|
|
2424
|
+
True
|
|
2425
|
+
"""
|
|
2426
|
+
for k in self.list_of_conjugates():
|
|
2427
|
+
if k.is_in_classP(f=f):
|
|
2428
|
+
return True
|
|
2429
|
+
return False
|
|
2430
|
+
|
|
2431
|
+
def dual_map(self, k=1):
|
|
2432
|
+
r"""
|
|
2433
|
+
Return the dual map `E_k^*` of ``self`` (see [1]).
|
|
2434
|
+
|
|
2435
|
+
.. NOTE::
|
|
2436
|
+
|
|
2437
|
+
It is actually implemented only for `k=1`.
|
|
2438
|
+
|
|
2439
|
+
INPUT:
|
|
2440
|
+
|
|
2441
|
+
- ``self`` -- unimodular endomorphism defined on integers
|
|
2442
|
+
``1, 2, \ldots, d``
|
|
2443
|
+
- ``k`` -- integer (default: 1)
|
|
2444
|
+
|
|
2445
|
+
OUTPUT: an instance of E1Star - the dual map
|
|
2446
|
+
|
|
2447
|
+
EXAMPLES::
|
|
2448
|
+
|
|
2449
|
+
sage: sigma = WordMorphism({1: [2], 2: [3], 3: [1,2]})
|
|
2450
|
+
sage: sigma.dual_map() # needs sage.modules
|
|
2451
|
+
E_1^*(1->2, 2->3, 3->12)
|
|
2452
|
+
|
|
2453
|
+
::
|
|
2454
|
+
|
|
2455
|
+
sage: sigma.dual_map(k=2)
|
|
2456
|
+
Traceback (most recent call last):
|
|
2457
|
+
...
|
|
2458
|
+
NotImplementedError: the dual map E_k^* is implemented only for k = 1 (not 2)
|
|
2459
|
+
|
|
2460
|
+
REFERENCES:
|
|
2461
|
+
|
|
2462
|
+
- [1] Sano, Y., Arnoux, P. and Ito, S., Higher dimensional
|
|
2463
|
+
extensions of substitutions and their dual maps, Journal
|
|
2464
|
+
d'Analyse Mathématique 83 (2001), 183-206.
|
|
2465
|
+
"""
|
|
2466
|
+
if k == 1:
|
|
2467
|
+
from sage.combinat.e_one_star import E1Star
|
|
2468
|
+
return E1Star(self)
|
|
2469
|
+
|
|
2470
|
+
raise NotImplementedError("the dual map E_k^* is implemented only "
|
|
2471
|
+
"for k = 1 (not %s)" % k)
|
|
2472
|
+
|
|
2473
|
+
@cached_method
|
|
2474
|
+
def rauzy_fractal_projection(self, eig=None, prec=53):
|
|
2475
|
+
r"""
|
|
2476
|
+
Return a dictionary giving the projection of the canonical basis.
|
|
2477
|
+
|
|
2478
|
+
See the method :meth:`rauzy_fractal_plot` for more details about the projection.
|
|
2479
|
+
|
|
2480
|
+
INPUT:
|
|
2481
|
+
|
|
2482
|
+
- ``eig`` -- a real element of ``QQbar`` of degree >= 2 (default: ``None``).
|
|
2483
|
+
The eigenvalue used for the projection.
|
|
2484
|
+
It must be an eigenvalue of ``self.incidence_matrix()``.
|
|
2485
|
+
The one used by default is the maximal eigenvalue of
|
|
2486
|
+
``self.incidence_matrix()`` (usually a Pisot number),
|
|
2487
|
+
but for substitutions with more than 3 letters
|
|
2488
|
+
other interesting choices are sometimes possible.
|
|
2489
|
+
|
|
2490
|
+
- ``prec`` -- integer (default: 53);
|
|
2491
|
+
the number of bits used in the floating point representations
|
|
2492
|
+
of the coordinates
|
|
2493
|
+
|
|
2494
|
+
OUTPUT:
|
|
2495
|
+
|
|
2496
|
+
dictionary, letter -> vector, giving the projection
|
|
2497
|
+
|
|
2498
|
+
EXAMPLES:
|
|
2499
|
+
|
|
2500
|
+
The projection for the Rauzy fractal of the Tribonacci substitution
|
|
2501
|
+
is::
|
|
2502
|
+
|
|
2503
|
+
sage: s = WordMorphism('1->12,2->13,3->1')
|
|
2504
|
+
sage: s.rauzy_fractal_projection() # needs sage.modules sage.rings.number_field
|
|
2505
|
+
{'1': (1.00000000000000, 0.000000000000000),
|
|
2506
|
+
'2': (-1.41964337760708, -0.606290729207199),
|
|
2507
|
+
'3': (-0.771844506346038, 1.11514250803994)}
|
|
2508
|
+
|
|
2509
|
+
TESTS::
|
|
2510
|
+
|
|
2511
|
+
sage: t = WordMorphism('1->12,2->3,3->45,4->5,5->6,6->7,7->8,8->1')
|
|
2512
|
+
sage: E = t.incidence_matrix().eigenvalues() # needs sage.modules sage.rings.number_field
|
|
2513
|
+
sage: x = [x for x in E if -0.8 < x < -0.7][0] # needs sage.modules sage.rings.number_field
|
|
2514
|
+
sage: t.rauzy_fractal_projection(prec=10) # needs sage.modules sage.rings.number_field
|
|
2515
|
+
{'1': (1.0, 0.00),
|
|
2516
|
+
'2': (-1.7, -0.56),
|
|
2517
|
+
'3': (0.79, 1.3),
|
|
2518
|
+
'4': (1.9, -0.74),
|
|
2519
|
+
'5': (-1.7, -0.56),
|
|
2520
|
+
'6': (0.79, 1.3),
|
|
2521
|
+
'7': (0.21, -1.3),
|
|
2522
|
+
'8': (-0.88, 0.74)}
|
|
2523
|
+
sage: t.rauzy_fractal_projection(eig=x, prec=10) # needs sage.modules sage.rings.number_field
|
|
2524
|
+
{'1': (1.0, 0.00),
|
|
2525
|
+
'2': (-0.12, -0.74),
|
|
2526
|
+
'3': (-0.66, -0.56),
|
|
2527
|
+
'4': (-0.46, -0.18),
|
|
2528
|
+
'5': (-0.54, 0.18),
|
|
2529
|
+
'6': (-0.34, 0.56),
|
|
2530
|
+
'7': (0.12, 0.74),
|
|
2531
|
+
'8': (0.66, 0.56)}
|
|
2532
|
+
|
|
2533
|
+
AUTHOR:
|
|
2534
|
+
|
|
2535
|
+
Timo Jolivet (2012-06-16)
|
|
2536
|
+
"""
|
|
2537
|
+
alphabet = self.domain().alphabet()
|
|
2538
|
+
size_alphabet = len(alphabet)
|
|
2539
|
+
|
|
2540
|
+
# Eigenvalues
|
|
2541
|
+
if eig is None:
|
|
2542
|
+
beta = max(self.incidence_matrix().eigenvalues(), key=abs)
|
|
2543
|
+
else:
|
|
2544
|
+
beta = eig
|
|
2545
|
+
|
|
2546
|
+
# Test is deg(beta) >= 2
|
|
2547
|
+
if beta.degree() < 2:
|
|
2548
|
+
raise ValueError("the algebraic degree of ``eig`` must be at least two")
|
|
2549
|
+
|
|
2550
|
+
# Algebraic conjugates of beta
|
|
2551
|
+
from sage.rings.qqbar import QQbar
|
|
2552
|
+
beta_conjugates = beta.minpoly().roots(QQbar, multiplicities=False)
|
|
2553
|
+
if not beta.imag():
|
|
2554
|
+
beta_conjugates.remove(beta)
|
|
2555
|
+
for x in beta_conjugates:
|
|
2556
|
+
if x.imag():
|
|
2557
|
+
beta_conjugates.remove(x.conjugate())
|
|
2558
|
+
|
|
2559
|
+
# Left eigenvector vb in the number field Q(beta)
|
|
2560
|
+
from sage.rings.number_field.number_field import NumberField
|
|
2561
|
+
K = NumberField(beta.minpoly(), 'b')
|
|
2562
|
+
vb = (self.incidence_matrix() - K.gen()).kernel().basis()[0]
|
|
2563
|
+
|
|
2564
|
+
# Projections of canonical base vectors from R^size_alphabet to C, using vb
|
|
2565
|
+
from sage.modules.free_module import VectorSpace
|
|
2566
|
+
canonical_basis = VectorSpace(K, size_alphabet).basis()
|
|
2567
|
+
canonical_basis_proj = {}
|
|
2568
|
+
|
|
2569
|
+
from sage.rings.real_mpfr import RealField
|
|
2570
|
+
RealField_prec = RealField(prec)
|
|
2571
|
+
for a, x in zip(alphabet, canonical_basis):
|
|
2572
|
+
v = []
|
|
2573
|
+
for y in beta_conjugates:
|
|
2574
|
+
# if y has nonzero imaginary part
|
|
2575
|
+
if y.imag():
|
|
2576
|
+
z = (vb * x).lift()(y)
|
|
2577
|
+
z1, z2 = z.real(), z.imag()
|
|
2578
|
+
v += [RealField_prec(z1), RealField_prec(z2)]
|
|
2579
|
+
# if y is real
|
|
2580
|
+
else:
|
|
2581
|
+
z = (vb * x).lift()(y)
|
|
2582
|
+
v += [RealField_prec(z)]
|
|
2583
|
+
canonical_basis_proj[a] = vector(v)
|
|
2584
|
+
|
|
2585
|
+
return canonical_basis_proj
|
|
2586
|
+
|
|
2587
|
+
def rauzy_fractal_points(self, n=None, exchange=False, eig=None, translate=None, prec=53):
|
|
2588
|
+
r"""
|
|
2589
|
+
Return a dictionary of list of points associated with the pieces
|
|
2590
|
+
of the Rauzy fractal of ``self``.
|
|
2591
|
+
|
|
2592
|
+
INPUT:
|
|
2593
|
+
|
|
2594
|
+
See the method :meth:`rauzy_fractal_plot` for a description
|
|
2595
|
+
of the options and more examples.
|
|
2596
|
+
|
|
2597
|
+
OUTPUT: dictionary of list of points
|
|
2598
|
+
|
|
2599
|
+
EXAMPLES:
|
|
2600
|
+
|
|
2601
|
+
The Rauzy fractal of the Tribonacci substitution and the number of
|
|
2602
|
+
points in the piece of the fractal associated with ``'1'``, ``'2'``
|
|
2603
|
+
and ``'3'`` are respectively::
|
|
2604
|
+
|
|
2605
|
+
sage: # needs sage.modules sage.rings.number_field
|
|
2606
|
+
sage: s = WordMorphism('1->12,2->13,3->1')
|
|
2607
|
+
sage: D = s.rauzy_fractal_points(n=100)
|
|
2608
|
+
sage: len(D['1'])
|
|
2609
|
+
54
|
|
2610
|
+
sage: len(D['2'])
|
|
2611
|
+
30
|
|
2612
|
+
sage: len(D['3'])
|
|
2613
|
+
16
|
|
2614
|
+
|
|
2615
|
+
TESTS::
|
|
2616
|
+
|
|
2617
|
+
sage: # needs sage.modules sage.rings.number_field
|
|
2618
|
+
sage: s = WordMorphism('1->12,2->13,3->1')
|
|
2619
|
+
sage: D = s.rauzy_fractal_points(n=100, exchange=True,
|
|
2620
|
+
....: translate=[(3,1,-2), (5,-33,8)], prec=40)
|
|
2621
|
+
sage: len(D['1'])
|
|
2622
|
+
108
|
|
2623
|
+
|
|
2624
|
+
AUTHOR:
|
|
2625
|
+
|
|
2626
|
+
Timo Jolivet (2012-06-16)
|
|
2627
|
+
"""
|
|
2628
|
+
alphabet = self.domain().alphabet()
|
|
2629
|
+
canonical_basis_proj = self.rauzy_fractal_projection(eig=eig, prec=prec)
|
|
2630
|
+
|
|
2631
|
+
# if exchange, set the projection to its opposite
|
|
2632
|
+
if exchange:
|
|
2633
|
+
for a in canonical_basis_proj:
|
|
2634
|
+
canonical_basis_proj[a] = - canonical_basis_proj[a]
|
|
2635
|
+
|
|
2636
|
+
# Compute a fixed point u
|
|
2637
|
+
if exchange:
|
|
2638
|
+
u = iter(self.reversal().periodic_points()[0][0])
|
|
2639
|
+
else:
|
|
2640
|
+
u = iter(self.periodic_points()[0][0])
|
|
2641
|
+
|
|
2642
|
+
# Manage various options in function of dimension
|
|
2643
|
+
if n is None:
|
|
2644
|
+
dim_fractal = len(canonical_basis_proj[alphabet[0]])
|
|
2645
|
+
if dim_fractal == 1:
|
|
2646
|
+
n = 1000
|
|
2647
|
+
elif dim_fractal == 2:
|
|
2648
|
+
n = 50000
|
|
2649
|
+
elif dim_fractal == 3:
|
|
2650
|
+
n = 5000
|
|
2651
|
+
else:
|
|
2652
|
+
n = 50000
|
|
2653
|
+
|
|
2654
|
+
# Compute orbit points to plot
|
|
2655
|
+
S = 0
|
|
2656
|
+
orbit_points = {a: [] for a in alphabet}
|
|
2657
|
+
for _ in range(n):
|
|
2658
|
+
a = next(u)
|
|
2659
|
+
S += canonical_basis_proj[a]
|
|
2660
|
+
orbit_points[a].append(S)
|
|
2661
|
+
|
|
2662
|
+
# Manage translated copies
|
|
2663
|
+
from sage.rings.real_mpfr import RealField
|
|
2664
|
+
RealField_prec = RealField(prec)
|
|
2665
|
+
if translate is not None:
|
|
2666
|
+
|
|
2667
|
+
if isinstance(translate, dict):
|
|
2668
|
+
for a in translate:
|
|
2669
|
+
translate[a] = [vector(RealField_prec, v) for v in translate[a]]
|
|
2670
|
+
|
|
2671
|
+
else:
|
|
2672
|
+
translate = [vector(RealField_prec, v) for v in translate]
|
|
2673
|
+
|
|
2674
|
+
for a in alphabet:
|
|
2675
|
+
translated_copies = {i: [] for i in alphabet}
|
|
2676
|
+
|
|
2677
|
+
if isinstance(translate, list):
|
|
2678
|
+
to_treat = translate
|
|
2679
|
+
|
|
2680
|
+
elif isinstance(translate, dict):
|
|
2681
|
+
try:
|
|
2682
|
+
to_treat = translate[a]
|
|
2683
|
+
except KeyError:
|
|
2684
|
+
to_treat = []
|
|
2685
|
+
|
|
2686
|
+
for x in to_treat:
|
|
2687
|
+
v = 0
|
|
2688
|
+
for i, z in zip(alphabet, x):
|
|
2689
|
+
v += z * canonical_basis_proj[i]
|
|
2690
|
+
translated_copies[a] += [vector(v) + w for w in orbit_points[a]]
|
|
2691
|
+
|
|
2692
|
+
orbit_points[a] = translated_copies[a]
|
|
2693
|
+
|
|
2694
|
+
return orbit_points
|
|
2695
|
+
|
|
2696
|
+
def rauzy_fractal_plot(self, n=None, exchange=False, eig=None,
|
|
2697
|
+
translate=None, prec=53,
|
|
2698
|
+
colormap='hsv', opacity=None, plot_origin=None,
|
|
2699
|
+
plot_basis=False, point_size=None):
|
|
2700
|
+
r"""
|
|
2701
|
+
Return a plot of the Rauzy fractal associated with a substitution.
|
|
2702
|
+
|
|
2703
|
+
The substitution does not have to be irreducible.
|
|
2704
|
+
The usual definition of a Rauzy fractal requires that
|
|
2705
|
+
its dominant eigenvalue is a Pisot number but the present method
|
|
2706
|
+
doesn't require this, allowing to plot some interesting pictures
|
|
2707
|
+
in the non-Pisot case (see the examples below).
|
|
2708
|
+
|
|
2709
|
+
For more details about the definition of the fractal and the
|
|
2710
|
+
projection which is used, see Section 3.1 of [1].
|
|
2711
|
+
|
|
2712
|
+
Plots with less than 100,000 points take a few seconds,
|
|
2713
|
+
and several millions of points can be plotted in reasonable time.
|
|
2714
|
+
|
|
2715
|
+
Other ways to draw Rauzy fractals (and more generally projections of paths)
|
|
2716
|
+
can be found in :meth:`sage.combinat.words.paths.FiniteWordPath_all.plot_projection`
|
|
2717
|
+
or in :meth:`sage.combinat.e_one_star`.
|
|
2718
|
+
|
|
2719
|
+
OUTPUT: a Graphics object
|
|
2720
|
+
|
|
2721
|
+
INPUT:
|
|
2722
|
+
|
|
2723
|
+
- ``n`` -- integer (default: ``None``)
|
|
2724
|
+
The number of points used to plot the fractal.
|
|
2725
|
+
Default values: ``1000`` for a 1D fractal,
|
|
2726
|
+
``50000`` for a 2D fractal, ``10000`` for a 3D fractal.
|
|
2727
|
+
|
|
2728
|
+
- ``exchange`` -- boolean (default: ``False``); plot the Rauzy fractal
|
|
2729
|
+
with domain exchange
|
|
2730
|
+
|
|
2731
|
+
- ``eig`` -- a real element of ``QQbar`` of degree >= 2 (default: ``None``);
|
|
2732
|
+
the eigenvalue used to plot the fractal.
|
|
2733
|
+
It must be an eigenvalue of ``self.incidence_matrix()``.
|
|
2734
|
+
The one used by default the maximal eigenvalue of
|
|
2735
|
+
``self.incidence_matrix()`` (usually a Pisot number),
|
|
2736
|
+
but for substitutions with more than 3 letters
|
|
2737
|
+
other interesting choices are sometimes possible.
|
|
2738
|
+
|
|
2739
|
+
- ``translate`` -- list of vectors of ``RR^size_alphabet``,
|
|
2740
|
+
or a dictionary from the alphabet to lists of vectors (default: ``None``).
|
|
2741
|
+
Plot translated copies of the fractal.
|
|
2742
|
+
This option allows to plot tilings easily.
|
|
2743
|
+
The projection used for these vectors is the same as
|
|
2744
|
+
the projection used for the canonical basis to plot the fractal.
|
|
2745
|
+
If the input is a list, all the pieces will be translated and plotted.
|
|
2746
|
+
If the input is a dictionary, each piece will be translated and plotted
|
|
2747
|
+
accordingly to the vectors associated with each letter in the dictionary.
|
|
2748
|
+
Note: by default, the Rauzy fractal placed at the origin
|
|
2749
|
+
is not plotted with the ``translate`` option;
|
|
2750
|
+
the vector ``(0,0,...,0)`` has to be added manually.
|
|
2751
|
+
|
|
2752
|
+
- ``prec`` -- integer (default: 53);
|
|
2753
|
+
the number of bits used in the floating point representations
|
|
2754
|
+
of the points of the fractal
|
|
2755
|
+
|
|
2756
|
+
- ``colormap`` -- color map or dictionary (default: ``'hsv'``).
|
|
2757
|
+
It can be one of the following:
|
|
2758
|
+
|
|
2759
|
+
- ``string`` -- a coloring map. For available coloring map names type:
|
|
2760
|
+
``sorted(colormaps)``
|
|
2761
|
+
|
|
2762
|
+
- ``dict`` -- dictionary of the alphabet mapped to colors
|
|
2763
|
+
|
|
2764
|
+
- ``opacity`` -- dictionary from the alphabet to the real interval
|
|
2765
|
+
[0,1] (default: ``None``); if none is specified, all letters are
|
|
2766
|
+
plotted with opacity ``1``
|
|
2767
|
+
|
|
2768
|
+
- ``plot_origin`` -- a couple ``(k,c)`` (default: ``None``);
|
|
2769
|
+
if specified, mark the origin by a point of size ``k`` and color ``c``
|
|
2770
|
+
|
|
2771
|
+
- ``plot_basis`` -- boolean (default: ``False``); plot the projection
|
|
2772
|
+
of the canonical basis with the fractal
|
|
2773
|
+
|
|
2774
|
+
- ``point_size`` -- float (default: ``None``); the size of the points
|
|
2775
|
+
used to plot the fractal
|
|
2776
|
+
|
|
2777
|
+
EXAMPLES:
|
|
2778
|
+
|
|
2779
|
+
#. The Rauzy fractal of the Tribonacci substitution::
|
|
2780
|
+
|
|
2781
|
+
sage: s = WordMorphism('1->12,2->13,3->1')
|
|
2782
|
+
sage: s.rauzy_fractal_plot() # long time # needs sage.plot
|
|
2783
|
+
Graphics object consisting of 3 graphics primitives
|
|
2784
|
+
|
|
2785
|
+
#. The "Hokkaido" fractal. We tweak the plot using the plotting options
|
|
2786
|
+
to get a nice reusable picture, in which we mark the origin by a black dot::
|
|
2787
|
+
|
|
2788
|
+
sage: s = WordMorphism('a->ab,b->c,c->d,d->e,e->a')
|
|
2789
|
+
sage: G = s.rauzy_fractal_plot(n=100000, point_size=3, # not tested
|
|
2790
|
+
....: plot_origin=(50,"black"))
|
|
2791
|
+
sage: G.show(figsize=10, axes=false) # not tested
|
|
2792
|
+
|
|
2793
|
+
#. Another "Hokkaido" fractal and its domain exchange::
|
|
2794
|
+
|
|
2795
|
+
sage: s = WordMorphism({1:[2], 2:[4,3], 3:[4], 4:[5,3], 5:[6], 6:[1]})
|
|
2796
|
+
sage: s.rauzy_fractal_plot() # not tested (> 1 second)
|
|
2797
|
+
sage: s.rauzy_fractal_plot(exchange=True) # not tested (> 1 second)
|
|
2798
|
+
|
|
2799
|
+
#. A three-dimensional Rauzy fractal::
|
|
2800
|
+
|
|
2801
|
+
sage: s = WordMorphism('1->12,2->13,3->14,4->1')
|
|
2802
|
+
sage: s.rauzy_fractal_plot() # not tested (> 1 second)
|
|
2803
|
+
|
|
2804
|
+
#. A one-dimensional Rauzy fractal (very scattered)::
|
|
2805
|
+
|
|
2806
|
+
sage: s = WordMorphism('1->2122,2->1')
|
|
2807
|
+
sage: s.rauzy_fractal_plot().show(figsize=20) # not tested (> 1 second)
|
|
2808
|
+
|
|
2809
|
+
#. A high resolution plot of a complicated fractal::
|
|
2810
|
+
|
|
2811
|
+
sage: s = WordMorphism('1->23,2->123,3->1122233')
|
|
2812
|
+
sage: G = s.rauzy_fractal_plot(n=300000) # not tested (> 1 second)
|
|
2813
|
+
sage: G.show(axes=false, figsize=20) # not tested (> 1 second)
|
|
2814
|
+
|
|
2815
|
+
#. A nice colorful animation of a domain exchange::
|
|
2816
|
+
|
|
2817
|
+
sage: s = WordMorphism('1->21,2->3,3->4,4->25,5->6,6->7,7->1')
|
|
2818
|
+
sage: L = [s.rauzy_fractal_plot(), # not tested (> 1 second)
|
|
2819
|
+
....: s.rauzy_fractal_plot(exchange=True)]
|
|
2820
|
+
sage: animate(L, axes=false).show(delay=100) # not tested (> 1 second)
|
|
2821
|
+
|
|
2822
|
+
#. Plotting with only one color::
|
|
2823
|
+
|
|
2824
|
+
sage: s = WordMorphism('1->12,2->31,3->1')
|
|
2825
|
+
sage: cm = {'1':'black', '2':'black', '3':'black'}
|
|
2826
|
+
sage: s.rauzy_fractal_plot(colormap=cm) # not tested (> 1 second)
|
|
2827
|
+
|
|
2828
|
+
#. Different fractals can be obtained by choosing another (non-Pisot) eigenvalue::
|
|
2829
|
+
|
|
2830
|
+
sage: s = WordMorphism('1->12,2->3,3->45,4->5,5->6,6->7,7->8,8->1')
|
|
2831
|
+
sage: E = s.incidence_matrix().eigenvalues() # needs sage.modules sage.rings.number_field
|
|
2832
|
+
sage: x = [x for x in E if -0.8 < x < -0.7][0] # needs sage.modules sage.rings.number_field
|
|
2833
|
+
sage: s.rauzy_fractal_plot() # not tested (> 1 second)
|
|
2834
|
+
sage: s.rauzy_fractal_plot(eig=x) # not tested (> 1 second)
|
|
2835
|
+
|
|
2836
|
+
#. A Pisot reducible substitution with seemingly overlapping tiles::
|
|
2837
|
+
|
|
2838
|
+
sage: s = WordMorphism({1:[1,2], 2:[2,3], 3:[4], 4:[5], 5:[6],
|
|
2839
|
+
....: 6:[7], 7:[8], 8:[9], 9:[10], 10:[1]})
|
|
2840
|
+
sage: s.rauzy_fractal_plot() # not tested (> 1 second)
|
|
2841
|
+
|
|
2842
|
+
#. A non-Pisot reducible substitution with a strange Rauzy fractal::
|
|
2843
|
+
|
|
2844
|
+
sage: s = WordMorphism({1:[3,2], 2:[3,3], 3:[4], 4:[1]})
|
|
2845
|
+
sage: s.rauzy_fractal_plot() # not tested (> 1 second)
|
|
2846
|
+
|
|
2847
|
+
#. A substitution with overlapping tiles. We use the options
|
|
2848
|
+
``colormap`` and ``opacity`` to study how the tiles overlap::
|
|
2849
|
+
|
|
2850
|
+
sage: s = WordMorphism('1->213,2->4,3->5,4->1,5->21')
|
|
2851
|
+
sage: s.rauzy_fractal_plot() # not tested (> 1 second)
|
|
2852
|
+
sage: s.rauzy_fractal_plot(colormap={'1':'red', '4':'purple'}) # not tested (> 1 second)
|
|
2853
|
+
sage: s.rauzy_fractal_plot(n=150000, # not tested (> 1 second)
|
|
2854
|
+
....: opacity={'1':0.1,'2':1,'3':0.1,'4':0.1,'5':0.1})
|
|
2855
|
+
|
|
2856
|
+
#. Funny experiments by playing with the precision of the float numbers used to plot the fractal::
|
|
2857
|
+
|
|
2858
|
+
sage: s = WordMorphism('1->12,2->13,3->1')
|
|
2859
|
+
sage: s.rauzy_fractal_plot(prec=6) # not tested
|
|
2860
|
+
sage: s.rauzy_fractal_plot(prec=9) # not tested
|
|
2861
|
+
sage: s.rauzy_fractal_plot(prec=15) # not tested
|
|
2862
|
+
sage: s.rauzy_fractal_plot(prec=19) # not tested
|
|
2863
|
+
sage: s.rauzy_fractal_plot(prec=25) # not tested
|
|
2864
|
+
|
|
2865
|
+
#. Using the ``translate`` option to plot periodic tilings::
|
|
2866
|
+
|
|
2867
|
+
sage: s = WordMorphism('1->12,2->13,3->1')
|
|
2868
|
+
sage: s.rauzy_fractal_plot(n=10000, # not tested (> 1 second)
|
|
2869
|
+
....: translate=[(0,0,0),(-1,0,1),(0,-1,1),(1,-1,0),
|
|
2870
|
+
....: (1,0,-1),(0,1,-1),(-1,1,0)])
|
|
2871
|
+
|
|
2872
|
+
::
|
|
2873
|
+
|
|
2874
|
+
sage: t = WordMorphism("a->aC,b->d,C->de,d->a,e->ab") # substitution found by Julien Bernat
|
|
2875
|
+
sage: V = [vector((0,0,1,0,-1)), vector((0,0,1,-1,0))] # needs sage.modules
|
|
2876
|
+
sage: S = set(map(tuple, [i*V[0] + j*V[1] # needs sage.modules
|
|
2877
|
+
....: for i in [-1,0,1] for j in [-1,0,1]]))
|
|
2878
|
+
sage: t.rauzy_fractal_plot(n=10000, # not tested (> 1 second)
|
|
2879
|
+
....: translate=S, exchange=true)
|
|
2880
|
+
|
|
2881
|
+
#. Using the ``translate`` option to plot arbitrary tilings with the fractal pieces.
|
|
2882
|
+
This can be used for example to plot the self-replicating tiling of the Rauzy fractal::
|
|
2883
|
+
|
|
2884
|
+
sage: s = WordMorphism({1:[1,2], 2:[3], 3:[4,3], 4:[5], 5:[6], 6:[1]})
|
|
2885
|
+
sage: s.rauzy_fractal_plot() # not tested (> 1 second)
|
|
2886
|
+
sage: D = {1: [(0,0,0,0,0,0), (0,1,0,0,0,0)],
|
|
2887
|
+
....: 3: [(0,0,0,0,0,0), (0,1,0,0,0,0)], 6: [(0,1,0,0,0,0)]}
|
|
2888
|
+
sage: s.rauzy_fractal_plot(n=30000, translate=D) # not tested (> 1 second)
|
|
2889
|
+
|
|
2890
|
+
#. Plot the projection of the canonical basis with the fractal::
|
|
2891
|
+
|
|
2892
|
+
sage: s = WordMorphism({1:[2,1], 2:[3], 3:[6,4], 4:[5,1],
|
|
2893
|
+
....: 5:[6], 6:[7], 7:[8], 8:[9], 9:[1]})
|
|
2894
|
+
sage: s.rauzy_fractal_plot(plot_basis=True) # not tested (> 1 second)
|
|
2895
|
+
|
|
2896
|
+
TESTS::
|
|
2897
|
+
|
|
2898
|
+
sage: s = WordMorphism('a->ab,b->c,c->d,d->e,e->a')
|
|
2899
|
+
sage: s.rauzy_fractal_plot(n=1000, colormap='Set1', # needs sage.modules sage.plot
|
|
2900
|
+
....: opacity={'a':0.5,'b':1,'c':0.7,'d':0,'e':0.2},
|
|
2901
|
+
....: plot_origin=(100,"black"), plot_basis=True,
|
|
2902
|
+
....: point_size=2.5)
|
|
2903
|
+
Graphics object consisting of 10 graphics primitives
|
|
2904
|
+
|
|
2905
|
+
REFERENCES:
|
|
2906
|
+
|
|
2907
|
+
- [1] Valerie Berthe and Anne Siegel,
|
|
2908
|
+
Tilings associated with beta-numeration and substitutions,
|
|
2909
|
+
Integers 5 (3), 2005.
|
|
2910
|
+
http://www.integers-ejcnt.org/vol5-3.html
|
|
2911
|
+
|
|
2912
|
+
AUTHOR:
|
|
2913
|
+
|
|
2914
|
+
Timo Jolivet (2012-06-16)
|
|
2915
|
+
"""
|
|
2916
|
+
alphabet = self.domain().alphabet()
|
|
2917
|
+
size_alphabet = len(alphabet)
|
|
2918
|
+
|
|
2919
|
+
orbit_points = self.rauzy_fractal_points(n=n, exchange=exchange, eig=eig, translate=translate, prec=prec)
|
|
2920
|
+
|
|
2921
|
+
dim_fractal = len(orbit_points[alphabet[0]][0])
|
|
2922
|
+
|
|
2923
|
+
# Manage colors and opacity
|
|
2924
|
+
if isinstance(colormap, dict):
|
|
2925
|
+
col_dict = colormap
|
|
2926
|
+
|
|
2927
|
+
elif isinstance(colormap, str):
|
|
2928
|
+
from matplotlib import cm
|
|
2929
|
+
|
|
2930
|
+
if colormap not in cm.datad:
|
|
2931
|
+
raise RuntimeError("color map %s not known (type sorted(colors) for valid names)" % colormap)
|
|
2932
|
+
|
|
2933
|
+
colormap = cm.__dict__[colormap]
|
|
2934
|
+
col_dict = {}
|
|
2935
|
+
for i, a in enumerate(alphabet):
|
|
2936
|
+
col_dict[a] = colormap(float(i) / float(size_alphabet))[:3]
|
|
2937
|
+
|
|
2938
|
+
else:
|
|
2939
|
+
raise TypeError("type of option colormap (=%s) must be dict or str" % colormap)
|
|
2940
|
+
|
|
2941
|
+
if opacity is None:
|
|
2942
|
+
opacity = {a: 1 for a in alphabet}
|
|
2943
|
+
|
|
2944
|
+
elif not isinstance(opacity, dict):
|
|
2945
|
+
raise TypeError("type of option opacity (=%s) must be dict" % opacity)
|
|
2946
|
+
|
|
2947
|
+
# Plot points size
|
|
2948
|
+
if point_size is None:
|
|
2949
|
+
if dim_fractal == 1 or dim_fractal == 2:
|
|
2950
|
+
point_size = 1
|
|
2951
|
+
elif dim_fractal == 3:
|
|
2952
|
+
point_size = 8
|
|
2953
|
+
|
|
2954
|
+
# Make graphics
|
|
2955
|
+
from sage.plot.plot import Graphics
|
|
2956
|
+
G = Graphics()
|
|
2957
|
+
|
|
2958
|
+
from sage.plot.point import points
|
|
2959
|
+
|
|
2960
|
+
# 1D plots
|
|
2961
|
+
if dim_fractal == 1:
|
|
2962
|
+
from sage.plot.plot import plot
|
|
2963
|
+
for a in col_dict:
|
|
2964
|
+
# We plot only the points with a color in col_dict and with positive opacity
|
|
2965
|
+
if (a in col_dict) and (opacity[a] > 0):
|
|
2966
|
+
G += plot([x[0] for x in orbit_points[a]], color=col_dict[a], alpha=opacity[a], thickness=point_size)
|
|
2967
|
+
if plot_basis:
|
|
2968
|
+
from matplotlib import cm
|
|
2969
|
+
from sage.plot.arrow import arrow
|
|
2970
|
+
canonical_basis_proj = self.rauzy_fractal_projection(eig=eig, prec=prec)
|
|
2971
|
+
for i, a in enumerate(alphabet):
|
|
2972
|
+
x = canonical_basis_proj[a]
|
|
2973
|
+
G += arrow((-1.1, 0), (-1.1, x[0]),
|
|
2974
|
+
color=cm.__dict__["gist_gray"](0.75 * float(i) / float(size_alphabet))[:3])
|
|
2975
|
+
|
|
2976
|
+
# 2D or 3D plots
|
|
2977
|
+
else:
|
|
2978
|
+
if point_size is None and dim_fractal == 2:
|
|
2979
|
+
point_size = 1
|
|
2980
|
+
elif point_size is None and dim_fractal == 3:
|
|
2981
|
+
point_size = 8
|
|
2982
|
+
|
|
2983
|
+
for a in col_dict:
|
|
2984
|
+
# We plot only the points with a color in col_dict and with positive opacity
|
|
2985
|
+
if (a in col_dict) and (opacity[a] > 0):
|
|
2986
|
+
G += points(orbit_points[a], color=col_dict[a], alpha=opacity[a], size=point_size)
|
|
2987
|
+
|
|
2988
|
+
if plot_basis:
|
|
2989
|
+
from matplotlib import cm
|
|
2990
|
+
from sage.plot.arrow import arrow
|
|
2991
|
+
canonical_basis_proj = self.rauzy_fractal_projection(eig=eig, prec=prec)
|
|
2992
|
+
for i, a in enumerate(alphabet):
|
|
2993
|
+
x = canonical_basis_proj[a]
|
|
2994
|
+
G += arrow([0] * dim_fractal, x,
|
|
2995
|
+
color=cm.__dict__["gist_gray"](0.75 * float(i) / float(size_alphabet))[:3])
|
|
2996
|
+
|
|
2997
|
+
if plot_origin:
|
|
2998
|
+
G += points([(0, 0)], size=plot_origin[0], color=plot_origin[1])
|
|
2999
|
+
|
|
3000
|
+
if dim_fractal == 1 or dim_fractal == 2:
|
|
3001
|
+
G.set_aspect_ratio(1)
|
|
3002
|
+
|
|
3003
|
+
return G
|
|
3004
|
+
|
|
3005
|
+
def is_growing(self, letter=None):
|
|
3006
|
+
r"""
|
|
3007
|
+
Return ``True`` if ``letter`` is a growing letter.
|
|
3008
|
+
|
|
3009
|
+
A letter `a` is *growing* for the morphism `s` if the length of the
|
|
3010
|
+
iterates of `| s^n(a) |` tend to infinity as `n` goes to infinity.
|
|
3011
|
+
|
|
3012
|
+
INPUT:
|
|
3013
|
+
|
|
3014
|
+
- ``letter`` -- ``None`` or a letter in the domain of ``self``
|
|
3015
|
+
|
|
3016
|
+
.. NOTE::
|
|
3017
|
+
|
|
3018
|
+
If letter is ``None``, this returns ``True`` if ``self`` is
|
|
3019
|
+
everywhere growing, i.e., all letters are growing letters (see
|
|
3020
|
+
[CassNic10]_), and that ``self`` **must** be an endomorphism.
|
|
3021
|
+
|
|
3022
|
+
EXAMPLES::
|
|
3023
|
+
|
|
3024
|
+
sage: WordMorphism('0->01,1->1').is_growing('0')
|
|
3025
|
+
True
|
|
3026
|
+
sage: WordMorphism('0->01,1->1').is_growing('1')
|
|
3027
|
+
False
|
|
3028
|
+
sage: WordMorphism('0->01,1->10').is_growing()
|
|
3029
|
+
True
|
|
3030
|
+
sage: WordMorphism('0->1,1->2,2->01').is_growing()
|
|
3031
|
+
True
|
|
3032
|
+
sage: WordMorphism('0->01,1->1').is_growing()
|
|
3033
|
+
False
|
|
3034
|
+
|
|
3035
|
+
The domain needs to be equal to the codomain::
|
|
3036
|
+
|
|
3037
|
+
sage: WordMorphism('0->01,1->0,2->1',codomain=Words('012')).is_growing()
|
|
3038
|
+
True
|
|
3039
|
+
|
|
3040
|
+
Test of erasing morphisms::
|
|
3041
|
+
|
|
3042
|
+
sage: WordMorphism('0->01,1->').is_growing('0')
|
|
3043
|
+
False
|
|
3044
|
+
sage: m = WordMorphism('a->bc,b->bcc,c->',codomain=Words('abc'))
|
|
3045
|
+
sage: m.is_growing('a')
|
|
3046
|
+
False
|
|
3047
|
+
sage: m.is_growing('b')
|
|
3048
|
+
False
|
|
3049
|
+
sage: m.is_growing('c')
|
|
3050
|
+
False
|
|
3051
|
+
|
|
3052
|
+
TESTS:
|
|
3053
|
+
|
|
3054
|
+
Make sure that :issue:`31454` is fixed::
|
|
3055
|
+
|
|
3056
|
+
sage: WordMorphism('a->a').is_growing('a')
|
|
3057
|
+
False
|
|
3058
|
+
|
|
3059
|
+
REFERENCES:
|
|
3060
|
+
|
|
3061
|
+
.. [CassNic10] Cassaigne J., Nicolas F. Factor complexity.
|
|
3062
|
+
Combinatorics, automata and number theory, 163--247, Encyclopedia
|
|
3063
|
+
Math. Appl., 135, Cambridge Univ. Press, Cambridge, 2010.
|
|
3064
|
+
"""
|
|
3065
|
+
if not letter:
|
|
3066
|
+
return self.domain().alphabet().cardinality() == len(self.growing_letters())
|
|
3067
|
+
else:
|
|
3068
|
+
return letter in self.growing_letters()
|
|
3069
|
+
|
|
3070
|
+
def growing_letters(self):
|
|
3071
|
+
r"""
|
|
3072
|
+
Return the list of growing letters.
|
|
3073
|
+
|
|
3074
|
+
See :meth:`.is_growing` for more information.
|
|
3075
|
+
|
|
3076
|
+
EXAMPLES::
|
|
3077
|
+
|
|
3078
|
+
sage: WordMorphism('0->01,1->10').growing_letters()
|
|
3079
|
+
['0', '1']
|
|
3080
|
+
sage: WordMorphism('0->01,1->1').growing_letters()
|
|
3081
|
+
['0']
|
|
3082
|
+
sage: WordMorphism('0->01,1->0,2->1',codomain=Words('012')).growing_letters()
|
|
3083
|
+
['0', '1', '2']
|
|
3084
|
+
sage: WordMorphism('a->b,b->a').growing_letters()
|
|
3085
|
+
[]
|
|
3086
|
+
sage: WordMorphism('a->b,b->c,c->d,d->c', codomain=Words('abcd')).growing_letters()
|
|
3087
|
+
[]
|
|
3088
|
+
|
|
3089
|
+
TESTS:
|
|
3090
|
+
|
|
3091
|
+
Make sure that :issue:`31454` is fixed::
|
|
3092
|
+
|
|
3093
|
+
sage: WordMorphism('a->a').growing_letters()
|
|
3094
|
+
[]
|
|
3095
|
+
"""
|
|
3096
|
+
# Remove letters that vanish, ie sigma^n(letter) is ultimately empty
|
|
3097
|
+
immortal = set(self.immortal_letters())
|
|
3098
|
+
new_morph = {x: [z for z in self._morph[x] if z in immortal] for x in immortal}
|
|
3099
|
+
|
|
3100
|
+
# Remove cycles of letters
|
|
3101
|
+
graph_one = {x: y[0] for x, y in new_morph.items() if len(y) == 1}
|
|
3102
|
+
no_loops = set(new_morph)
|
|
3103
|
+
for cycle in get_cycles(graph_one.__getitem__, graph_one):
|
|
3104
|
+
no_loops.difference_update(cycle)
|
|
3105
|
+
new_morph = {x: [z for z in new_morph[x] if z in no_loops] for x in no_loops}
|
|
3106
|
+
|
|
3107
|
+
# NOTE: here we should actually be using the domain made of the
|
|
3108
|
+
# remaining letters in new_morph. However, building the corresponding
|
|
3109
|
+
# alphabet and finite words cost much more time than using the same
|
|
3110
|
+
# domain. Instead we just erase the corresponding letters.
|
|
3111
|
+
for a in self._domain.alphabet():
|
|
3112
|
+
if a not in new_morph:
|
|
3113
|
+
new_morph[a] = self._codomain()
|
|
3114
|
+
|
|
3115
|
+
# Remove letters ending in a cycle
|
|
3116
|
+
new_morph = WordMorphism(new_morph, domain=self.domain(), codomain=self.codomain())
|
|
3117
|
+
return new_morph.immortal_letters()
|
|
3118
|
+
|
|
3119
|
+
def immortal_letters(self):
|
|
3120
|
+
r"""
|
|
3121
|
+
Return the list of immortal letters.
|
|
3122
|
+
|
|
3123
|
+
A letter `a` is *immortal* for the morphism `s` if the length of the
|
|
3124
|
+
iterates of `| s^n(a) |` is larger than zero as `n` goes to infinity.
|
|
3125
|
+
|
|
3126
|
+
Requires this morphism to be self-composable.
|
|
3127
|
+
|
|
3128
|
+
EXAMPLES::
|
|
3129
|
+
|
|
3130
|
+
sage: WordMorphism('a->a').immortal_letters()
|
|
3131
|
+
['a']
|
|
3132
|
+
sage: WordMorphism('a->b,b->a').immortal_letters()
|
|
3133
|
+
['a', 'b']
|
|
3134
|
+
sage: WordMorphism('a->abcd,b->cd,c->dd,d->').immortal_letters()
|
|
3135
|
+
['a']
|
|
3136
|
+
sage: WordMorphism('a->bc,b->cac,c->de,d->,e->').immortal_letters()
|
|
3137
|
+
['a', 'b']
|
|
3138
|
+
sage: WordMorphism('a->', domain=Words('a'), codomain=Words('a')).immortal_letters()
|
|
3139
|
+
[]
|
|
3140
|
+
|
|
3141
|
+
sage: WordMorphism('a->').immortal_letters()
|
|
3142
|
+
[]
|
|
3143
|
+
"""
|
|
3144
|
+
if not self.is_self_composable():
|
|
3145
|
+
raise TypeError(f'self ({self}) is not a self-composable')
|
|
3146
|
+
|
|
3147
|
+
forward = {}
|
|
3148
|
+
backward = {letter: set() for letter in self._morph}
|
|
3149
|
+
stack = []
|
|
3150
|
+
for letter, image in self._morph.items():
|
|
3151
|
+
if not image:
|
|
3152
|
+
stack.append(letter)
|
|
3153
|
+
forward[letter] = set()
|
|
3154
|
+
else:
|
|
3155
|
+
simage = set(image)
|
|
3156
|
+
forward[letter] = simage
|
|
3157
|
+
for occurrence in simage:
|
|
3158
|
+
backward[occurrence].add(letter)
|
|
3159
|
+
|
|
3160
|
+
while stack:
|
|
3161
|
+
letter = stack.pop()
|
|
3162
|
+
for preimage in backward[letter]:
|
|
3163
|
+
forward[preimage].remove(letter)
|
|
3164
|
+
if not forward[preimage]:
|
|
3165
|
+
stack.append(preimage)
|
|
3166
|
+
del forward[letter]
|
|
3167
|
+
del backward[letter]
|
|
3168
|
+
|
|
3169
|
+
return sorted(forward, key=self.domain().alphabet().rank)
|
|
3170
|
+
|
|
3171
|
+
def letter_growth_types(self):
|
|
3172
|
+
r"""
|
|
3173
|
+
Return the mortal, polynomial and exponential growing letters.
|
|
3174
|
+
|
|
3175
|
+
The growth of `| s^n(a) |` as `n` goes to `\infty` is always of the
|
|
3176
|
+
form `\alpha^n n^\beta` (where `\alpha` is a Perron number and
|
|
3177
|
+
`\beta` an integer).
|
|
3178
|
+
|
|
3179
|
+
Without doing any linear algebra three cases can be differentiated:
|
|
3180
|
+
mortal (ultimately empty or `\alpha=0`); polynomial (`\alpha=1`);
|
|
3181
|
+
exponential (`\alpha > 1`). This is what is done in this method.
|
|
3182
|
+
|
|
3183
|
+
It requires this morphism to be an endomorphism.
|
|
3184
|
+
|
|
3185
|
+
OUTPUT:
|
|
3186
|
+
|
|
3187
|
+
The output is a 3-tuple of lists (mortal, polynomial, exponential)
|
|
3188
|
+
where:
|
|
3189
|
+
|
|
3190
|
+
- ``mortal`` -- list of mortal letters
|
|
3191
|
+
- ``polynomial`` -- list of lists where ``polynomial[i]`` is the
|
|
3192
|
+
list of letters with growth `n^i`
|
|
3193
|
+
- ``exponential`` -- list of at least exponentionally growing letters
|
|
3194
|
+
|
|
3195
|
+
EXAMPLES::
|
|
3196
|
+
|
|
3197
|
+
sage: s = WordMorphism('a->abc,b->bc,c->c')
|
|
3198
|
+
sage: mortal, poly, expo = s.letter_growth_types()
|
|
3199
|
+
sage: mortal
|
|
3200
|
+
[]
|
|
3201
|
+
sage: poly
|
|
3202
|
+
[['c'], ['b'], ['a']]
|
|
3203
|
+
sage: expo
|
|
3204
|
+
[]
|
|
3205
|
+
|
|
3206
|
+
When three mortal letters (c, d, and e), and two letters (a, b) are
|
|
3207
|
+
not growing::
|
|
3208
|
+
|
|
3209
|
+
sage: s = WordMorphism('a->bc,b->cac,c->de,d->,e->')
|
|
3210
|
+
sage: s^20
|
|
3211
|
+
WordMorphism: a->cacde, b->debcde, c->, d->, e->
|
|
3212
|
+
sage: mortal, poly, expo = s.letter_growth_types()
|
|
3213
|
+
sage: mortal
|
|
3214
|
+
['c', 'd', 'e']
|
|
3215
|
+
sage: poly
|
|
3216
|
+
[['a', 'b']]
|
|
3217
|
+
sage: expo
|
|
3218
|
+
[]
|
|
3219
|
+
|
|
3220
|
+
::
|
|
3221
|
+
|
|
3222
|
+
sage: s = WordMorphism('a->abcd,b->bc,c->c,d->a')
|
|
3223
|
+
sage: mortal, poly, expo = s.letter_growth_types()
|
|
3224
|
+
sage: mortal
|
|
3225
|
+
[]
|
|
3226
|
+
sage: poly
|
|
3227
|
+
[['c'], ['b']]
|
|
3228
|
+
sage: expo
|
|
3229
|
+
['a', 'd']
|
|
3230
|
+
|
|
3231
|
+
TESTS::
|
|
3232
|
+
|
|
3233
|
+
sage: s = WordMorphism('a->a')
|
|
3234
|
+
sage: s.letter_growth_types()
|
|
3235
|
+
([], [['a']], [])
|
|
3236
|
+
|
|
3237
|
+
::
|
|
3238
|
+
|
|
3239
|
+
sage: s = WordMorphism('a->b,b->a')
|
|
3240
|
+
sage: s.letter_growth_types()
|
|
3241
|
+
([], [['a', 'b']], [])
|
|
3242
|
+
|
|
3243
|
+
::
|
|
3244
|
+
|
|
3245
|
+
sage: s = WordMorphism('a->abcd,b->cd,c->dd,d->')
|
|
3246
|
+
sage: s.letter_growth_types()
|
|
3247
|
+
(['b', 'c', 'd'], [['a']], [])
|
|
3248
|
+
|
|
3249
|
+
::
|
|
3250
|
+
|
|
3251
|
+
sage: s = WordMorphism('a->', domain=Words('a'), codomain=Words('a'))
|
|
3252
|
+
sage: s.letter_growth_types()
|
|
3253
|
+
(['a'], [], [])
|
|
3254
|
+
"""
|
|
3255
|
+
immortal = set(self.immortal_letters())
|
|
3256
|
+
mortal = [a for a in self.domain().alphabet()
|
|
3257
|
+
if a not in immortal]
|
|
3258
|
+
|
|
3259
|
+
# Starting with degree d=0, search for letters with polynomial
|
|
3260
|
+
# growth of degree d.
|
|
3261
|
+
polynomial = []
|
|
3262
|
+
m = {a: [b for b in self.image(a) if b in immortal] for a in immortal}
|
|
3263
|
+
while True:
|
|
3264
|
+
# Construct the permutation of letters containing all letters whose
|
|
3265
|
+
# iterated images under morphism m is always of length 1.
|
|
3266
|
+
not_growing = {a: image_a[0] for a, image_a in m.items()
|
|
3267
|
+
if len(image_a) == 1}
|
|
3268
|
+
preimages = {}
|
|
3269
|
+
roots = []
|
|
3270
|
+
for k, v in not_growing.items():
|
|
3271
|
+
if v not in not_growing:
|
|
3272
|
+
roots.append(v)
|
|
3273
|
+
if v not in preimages:
|
|
3274
|
+
preimages[v] = []
|
|
3275
|
+
preimages[v].append(k)
|
|
3276
|
+
|
|
3277
|
+
while roots:
|
|
3278
|
+
v = roots.pop()
|
|
3279
|
+
for k in preimages.get(v):
|
|
3280
|
+
del not_growing[k]
|
|
3281
|
+
if k in preimages:
|
|
3282
|
+
roots.append(k)
|
|
3283
|
+
|
|
3284
|
+
# The letters inside not_growing are the ones with polynomial
|
|
3285
|
+
# growth d. If there is none, then the remaining letters in m
|
|
3286
|
+
# have exponential growth.
|
|
3287
|
+
if not not_growing:
|
|
3288
|
+
break
|
|
3289
|
+
polynomial.append(list(not_growing))
|
|
3290
|
+
|
|
3291
|
+
# clean the morphism m for the next iteration by removing the
|
|
3292
|
+
# letters with polynomial growth degree d
|
|
3293
|
+
m = {a: [b for b in L if b not in not_growing] for a, L in m.items()
|
|
3294
|
+
if a not in not_growing}
|
|
3295
|
+
|
|
3296
|
+
exponential = list(m)
|
|
3297
|
+
|
|
3298
|
+
# sort the letters as in the input alphabet if possible
|
|
3299
|
+
A = self.domain().alphabet()
|
|
3300
|
+
try:
|
|
3301
|
+
rank = A.rank
|
|
3302
|
+
except AttributeError:
|
|
3303
|
+
pass
|
|
3304
|
+
else:
|
|
3305
|
+
mortal.sort(key=rank)
|
|
3306
|
+
for letters in polynomial:
|
|
3307
|
+
letters.sort(key=rank)
|
|
3308
|
+
exponential.sort(key=rank)
|
|
3309
|
+
|
|
3310
|
+
return mortal, polynomial, exponential
|
|
3311
|
+
|
|
3312
|
+
def abelian_rotation_subspace(self):
|
|
3313
|
+
r"""
|
|
3314
|
+
Return the subspace on which the incidence matrix of ``self`` acts by
|
|
3315
|
+
roots of unity.
|
|
3316
|
+
|
|
3317
|
+
EXAMPLES::
|
|
3318
|
+
|
|
3319
|
+
sage: # needs sage.libs.pari sage.modules
|
|
3320
|
+
sage: WordMorphism('0->1,1->0').abelian_rotation_subspace()
|
|
3321
|
+
Vector space of degree 2 and dimension 2 over Rational Field
|
|
3322
|
+
Basis matrix:
|
|
3323
|
+
[1 0]
|
|
3324
|
+
[0 1]
|
|
3325
|
+
sage: WordMorphism('0->01,1->10').abelian_rotation_subspace()
|
|
3326
|
+
Vector space of degree 2 and dimension 0 over Rational Field
|
|
3327
|
+
Basis matrix:
|
|
3328
|
+
[]
|
|
3329
|
+
sage: WordMorphism('0->01,1->1').abelian_rotation_subspace()
|
|
3330
|
+
Vector space of degree 2 and dimension 1 over Rational Field
|
|
3331
|
+
Basis matrix:
|
|
3332
|
+
[0 1]
|
|
3333
|
+
sage: WordMorphism('1->122,2->211').abelian_rotation_subspace()
|
|
3334
|
+
Vector space of degree 2 and dimension 1 over Rational Field
|
|
3335
|
+
Basis matrix:
|
|
3336
|
+
[ 1 -1]
|
|
3337
|
+
sage: WordMorphism('0->1,1->102,2->3,3->4,4->2').abelian_rotation_subspace()
|
|
3338
|
+
Vector space of degree 5 and dimension 3 over Rational Field
|
|
3339
|
+
Basis matrix:
|
|
3340
|
+
[0 0 1 0 0]
|
|
3341
|
+
[0 0 0 1 0]
|
|
3342
|
+
[0 0 0 0 1]
|
|
3343
|
+
|
|
3344
|
+
The domain needs to be equal to the codomain::
|
|
3345
|
+
|
|
3346
|
+
sage: WordMorphism('0->1,1->',codomain=Words('01')).abelian_rotation_subspace() # needs sage.libs.pari sage.modules
|
|
3347
|
+
Vector space of degree 2 and dimension 0 over Rational Field
|
|
3348
|
+
Basis matrix:
|
|
3349
|
+
[]
|
|
3350
|
+
"""
|
|
3351
|
+
if not self.domain() == self.codomain():
|
|
3352
|
+
raise TypeError("self (=%s) is not an endomorphism" % self)
|
|
3353
|
+
|
|
3354
|
+
if self.domain().alphabet().cardinality() == Infinity:
|
|
3355
|
+
raise ValueError("the alphabet is infinite")
|
|
3356
|
+
|
|
3357
|
+
M = self.incidence_matrix()
|
|
3358
|
+
p = M.charpoly().factor()
|
|
3359
|
+
basis = []
|
|
3360
|
+
for factor in p:
|
|
3361
|
+
if factor[0].is_cyclotomic():
|
|
3362
|
+
basis.extend((factor[0])(M).right_kernel().basis())
|
|
3363
|
+
|
|
3364
|
+
return M.column_ambient_module(base_ring=QQ).subspace(basis)
|
|
3365
|
+
|
|
3366
|
+
def is_injective(self):
|
|
3367
|
+
"""
|
|
3368
|
+
Return whether this morphism is injective.
|
|
3369
|
+
|
|
3370
|
+
ALGORITHM:
|
|
3371
|
+
|
|
3372
|
+
Uses a version of :wikipedia:`Sardinas–Patterson_algorithm`.
|
|
3373
|
+
Time complexity is on average quadratic with regards to the size of the
|
|
3374
|
+
morphism.
|
|
3375
|
+
|
|
3376
|
+
EXAMPLES::
|
|
3377
|
+
|
|
3378
|
+
sage: WordMorphism('a->0,b->10,c->110,d->111').is_injective()
|
|
3379
|
+
True
|
|
3380
|
+
sage: WordMorphism('a->00,b->01,c->012,d->20001').is_injective()
|
|
3381
|
+
False
|
|
3382
|
+
"""
|
|
3383
|
+
def check(u, v):
|
|
3384
|
+
if u.is_prefix(v):
|
|
3385
|
+
tail = v[u.length():]
|
|
3386
|
+
if tail not in tails:
|
|
3387
|
+
tails.add(tail)
|
|
3388
|
+
todo.append(tail)
|
|
3389
|
+
|
|
3390
|
+
if self.is_erasing():
|
|
3391
|
+
return False
|
|
3392
|
+
images = self.images()
|
|
3393
|
+
tails = set()
|
|
3394
|
+
todo = []
|
|
3395
|
+
|
|
3396
|
+
for i in range(len(images)):
|
|
3397
|
+
for j in range(i + 1, len(images)):
|
|
3398
|
+
if images[i] == images[j]:
|
|
3399
|
+
return False
|
|
3400
|
+
check(images[i], images[j])
|
|
3401
|
+
check(images[j], images[i])
|
|
3402
|
+
while todo:
|
|
3403
|
+
u = todo.pop()
|
|
3404
|
+
for v in images:
|
|
3405
|
+
if u == v:
|
|
3406
|
+
return False
|
|
3407
|
+
check(u, v)
|
|
3408
|
+
check(v, u)
|
|
3409
|
+
|
|
3410
|
+
return True
|
|
3411
|
+
|
|
3412
|
+
def is_pushy(self, w=None):
|
|
3413
|
+
r"""
|
|
3414
|
+
Return whether the language `\{m^n(w) | n \ge 0\}` is pushy,
|
|
3415
|
+
where `m` is this morphism and `w` is a word inputted as a parameter.
|
|
3416
|
+
|
|
3417
|
+
Requires this morphism to be an endomorphism.
|
|
3418
|
+
|
|
3419
|
+
A language created by iterating a morphism is pushy, if its words
|
|
3420
|
+
contain an infinite number of factors containing no growing letters. It
|
|
3421
|
+
turns out that this is equivalent to having at least one infinite
|
|
3422
|
+
repetition containing no growing letters.
|
|
3423
|
+
|
|
3424
|
+
See :meth:`infinite_repetitions_primitive_roots` and :meth:`is_growing`.
|
|
3425
|
+
|
|
3426
|
+
INPUT:
|
|
3427
|
+
|
|
3428
|
+
- ``w`` -- finite iterable (default: ``self.domain().alphabet()``);
|
|
3429
|
+
represents a word used to start the language
|
|
3430
|
+
|
|
3431
|
+
EXAMPLES::
|
|
3432
|
+
|
|
3433
|
+
sage: WordMorphism('a->abca,b->bc,c->').is_pushy()
|
|
3434
|
+
False
|
|
3435
|
+
sage: WordMorphism('a->abc,b->,c->bcb').is_pushy()
|
|
3436
|
+
True
|
|
3437
|
+
"""
|
|
3438
|
+
return bool(self.infinite_repetitions_primitive_roots(w, False))
|
|
3439
|
+
|
|
3440
|
+
def is_unboundedly_repetitive(self, w=None):
|
|
3441
|
+
r"""
|
|
3442
|
+
Return whether the language `\{m^n(w) | n \ge 0\}` is unboundedly repetitive,
|
|
3443
|
+
where `m` is this morphism and `w` is a word inputted as a parameter.
|
|
3444
|
+
|
|
3445
|
+
Requires this morphism to be an endomorphism.
|
|
3446
|
+
|
|
3447
|
+
A language created by iterating a morphism is unboundedly repetitive, if
|
|
3448
|
+
it has at least one infinite repetition containing at least one growing
|
|
3449
|
+
letter.
|
|
3450
|
+
|
|
3451
|
+
See :meth:`infinite_repetitions_primitive_roots` and :meth:`is_growing`.
|
|
3452
|
+
|
|
3453
|
+
INPUT:
|
|
3454
|
+
|
|
3455
|
+
- ``w`` -- finite iterable (default: ``self.domain().alphabet()``);
|
|
3456
|
+
represents a word used to start the language
|
|
3457
|
+
|
|
3458
|
+
EXAMPLES::
|
|
3459
|
+
|
|
3460
|
+
sage: WordMorphism('a->abca,b->bc,c->').is_unboundedly_repetitive()
|
|
3461
|
+
True
|
|
3462
|
+
sage: WordMorphism('a->abc,b->,c->bcb').is_unboundedly_repetitive()
|
|
3463
|
+
False
|
|
3464
|
+
"""
|
|
3465
|
+
return bool(self.infinite_repetitions_primitive_roots(w, True))
|
|
3466
|
+
|
|
3467
|
+
def is_repetitive(self, w=None):
|
|
3468
|
+
r"""
|
|
3469
|
+
Return whether the language `\{m^n(w) | n \ge 0\}` is repetitive,
|
|
3470
|
+
where `m` is this morphism and `w` is a word inputted as a parameter.
|
|
3471
|
+
|
|
3472
|
+
Requires this morphism to be an endomorphism.
|
|
3473
|
+
|
|
3474
|
+
A language is repetitive, if for each positive integer `k` there exists
|
|
3475
|
+
a word `u` such that `u^k` is a factor of some word of the language.
|
|
3476
|
+
|
|
3477
|
+
It turns out that for languages created by iterating a morphism this is
|
|
3478
|
+
equivalent to having at least one infinite repetition (this property is
|
|
3479
|
+
also known as strong repetitiveness).
|
|
3480
|
+
|
|
3481
|
+
See :meth:`infinite_repetitions_primitive_roots`.
|
|
3482
|
+
|
|
3483
|
+
INPUT:
|
|
3484
|
+
|
|
3485
|
+
- ``w`` -- finite iterable (default: ``self.domain().alphabet()``);
|
|
3486
|
+
represents a word used to start the language
|
|
3487
|
+
|
|
3488
|
+
EXAMPLES:
|
|
3489
|
+
|
|
3490
|
+
This method can be used to check whether a purely morphic word is not
|
|
3491
|
+
k-power free for all positive integers k. For example, the language
|
|
3492
|
+
containing just the Thue-Morse word and its prefixes is not repetitive,
|
|
3493
|
+
since the Thue-Morse word is cube-free::
|
|
3494
|
+
|
|
3495
|
+
sage: WordMorphism('a->ab,b->ba').is_repetitive('a')
|
|
3496
|
+
False
|
|
3497
|
+
|
|
3498
|
+
Similarly, the Hanoi word is square-free::
|
|
3499
|
+
|
|
3500
|
+
sage: WordMorphism('a->aC,A->ac,b->cB,B->cb,c->bA,C->ba').is_repetitive('a')
|
|
3501
|
+
False
|
|
3502
|
+
|
|
3503
|
+
However, this method solves a more general problem, as it can be called
|
|
3504
|
+
on any morphism `m` and with any word `w`::
|
|
3505
|
+
|
|
3506
|
+
sage: WordMorphism('a->c,b->cda,c->a,d->abc').is_repetitive('bd')
|
|
3507
|
+
True
|
|
3508
|
+
"""
|
|
3509
|
+
return self.is_pushy(w) or self.is_unboundedly_repetitive(w)
|
|
3510
|
+
|
|
3511
|
+
def infinite_repetitions_primitive_roots(self, w=None, allow_growing=None):
|
|
3512
|
+
r"""
|
|
3513
|
+
Return the set of primitive roots (up to conjugacy) of infinite
|
|
3514
|
+
repetitions from the language `\{m^n(w) | n \ge 0\}`, where `m` is this
|
|
3515
|
+
morphism and `w` is a word inputted as a parameter.
|
|
3516
|
+
|
|
3517
|
+
Requires this morphism to be an endomorphism.
|
|
3518
|
+
|
|
3519
|
+
The word `v^\omega` is an infinite repetition (in other words, an
|
|
3520
|
+
infinite periodic factor) of a language, if `v` is a non-empty word and
|
|
3521
|
+
for each positive integer `k` the word `v^k` is a factor of some word
|
|
3522
|
+
from the language. It turns out that a language created by iterating a
|
|
3523
|
+
morphism has a finite number of primitive roots of infinite repetitions.
|
|
3524
|
+
|
|
3525
|
+
If `v` is a primitive root of an infinite repetition, then all its
|
|
3526
|
+
conjugations are also primitive roots of an infinite repetition. For
|
|
3527
|
+
simplicity's sake this method returns only the lexicographically minimal
|
|
3528
|
+
one from each conjugacy class.
|
|
3529
|
+
|
|
3530
|
+
INPUT:
|
|
3531
|
+
|
|
3532
|
+
- ``w`` -- finite iterable (default: ``self.domain().alphabet()``);
|
|
3533
|
+
represents a word used to start the language
|
|
3534
|
+
|
|
3535
|
+
- ``allow_growing`` -- boolean or ``None`` (default: ``None``); if
|
|
3536
|
+
``False``, return only the primitive roots that contain no growing
|
|
3537
|
+
letters. If ``True``, return only the primitive roots that contain at
|
|
3538
|
+
least one growing letter. If ``None``, return both.
|
|
3539
|
+
|
|
3540
|
+
ALGORITHM:
|
|
3541
|
+
|
|
3542
|
+
The algorithm used is described in detail in [KS2015]_.
|
|
3543
|
+
|
|
3544
|
+
EXAMPLES::
|
|
3545
|
+
|
|
3546
|
+
sage: m = WordMorphism('a->aba,b->aba,c->cd,d->e,e->d')
|
|
3547
|
+
sage: inf_reps = m.infinite_repetitions_primitive_roots('ac')
|
|
3548
|
+
sage: sorted(inf_reps)
|
|
3549
|
+
[word: aab, word: de]
|
|
3550
|
+
|
|
3551
|
+
``allow_growing`` parameter::
|
|
3552
|
+
|
|
3553
|
+
sage: sorted(m.infinite_repetitions_primitive_roots('ac', True))
|
|
3554
|
+
[word: aab]
|
|
3555
|
+
sage: sorted(m.infinite_repetitions_primitive_roots('ac', False))
|
|
3556
|
+
[word: de]
|
|
3557
|
+
|
|
3558
|
+
Incomplete check that these words are indeed the primitive roots of
|
|
3559
|
+
infinite repetitions::
|
|
3560
|
+
|
|
3561
|
+
sage: SL = m._language_naive(10, Word('ac'))
|
|
3562
|
+
sage: all(x in SL for x in inf_reps)
|
|
3563
|
+
True
|
|
3564
|
+
sage: all(x^2 in SL for x in inf_reps)
|
|
3565
|
+
True
|
|
3566
|
+
sage: all(x^3 in SL for x in inf_reps)
|
|
3567
|
+
True
|
|
3568
|
+
|
|
3569
|
+
Large example::
|
|
3570
|
+
|
|
3571
|
+
sage: m = WordMorphism('a->1b5,b->fcg,c->dae,d->432,e->678,f->f,g->g,1->2,2->3,3->4,4->1,5->6,6->7,7->8,8->5')
|
|
3572
|
+
sage: sorted(m.infinite_repetitions_primitive_roots('a'))
|
|
3573
|
+
[word: 1432f2143f3214f4321f, word: 5678g8567g7856g6785g]
|
|
3574
|
+
|
|
3575
|
+
TESTS::
|
|
3576
|
+
|
|
3577
|
+
sage: m = WordMorphism('a->Cab,b->1c1,c->E2bd5,d->BbaA,5->6,6->7,7->8,8->9,9->5,1->2,2->1,A->B,B->C,C->D,D->E,E->')
|
|
3578
|
+
sage: sorted(m.infinite_repetitions_primitive_roots())
|
|
3579
|
+
[word: 1, word: 1519181716, word: 2, word: 2529282726]
|
|
3580
|
+
|
|
3581
|
+
sage: m = WordMorphism('a->b,b->b', codomain=FiniteWords('ab'))
|
|
3582
|
+
sage: m.infinite_repetitions_primitive_roots()
|
|
3583
|
+
set()
|
|
3584
|
+
|
|
3585
|
+
sage: m = WordMorphism('c->d,d->c,e->fc,f->ed')
|
|
3586
|
+
sage: sorted(m.infinite_repetitions_primitive_roots())
|
|
3587
|
+
[word: c, word: d]
|
|
3588
|
+
|
|
3589
|
+
sage: m = WordMorphism('a->bcb,b->ada,c->d,d->c')
|
|
3590
|
+
sage: sorted(m.infinite_repetitions_primitive_roots())
|
|
3591
|
+
[word: ad, word: bc]
|
|
3592
|
+
|
|
3593
|
+
sage: m = WordMorphism('b->c,c->bcb')
|
|
3594
|
+
sage: sorted(m.infinite_repetitions_primitive_roots())
|
|
3595
|
+
[word: bc]
|
|
3596
|
+
|
|
3597
|
+
sage: m = WordMorphism('a->abc,b->dab,c->abc,d->dab')
|
|
3598
|
+
sage: sorted(m.infinite_repetitions_primitive_roots())
|
|
3599
|
+
[word: ababcd]
|
|
3600
|
+
"""
|
|
3601
|
+
def impl_no_growing(g, k):
|
|
3602
|
+
U = {}
|
|
3603
|
+
for x in unbounded:
|
|
3604
|
+
xg = g.image(x)
|
|
3605
|
+
for i, y in enumerate(reversed(xg)):
|
|
3606
|
+
if y in unbounded:
|
|
3607
|
+
break
|
|
3608
|
+
U[x] = y, xg[xg.length() - i:]
|
|
3609
|
+
for cycle in get_cycles(lambda x: U[x][0], domain=unbounded):
|
|
3610
|
+
if all(not U[x][1] for x in cycle):
|
|
3611
|
+
continue
|
|
3612
|
+
gq = gb**len(cycle)
|
|
3613
|
+
for cyc in g.domain()(cycle).conjugates_iterator():
|
|
3614
|
+
u = g.domain()()
|
|
3615
|
+
for x in cyc:
|
|
3616
|
+
u = U[x][1] + gb(u)
|
|
3617
|
+
inf_rep = g.domain()()
|
|
3618
|
+
history = set()
|
|
3619
|
+
while u not in history:
|
|
3620
|
+
history.add(u)
|
|
3621
|
+
inf_rep += u
|
|
3622
|
+
u = gq(u)
|
|
3623
|
+
yield k(inf_rep.primitive()).primitive()
|
|
3624
|
+
|
|
3625
|
+
if w is None:
|
|
3626
|
+
w = self._morph
|
|
3627
|
+
reach = self._language_naive(2, self._domain(w))
|
|
3628
|
+
f = self.restrict_domain([x[0] for x in reach])
|
|
3629
|
+
f._codomain = f._domain
|
|
3630
|
+
g, _, k, _ = f.simplify_until_injective()
|
|
3631
|
+
g._codomain = g._domain
|
|
3632
|
+
unbounded = set(g.growing_letters())
|
|
3633
|
+
result = set()
|
|
3634
|
+
|
|
3635
|
+
if allow_growing is not True:
|
|
3636
|
+
gb = g.restrict_domain(set(g._morph) - unbounded)
|
|
3637
|
+
for x in impl_no_growing(g, k): # UR.
|
|
3638
|
+
result.add(x.minimal_conjugate())
|
|
3639
|
+
for x in impl_no_growing(g.reversal(), k.reversal()): # UL.
|
|
3640
|
+
result.add(self.domain()(reversed(x)).minimal_conjugate())
|
|
3641
|
+
|
|
3642
|
+
if allow_growing is not False:
|
|
3643
|
+
for periodic_orbit in g.periodic_points():
|
|
3644
|
+
gq = g**len(periodic_orbit)
|
|
3645
|
+
for periodic_point in periodic_orbit:
|
|
3646
|
+
# Check if this periodic point is a periodic infinite word.
|
|
3647
|
+
periodic_point = periodic_point[:1]
|
|
3648
|
+
occurred = set(periodic_point)
|
|
3649
|
+
one_unbounded_twice = False
|
|
3650
|
+
for _ in g.domain().alphabet():
|
|
3651
|
+
previous_length = periodic_point.length()
|
|
3652
|
+
periodic_point = gq(periodic_point)
|
|
3653
|
+
for i, letter in enumerate(periodic_point[previous_length:]):
|
|
3654
|
+
if letter in unbounded:
|
|
3655
|
+
if letter in occurred:
|
|
3656
|
+
one_unbounded_twice = True
|
|
3657
|
+
break
|
|
3658
|
+
occurred.add(letter)
|
|
3659
|
+
if one_unbounded_twice:
|
|
3660
|
+
break
|
|
3661
|
+
if not one_unbounded_twice or letter != periodic_point[0]:
|
|
3662
|
+
break
|
|
3663
|
+
v = periodic_point[:previous_length + i]
|
|
3664
|
+
vq = gq(v)
|
|
3665
|
+
m = 0
|
|
3666
|
+
while vq[m * v.length(): (m + 1) * v.length()] == v:
|
|
3667
|
+
m += 1
|
|
3668
|
+
if m * v.length() != vq.length():
|
|
3669
|
+
break
|
|
3670
|
+
result.add(k(v).primitive().minimal_conjugate())
|
|
3671
|
+
|
|
3672
|
+
return result
|
|
3673
|
+
|
|
3674
|
+
def simplify_alphabet_size(self, Z=None):
|
|
3675
|
+
r"""
|
|
3676
|
+
If this morphism is simplifiable, return morphisms `h` and `k` such that
|
|
3677
|
+
this morphism is simplifiable with respect to `h` and `k`, otherwise
|
|
3678
|
+
raise :exc:`ValueError`.
|
|
3679
|
+
|
|
3680
|
+
This method is quite fast if this morphism is non-injective, but very
|
|
3681
|
+
slow if it is injective.
|
|
3682
|
+
|
|
3683
|
+
Let `f: X^* \rightarrow Y^*` be a morphism. Then `f` is simplifiable
|
|
3684
|
+
with respect to morphisms `h: X^* \rightarrow Z^*` and
|
|
3685
|
+
`k: Z^* \rightarrow Y^*`, if `f = k \circ h` and `|Z| < |X|`. If also
|
|
3686
|
+
`Y \subseteq X`, then the morphism `g: Z^* \rightarrow Z^* = h \circ k`
|
|
3687
|
+
is a simplification of `f` (with respect to `h` and `k`).
|
|
3688
|
+
|
|
3689
|
+
Loosely speaking, a morphism is simplifiable if it contains "more letters
|
|
3690
|
+
than is needed". Non-injectivity implies simplifiability. Simplification
|
|
3691
|
+
preserves some properties of the original morphism (e.g. repetitiveness).
|
|
3692
|
+
|
|
3693
|
+
For more information see Section 3 in [KO2000]_.
|
|
3694
|
+
|
|
3695
|
+
INPUT:
|
|
3696
|
+
|
|
3697
|
+
- ``Z`` -- iterable (default: ``self.domain().alphabet()``) whose
|
|
3698
|
+
elements are used as an alphabet for the simplification
|
|
3699
|
+
|
|
3700
|
+
EXAMPLES:
|
|
3701
|
+
|
|
3702
|
+
Example of a simplifiable (non-injective) morphism::
|
|
3703
|
+
|
|
3704
|
+
sage: f = WordMorphism('a->aca,b->badc,c->acab,d->adc')
|
|
3705
|
+
sage: h, k = f.simplify_alphabet_size('xyz'); h, k
|
|
3706
|
+
(WordMorphism: a->x, b->zy, c->xz, d->y, WordMorphism: x->aca, y->adc, z->b)
|
|
3707
|
+
sage: k * h == f
|
|
3708
|
+
True
|
|
3709
|
+
sage: g = h * k; g
|
|
3710
|
+
WordMorphism: x->xxzx, y->xyxz, z->zy
|
|
3711
|
+
|
|
3712
|
+
Example of a simplifiable (injective) morphism::
|
|
3713
|
+
|
|
3714
|
+
sage: f = WordMorphism('a->abcc,b->abcd,c->abdc,d->abdd')
|
|
3715
|
+
sage: h, k = f.simplify_alphabet_size('xyz'); h, k
|
|
3716
|
+
(WordMorphism: a->xyy, b->xyz, c->xzy, d->xzz, WordMorphism: x->ab, y->c, z->d)
|
|
3717
|
+
sage: k * h == f
|
|
3718
|
+
True
|
|
3719
|
+
sage: g = h * k; g
|
|
3720
|
+
WordMorphism: x->xyyxyz, y->xzy, z->xzz
|
|
3721
|
+
|
|
3722
|
+
Example of a non-simplifiable morphism::
|
|
3723
|
+
|
|
3724
|
+
sage: WordMorphism('a->aa').simplify_alphabet_size()
|
|
3725
|
+
Traceback (most recent call last):
|
|
3726
|
+
...
|
|
3727
|
+
ValueError: self (a->aa) is not simplifiable
|
|
3728
|
+
|
|
3729
|
+
Example of an erasing morphism::
|
|
3730
|
+
|
|
3731
|
+
sage: f = WordMorphism('a->abc,b->cc,c->')
|
|
3732
|
+
sage: h, k = f.simplify_alphabet_size(); h, k
|
|
3733
|
+
(WordMorphism: a->a, b->b, c->, WordMorphism: a->abc, b->cc)
|
|
3734
|
+
sage: k * h == f
|
|
3735
|
+
True
|
|
3736
|
+
sage: g = h * k; g
|
|
3737
|
+
WordMorphism: a->ab, b->
|
|
3738
|
+
|
|
3739
|
+
Example of a morphism, that is not an endomorphism::
|
|
3740
|
+
|
|
3741
|
+
sage: f = WordMorphism('a->xx,b->xy,c->yx,d->yy')
|
|
3742
|
+
sage: h, k = f.simplify_alphabet_size(NN); h, k
|
|
3743
|
+
(WordMorphism: a->00, b->01, c->10, d->11, WordMorphism: 0->x, 1->y)
|
|
3744
|
+
sage: k * h == f
|
|
3745
|
+
True
|
|
3746
|
+
sage: len(k.domain().alphabet()) < len(f.domain().alphabet())
|
|
3747
|
+
True
|
|
3748
|
+
"""
|
|
3749
|
+
def try_create_h(f, k):
|
|
3750
|
+
h = {}
|
|
3751
|
+
for letter1, image1 in f.items():
|
|
3752
|
+
image3 = []
|
|
3753
|
+
while image1:
|
|
3754
|
+
for letter2, image2 in k.items():
|
|
3755
|
+
if image2.is_prefix(image1):
|
|
3756
|
+
image1 = image1[image2.length():]
|
|
3757
|
+
image3.append(letter2)
|
|
3758
|
+
break
|
|
3759
|
+
else: # nobreak
|
|
3760
|
+
return None
|
|
3761
|
+
h[letter1] = image3
|
|
3762
|
+
return h
|
|
3763
|
+
|
|
3764
|
+
X = self.domain().alphabet()
|
|
3765
|
+
Y = self.codomain().alphabet()
|
|
3766
|
+
f = self._morph
|
|
3767
|
+
|
|
3768
|
+
if self.is_erasing(): # Trivial case #1.
|
|
3769
|
+
k = {letter: image for letter, image in f.items() if image}
|
|
3770
|
+
h = {letter: [letter] if image else [] for letter, image in f.items()}
|
|
3771
|
+
elif len(Y) < len(X): # Trivial case #2.
|
|
3772
|
+
k = {x: [y] for x, y in zip(X, Y)}
|
|
3773
|
+
k_inverse = dict(zip(Y, X))
|
|
3774
|
+
h = {x: [k_inverse[y] for y in image] for x, image in f.items()}
|
|
3775
|
+
elif not self.is_injective(): # Non-trivial but a fast case.
|
|
3776
|
+
k = dict(f)
|
|
3777
|
+
to_do = set(k)
|
|
3778
|
+
while to_do:
|
|
3779
|
+
to_remove = []
|
|
3780
|
+
# min() and remove() instead of pop() to have deterministic output.
|
|
3781
|
+
letter1 = min(to_do)
|
|
3782
|
+
to_do.remove(letter1)
|
|
3783
|
+
image1 = k[letter1]
|
|
3784
|
+
for letter2, image2 in k.items():
|
|
3785
|
+
if letter1 == letter2:
|
|
3786
|
+
continue
|
|
3787
|
+
if image1 == image2:
|
|
3788
|
+
to_remove.append(letter2)
|
|
3789
|
+
to_do.discard(letter2)
|
|
3790
|
+
elif image1.is_prefix(image2):
|
|
3791
|
+
k[letter2] = image2[image1.length():]
|
|
3792
|
+
to_do.add(letter2)
|
|
3793
|
+
elif image2.is_prefix(image1):
|
|
3794
|
+
k[letter1] = image1[image2.length():]
|
|
3795
|
+
to_do.add(letter1)
|
|
3796
|
+
break
|
|
3797
|
+
for letter in to_remove:
|
|
3798
|
+
del k[letter]
|
|
3799
|
+
h = try_create_h(f, k)
|
|
3800
|
+
else: # Non-trivial and a slow case.
|
|
3801
|
+
factors = set()
|
|
3802
|
+
for image in f.values():
|
|
3803
|
+
factors.update(x.primitive() for x in image.factor_iterator())
|
|
3804
|
+
factors.remove(self.codomain()())
|
|
3805
|
+
factors = sorted(factors) # For deterministic output.
|
|
3806
|
+
from itertools import combinations
|
|
3807
|
+
for comb in combinations(factors, len(X) - 1):
|
|
3808
|
+
if any(x.is_proper_prefix(y) for x in comb for y in comb):
|
|
3809
|
+
continue
|
|
3810
|
+
k = dict(zip(X, comb))
|
|
3811
|
+
h = try_create_h(f, k)
|
|
3812
|
+
if h:
|
|
3813
|
+
break
|
|
3814
|
+
else: # nobreak
|
|
3815
|
+
raise ValueError(f'self ({self}) is not simplifiable')
|
|
3816
|
+
|
|
3817
|
+
k = WordMorphism(k, codomain=self.codomain())
|
|
3818
|
+
h = WordMorphism(h, domain=self.domain(), codomain=k.domain())
|
|
3819
|
+
|
|
3820
|
+
if Z is not None: # Custom alphabet.
|
|
3821
|
+
old_Z_star = k.domain()
|
|
3822
|
+
old_Z = old_Z_star.alphabet()
|
|
3823
|
+
Z = [z for z, _ in zip(Z, old_Z)]
|
|
3824
|
+
if len(Z) < len(old_Z):
|
|
3825
|
+
raise ValueError(f'Z should have length at least {len(old_Z)}, is {len(Z)}')
|
|
3826
|
+
Z_star = FiniteWords(Z)
|
|
3827
|
+
h_new = {old: [new] for old, new in zip(old_Z, Z)}
|
|
3828
|
+
k_new = {new: [old] for new, old in zip(Z, old_Z)}
|
|
3829
|
+
h_new = WordMorphism(h_new, domain=old_Z_star, codomain=Z_star)
|
|
3830
|
+
k_new = WordMorphism(k_new, domain=Z_star, codomain=old_Z_star)
|
|
3831
|
+
h = h_new * h
|
|
3832
|
+
k = k * k_new
|
|
3833
|
+
|
|
3834
|
+
return h, k
|
|
3835
|
+
|
|
3836
|
+
def simplify_until_injective(self):
|
|
3837
|
+
r"""
|
|
3838
|
+
Return a quadruplet `(g, h, k, i)`, where `g` is an injective
|
|
3839
|
+
simplification of this morphism with respect to `h`, `k` and `i`.
|
|
3840
|
+
|
|
3841
|
+
Requires this morphism to be an endomorphism.
|
|
3842
|
+
|
|
3843
|
+
This methods basically calls :meth:`simplify_alphabet_size` until the
|
|
3844
|
+
returned simplification is injective. If this morphism is already
|
|
3845
|
+
injective, a quadruplet `(g, h, k, i)` is still returned, where `g`
|
|
3846
|
+
is this morphism, `h` and `k` are the identity morphisms and `i` is 0.
|
|
3847
|
+
|
|
3848
|
+
Let `f: X^* \rightarrow Y^*` be a morphism and `Y \subseteq X`. Then
|
|
3849
|
+
`g: Z^* \rightarrow Z^*` is an injective simplification of `f` with
|
|
3850
|
+
respect to morphisms `h: X^* \rightarrow Z^*` and
|
|
3851
|
+
`k: Z^* \rightarrow Y^*` and a positive integer `i`, if `g` is
|
|
3852
|
+
injective, `|Z| < |X|`, `g^i = h \circ k` and `f^i = k \circ h`.
|
|
3853
|
+
|
|
3854
|
+
For more information see Section 4 in [KO2000]_.
|
|
3855
|
+
|
|
3856
|
+
EXAMPLES::
|
|
3857
|
+
|
|
3858
|
+
sage: f = WordMorphism('a->abc,b->a,c->bc')
|
|
3859
|
+
sage: g, h, k, i = f.simplify_until_injective(); g, h, k, i
|
|
3860
|
+
(WordMorphism: a->aa, WordMorphism: a->aa, b->a, c->a, WordMorphism: a->abc, 2)
|
|
3861
|
+
sage: g.is_injective()
|
|
3862
|
+
True
|
|
3863
|
+
sage: g**i == h * k
|
|
3864
|
+
True
|
|
3865
|
+
sage: f**i == k * h
|
|
3866
|
+
True
|
|
3867
|
+
"""
|
|
3868
|
+
if not self.is_endomorphism():
|
|
3869
|
+
raise TypeError(f'self ({self}) is not an endomorphism')
|
|
3870
|
+
|
|
3871
|
+
g = self
|
|
3872
|
+
h = self.domain().identity_morphism()
|
|
3873
|
+
k = self.codomain().identity_morphism()
|
|
3874
|
+
i = 0
|
|
3875
|
+
while not g.is_injective():
|
|
3876
|
+
h_new, k_new = g.simplify_alphabet_size()
|
|
3877
|
+
g, h, k, i = h_new * k_new, h_new * h, k * k_new, i + 1
|
|
3878
|
+
return g, h, k, i
|