passagemath-combinat 10.6.42__cp314-cp314t-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (401) hide show
  1. passagemath_combinat/__init__.py +3 -0
  2. passagemath_combinat-10.6.42.dist-info/DELVEWHEEL +2 -0
  3. passagemath_combinat-10.6.42.dist-info/METADATA +160 -0
  4. passagemath_combinat-10.6.42.dist-info/RECORD +401 -0
  5. passagemath_combinat-10.6.42.dist-info/WHEEL +5 -0
  6. passagemath_combinat-10.6.42.dist-info/top_level.txt +3 -0
  7. passagemath_combinat.libs/libgmp-10-3a5f019e2510aeaad918cab2b57a689d.dll +0 -0
  8. passagemath_combinat.libs/libsymmetrica-3-7dcf900932804d0df5fd0919b4668720.dll +0 -0
  9. sage/algebras/affine_nil_temperley_lieb.py +263 -0
  10. sage/algebras/all.py +24 -0
  11. sage/algebras/all__sagemath_combinat.py +35 -0
  12. sage/algebras/askey_wilson.py +935 -0
  13. sage/algebras/associated_graded.py +345 -0
  14. sage/algebras/cellular_basis.py +350 -0
  15. sage/algebras/cluster_algebra.py +2766 -0
  16. sage/algebras/down_up_algebra.py +860 -0
  17. sage/algebras/free_algebra.py +1698 -0
  18. sage/algebras/free_algebra_element.py +345 -0
  19. sage/algebras/free_algebra_quotient.py +405 -0
  20. sage/algebras/free_algebra_quotient_element.py +295 -0
  21. sage/algebras/free_zinbiel_algebra.py +885 -0
  22. sage/algebras/hall_algebra.py +783 -0
  23. sage/algebras/hecke_algebras/all.py +4 -0
  24. sage/algebras/hecke_algebras/ariki_koike_algebra.py +1796 -0
  25. sage/algebras/hecke_algebras/ariki_koike_specht_modules.py +475 -0
  26. sage/algebras/hecke_algebras/cubic_hecke_algebra.py +3520 -0
  27. sage/algebras/hecke_algebras/cubic_hecke_base_ring.py +1473 -0
  28. sage/algebras/hecke_algebras/cubic_hecke_matrix_rep.py +1079 -0
  29. sage/algebras/iwahori_hecke_algebra.py +3095 -0
  30. sage/algebras/jordan_algebra.py +1773 -0
  31. sage/algebras/lie_conformal_algebras/abelian_lie_conformal_algebra.py +113 -0
  32. sage/algebras/lie_conformal_algebras/affine_lie_conformal_algebra.py +156 -0
  33. sage/algebras/lie_conformal_algebras/all.py +18 -0
  34. sage/algebras/lie_conformal_algebras/bosonic_ghosts_lie_conformal_algebra.py +134 -0
  35. sage/algebras/lie_conformal_algebras/examples.py +43 -0
  36. sage/algebras/lie_conformal_algebras/fermionic_ghosts_lie_conformal_algebra.py +131 -0
  37. sage/algebras/lie_conformal_algebras/finitely_freely_generated_lca.py +139 -0
  38. sage/algebras/lie_conformal_algebras/free_bosons_lie_conformal_algebra.py +174 -0
  39. sage/algebras/lie_conformal_algebras/free_fermions_lie_conformal_algebra.py +167 -0
  40. sage/algebras/lie_conformal_algebras/freely_generated_lie_conformal_algebra.py +107 -0
  41. sage/algebras/lie_conformal_algebras/graded_lie_conformal_algebra.py +135 -0
  42. sage/algebras/lie_conformal_algebras/lie_conformal_algebra.py +353 -0
  43. sage/algebras/lie_conformal_algebras/lie_conformal_algebra_element.py +236 -0
  44. sage/algebras/lie_conformal_algebras/lie_conformal_algebra_with_basis.py +78 -0
  45. sage/algebras/lie_conformal_algebras/lie_conformal_algebra_with_structure_coefs.py +328 -0
  46. sage/algebras/lie_conformal_algebras/n2_lie_conformal_algebra.py +117 -0
  47. sage/algebras/lie_conformal_algebras/neveu_schwarz_lie_conformal_algebra.py +86 -0
  48. sage/algebras/lie_conformal_algebras/virasoro_lie_conformal_algebra.py +82 -0
  49. sage/algebras/lie_conformal_algebras/weyl_lie_conformal_algebra.py +205 -0
  50. sage/algebras/nil_coxeter_algebra.py +191 -0
  51. sage/algebras/q_commuting_polynomials.py +673 -0
  52. sage/algebras/q_system.py +608 -0
  53. sage/algebras/quantum_clifford.py +959 -0
  54. sage/algebras/quantum_groups/ace_quantum_onsager.py +693 -0
  55. sage/algebras/quantum_groups/all.py +9 -0
  56. sage/algebras/quantum_groups/fock_space.py +2219 -0
  57. sage/algebras/quantum_groups/q_numbers.py +207 -0
  58. sage/algebras/quantum_groups/quantum_group_gap.py +2695 -0
  59. sage/algebras/quantum_groups/representations.py +591 -0
  60. sage/algebras/quantum_matrix_coordinate_algebra.py +1006 -0
  61. sage/algebras/quantum_oscillator.py +623 -0
  62. sage/algebras/quaternion_algebra.py +20 -0
  63. sage/algebras/quaternion_algebra_element.py +55 -0
  64. sage/algebras/rational_cherednik_algebra.py +525 -0
  65. sage/algebras/schur_algebra.py +670 -0
  66. sage/algebras/shuffle_algebra.py +1011 -0
  67. sage/algebras/splitting_algebra.py +779 -0
  68. sage/algebras/tensor_algebra.py +709 -0
  69. sage/algebras/yangian.py +1082 -0
  70. sage/algebras/yokonuma_hecke_algebra.py +1018 -0
  71. sage/all__sagemath_combinat.py +44 -0
  72. sage/combinat/SJT.py +255 -0
  73. sage/combinat/affine_permutation.py +2405 -0
  74. sage/combinat/algebraic_combinatorics.py +55 -0
  75. sage/combinat/all.py +53 -0
  76. sage/combinat/all__sagemath_combinat.py +195 -0
  77. sage/combinat/alternating_sign_matrix.py +2063 -0
  78. sage/combinat/baxter_permutations.py +346 -0
  79. sage/combinat/bijectionist.py +3220 -0
  80. sage/combinat/binary_recurrence_sequences.py +1180 -0
  81. sage/combinat/blob_algebra.py +685 -0
  82. sage/combinat/catalog_partitions.py +27 -0
  83. sage/combinat/chas/all.py +23 -0
  84. sage/combinat/chas/fsym.py +1180 -0
  85. sage/combinat/chas/wqsym.py +2601 -0
  86. sage/combinat/cluster_complex.py +326 -0
  87. sage/combinat/colored_permutations.py +2039 -0
  88. sage/combinat/colored_permutations_representations.py +964 -0
  89. sage/combinat/composition_signed.py +142 -0
  90. sage/combinat/composition_tableau.py +855 -0
  91. sage/combinat/constellation.py +1729 -0
  92. sage/combinat/core.py +751 -0
  93. sage/combinat/counting.py +12 -0
  94. sage/combinat/crystals/affine.py +742 -0
  95. sage/combinat/crystals/affine_factorization.py +518 -0
  96. sage/combinat/crystals/affinization.py +331 -0
  97. sage/combinat/crystals/alcove_path.py +2013 -0
  98. sage/combinat/crystals/all.py +22 -0
  99. sage/combinat/crystals/bkk_crystals.py +141 -0
  100. sage/combinat/crystals/catalog.py +115 -0
  101. sage/combinat/crystals/catalog_elementary_crystals.py +18 -0
  102. sage/combinat/crystals/catalog_infinity_crystals.py +33 -0
  103. sage/combinat/crystals/catalog_kirillov_reshetikhin.py +18 -0
  104. sage/combinat/crystals/crystals.py +257 -0
  105. sage/combinat/crystals/direct_sum.py +260 -0
  106. sage/combinat/crystals/elementary_crystals.py +1251 -0
  107. sage/combinat/crystals/fast_crystals.py +441 -0
  108. sage/combinat/crystals/fully_commutative_stable_grothendieck.py +1205 -0
  109. sage/combinat/crystals/generalized_young_walls.py +1076 -0
  110. sage/combinat/crystals/highest_weight_crystals.py +436 -0
  111. sage/combinat/crystals/induced_structure.py +695 -0
  112. sage/combinat/crystals/infinity_crystals.py +730 -0
  113. sage/combinat/crystals/kac_modules.py +863 -0
  114. sage/combinat/crystals/kirillov_reshetikhin.py +4196 -0
  115. sage/combinat/crystals/kyoto_path_model.py +497 -0
  116. sage/combinat/crystals/letters.cp314t-win_amd64.pyd +0 -0
  117. sage/combinat/crystals/letters.pxd +79 -0
  118. sage/combinat/crystals/letters.pyx +3056 -0
  119. sage/combinat/crystals/littelmann_path.py +1518 -0
  120. sage/combinat/crystals/monomial_crystals.py +1262 -0
  121. sage/combinat/crystals/multisegments.py +462 -0
  122. sage/combinat/crystals/mv_polytopes.py +467 -0
  123. sage/combinat/crystals/pbw_crystal.py +511 -0
  124. sage/combinat/crystals/pbw_datum.cp314t-win_amd64.pyd +0 -0
  125. sage/combinat/crystals/pbw_datum.pxd +4 -0
  126. sage/combinat/crystals/pbw_datum.pyx +487 -0
  127. sage/combinat/crystals/polyhedral_realization.py +372 -0
  128. sage/combinat/crystals/spins.cp314t-win_amd64.pyd +0 -0
  129. sage/combinat/crystals/spins.pxd +21 -0
  130. sage/combinat/crystals/spins.pyx +756 -0
  131. sage/combinat/crystals/star_crystal.py +290 -0
  132. sage/combinat/crystals/subcrystal.py +464 -0
  133. sage/combinat/crystals/tensor_product.py +1177 -0
  134. sage/combinat/crystals/tensor_product_element.cp314t-win_amd64.pyd +0 -0
  135. sage/combinat/crystals/tensor_product_element.pxd +35 -0
  136. sage/combinat/crystals/tensor_product_element.pyx +1870 -0
  137. sage/combinat/crystals/virtual_crystal.py +420 -0
  138. sage/combinat/cyclic_sieving_phenomenon.py +204 -0
  139. sage/combinat/debruijn_sequence.cp314t-win_amd64.pyd +0 -0
  140. sage/combinat/debruijn_sequence.pyx +355 -0
  141. sage/combinat/decorated_permutation.py +270 -0
  142. sage/combinat/degree_sequences.cp314t-win_amd64.pyd +0 -0
  143. sage/combinat/degree_sequences.pyx +588 -0
  144. sage/combinat/derangements.py +527 -0
  145. sage/combinat/descent_algebra.py +1008 -0
  146. sage/combinat/diagram.py +1551 -0
  147. sage/combinat/diagram_algebras.py +5886 -0
  148. sage/combinat/dyck_word.py +4349 -0
  149. sage/combinat/e_one_star.py +1623 -0
  150. sage/combinat/enumerated_sets.py +123 -0
  151. sage/combinat/expnums.cp314t-win_amd64.pyd +0 -0
  152. sage/combinat/expnums.pyx +148 -0
  153. sage/combinat/fast_vector_partitions.cp314t-win_amd64.pyd +0 -0
  154. sage/combinat/fast_vector_partitions.pyx +346 -0
  155. sage/combinat/fqsym.py +1977 -0
  156. sage/combinat/free_dendriform_algebra.py +954 -0
  157. sage/combinat/free_prelie_algebra.py +1141 -0
  158. sage/combinat/fully_commutative_elements.py +1077 -0
  159. sage/combinat/fully_packed_loop.py +1523 -0
  160. sage/combinat/gelfand_tsetlin_patterns.py +1409 -0
  161. sage/combinat/gray_codes.py +311 -0
  162. sage/combinat/grossman_larson_algebras.py +667 -0
  163. sage/combinat/growth.py +4352 -0
  164. sage/combinat/hall_polynomial.py +188 -0
  165. sage/combinat/hillman_grassl.py +866 -0
  166. sage/combinat/integer_matrices.py +329 -0
  167. sage/combinat/integer_vectors_mod_permgroup.py +1238 -0
  168. sage/combinat/k_tableau.py +4564 -0
  169. sage/combinat/kazhdan_lusztig.py +215 -0
  170. sage/combinat/key_polynomial.py +885 -0
  171. sage/combinat/knutson_tao_puzzles.py +2286 -0
  172. sage/combinat/lr_tableau.py +311 -0
  173. sage/combinat/matrices/all.py +24 -0
  174. sage/combinat/matrices/hadamard_matrix.py +3790 -0
  175. sage/combinat/matrices/latin.py +2912 -0
  176. sage/combinat/misc.py +401 -0
  177. sage/combinat/multiset_partition_into_sets_ordered.py +3541 -0
  178. sage/combinat/ncsf_qsym/all.py +21 -0
  179. sage/combinat/ncsf_qsym/combinatorics.py +317 -0
  180. sage/combinat/ncsf_qsym/generic_basis_code.py +1427 -0
  181. sage/combinat/ncsf_qsym/ncsf.py +5637 -0
  182. sage/combinat/ncsf_qsym/qsym.py +4053 -0
  183. sage/combinat/ncsf_qsym/tutorial.py +447 -0
  184. sage/combinat/ncsym/all.py +21 -0
  185. sage/combinat/ncsym/bases.py +855 -0
  186. sage/combinat/ncsym/dual.py +593 -0
  187. sage/combinat/ncsym/ncsym.py +2076 -0
  188. sage/combinat/necklace.py +551 -0
  189. sage/combinat/non_decreasing_parking_function.py +634 -0
  190. sage/combinat/nu_dyck_word.py +1474 -0
  191. sage/combinat/output.py +861 -0
  192. sage/combinat/parallelogram_polyomino.py +4326 -0
  193. sage/combinat/parking_functions.py +1602 -0
  194. sage/combinat/partition_algebra.py +1998 -0
  195. sage/combinat/partition_kleshchev.py +1982 -0
  196. sage/combinat/partition_shifting_algebras.py +584 -0
  197. sage/combinat/partition_tuple.py +3114 -0
  198. sage/combinat/path_tableaux/all.py +13 -0
  199. sage/combinat/path_tableaux/catalog.py +29 -0
  200. sage/combinat/path_tableaux/dyck_path.py +380 -0
  201. sage/combinat/path_tableaux/frieze.py +476 -0
  202. sage/combinat/path_tableaux/path_tableau.py +728 -0
  203. sage/combinat/path_tableaux/semistandard.py +510 -0
  204. sage/combinat/perfect_matching.py +779 -0
  205. sage/combinat/plane_partition.py +3300 -0
  206. sage/combinat/q_bernoulli.cp314t-win_amd64.pyd +0 -0
  207. sage/combinat/q_bernoulli.pyx +128 -0
  208. sage/combinat/quickref.py +81 -0
  209. sage/combinat/recognizable_series.py +2051 -0
  210. sage/combinat/regular_sequence.py +4316 -0
  211. sage/combinat/regular_sequence_bounded.py +543 -0
  212. sage/combinat/restricted_growth.py +81 -0
  213. sage/combinat/ribbon.py +20 -0
  214. sage/combinat/ribbon_shaped_tableau.py +489 -0
  215. sage/combinat/ribbon_tableau.py +1180 -0
  216. sage/combinat/rigged_configurations/all.py +46 -0
  217. sage/combinat/rigged_configurations/bij_abstract_class.py +548 -0
  218. sage/combinat/rigged_configurations/bij_infinity.py +370 -0
  219. sage/combinat/rigged_configurations/bij_type_A.py +163 -0
  220. sage/combinat/rigged_configurations/bij_type_A2_dual.py +338 -0
  221. sage/combinat/rigged_configurations/bij_type_A2_even.py +218 -0
  222. sage/combinat/rigged_configurations/bij_type_A2_odd.py +199 -0
  223. sage/combinat/rigged_configurations/bij_type_B.py +900 -0
  224. sage/combinat/rigged_configurations/bij_type_C.py +267 -0
  225. sage/combinat/rigged_configurations/bij_type_D.py +771 -0
  226. sage/combinat/rigged_configurations/bij_type_D_tri.py +392 -0
  227. sage/combinat/rigged_configurations/bij_type_D_twisted.py +576 -0
  228. sage/combinat/rigged_configurations/bij_type_E67.py +402 -0
  229. sage/combinat/rigged_configurations/bijection.py +143 -0
  230. sage/combinat/rigged_configurations/kleber_tree.py +1475 -0
  231. sage/combinat/rigged_configurations/kr_tableaux.py +1898 -0
  232. sage/combinat/rigged_configurations/rc_crystal.py +461 -0
  233. sage/combinat/rigged_configurations/rc_infinity.py +540 -0
  234. sage/combinat/rigged_configurations/rigged_configuration_element.py +2403 -0
  235. sage/combinat/rigged_configurations/rigged_configurations.py +1918 -0
  236. sage/combinat/rigged_configurations/rigged_partition.cp314t-win_amd64.pyd +0 -0
  237. sage/combinat/rigged_configurations/rigged_partition.pxd +15 -0
  238. sage/combinat/rigged_configurations/rigged_partition.pyx +680 -0
  239. sage/combinat/rigged_configurations/tensor_product_kr_tableaux.py +499 -0
  240. sage/combinat/rigged_configurations/tensor_product_kr_tableaux_element.py +428 -0
  241. sage/combinat/rsk.py +3438 -0
  242. sage/combinat/schubert_polynomial.py +508 -0
  243. sage/combinat/set_partition.py +3318 -0
  244. sage/combinat/set_partition_iterator.cp314t-win_amd64.pyd +0 -0
  245. sage/combinat/set_partition_iterator.pyx +136 -0
  246. sage/combinat/set_partition_ordered.py +1590 -0
  247. sage/combinat/sf/abreu_nigro.py +346 -0
  248. sage/combinat/sf/all.py +52 -0
  249. sage/combinat/sf/character.py +576 -0
  250. sage/combinat/sf/classical.py +319 -0
  251. sage/combinat/sf/dual.py +996 -0
  252. sage/combinat/sf/elementary.py +549 -0
  253. sage/combinat/sf/hall_littlewood.py +1028 -0
  254. sage/combinat/sf/hecke.py +336 -0
  255. sage/combinat/sf/homogeneous.py +464 -0
  256. sage/combinat/sf/jack.py +1428 -0
  257. sage/combinat/sf/k_dual.py +1458 -0
  258. sage/combinat/sf/kfpoly.py +447 -0
  259. sage/combinat/sf/llt.py +789 -0
  260. sage/combinat/sf/macdonald.py +2019 -0
  261. sage/combinat/sf/monomial.py +525 -0
  262. sage/combinat/sf/multiplicative.py +113 -0
  263. sage/combinat/sf/new_kschur.py +1786 -0
  264. sage/combinat/sf/ns_macdonald.py +964 -0
  265. sage/combinat/sf/orthogonal.py +246 -0
  266. sage/combinat/sf/orthotriang.py +355 -0
  267. sage/combinat/sf/powersum.py +963 -0
  268. sage/combinat/sf/schur.py +880 -0
  269. sage/combinat/sf/sf.py +1653 -0
  270. sage/combinat/sf/sfa.py +7053 -0
  271. sage/combinat/sf/symplectic.py +253 -0
  272. sage/combinat/sf/witt.py +721 -0
  273. sage/combinat/shifted_primed_tableau.py +2735 -0
  274. sage/combinat/shuffle.py +830 -0
  275. sage/combinat/sidon_sets.py +146 -0
  276. sage/combinat/similarity_class_type.py +1721 -0
  277. sage/combinat/sine_gordon.py +618 -0
  278. sage/combinat/six_vertex_model.py +784 -0
  279. sage/combinat/skew_partition.py +2053 -0
  280. sage/combinat/skew_tableau.py +2989 -0
  281. sage/combinat/sloane_functions.py +8935 -0
  282. sage/combinat/specht_module.py +1403 -0
  283. sage/combinat/species/all.py +48 -0
  284. sage/combinat/species/characteristic_species.py +321 -0
  285. sage/combinat/species/composition_species.py +273 -0
  286. sage/combinat/species/cycle_species.py +284 -0
  287. sage/combinat/species/empty_species.py +155 -0
  288. sage/combinat/species/functorial_composition_species.py +148 -0
  289. sage/combinat/species/generating_series.py +673 -0
  290. sage/combinat/species/library.py +148 -0
  291. sage/combinat/species/linear_order_species.py +169 -0
  292. sage/combinat/species/misc.py +83 -0
  293. sage/combinat/species/partition_species.py +290 -0
  294. sage/combinat/species/permutation_species.py +268 -0
  295. sage/combinat/species/product_species.py +423 -0
  296. sage/combinat/species/recursive_species.py +476 -0
  297. sage/combinat/species/set_species.py +192 -0
  298. sage/combinat/species/species.py +820 -0
  299. sage/combinat/species/structure.py +539 -0
  300. sage/combinat/species/subset_species.py +243 -0
  301. sage/combinat/species/sum_species.py +225 -0
  302. sage/combinat/subword.py +564 -0
  303. sage/combinat/subword_complex.py +2122 -0
  304. sage/combinat/subword_complex_c.cp314t-win_amd64.pyd +0 -0
  305. sage/combinat/subword_complex_c.pyx +119 -0
  306. sage/combinat/super_tableau.py +821 -0
  307. sage/combinat/superpartition.py +1154 -0
  308. sage/combinat/symmetric_group_algebra.py +3774 -0
  309. sage/combinat/symmetric_group_representations.py +1830 -0
  310. sage/combinat/t_sequences.py +877 -0
  311. sage/combinat/tableau.py +9506 -0
  312. sage/combinat/tableau_residues.py +860 -0
  313. sage/combinat/tableau_tuple.py +5353 -0
  314. sage/combinat/tiling.py +2432 -0
  315. sage/combinat/triangles_FHM.py +777 -0
  316. sage/combinat/tutorial.py +1857 -0
  317. sage/combinat/vector_partition.py +337 -0
  318. sage/combinat/words/abstract_word.py +1722 -0
  319. sage/combinat/words/all.py +59 -0
  320. sage/combinat/words/alphabet.py +268 -0
  321. sage/combinat/words/finite_word.py +7201 -0
  322. sage/combinat/words/infinite_word.py +113 -0
  323. sage/combinat/words/lyndon_word.py +652 -0
  324. sage/combinat/words/morphic.py +351 -0
  325. sage/combinat/words/morphism.py +3878 -0
  326. sage/combinat/words/paths.py +2932 -0
  327. sage/combinat/words/shuffle_product.py +278 -0
  328. sage/combinat/words/suffix_trees.py +1873 -0
  329. sage/combinat/words/word.py +769 -0
  330. sage/combinat/words/word_char.cp314t-win_amd64.pyd +0 -0
  331. sage/combinat/words/word_char.pyx +847 -0
  332. sage/combinat/words/word_datatypes.cp314t-win_amd64.pyd +0 -0
  333. sage/combinat/words/word_datatypes.pxd +4 -0
  334. sage/combinat/words/word_datatypes.pyx +1067 -0
  335. sage/combinat/words/word_generators.py +2026 -0
  336. sage/combinat/words/word_infinite_datatypes.py +1218 -0
  337. sage/combinat/words/word_options.py +99 -0
  338. sage/combinat/words/words.py +2396 -0
  339. sage/data_structures/all__sagemath_combinat.py +1 -0
  340. sage/databases/all__sagemath_combinat.py +13 -0
  341. sage/databases/findstat.py +4897 -0
  342. sage/databases/oeis.py +2058 -0
  343. sage/databases/sloane.py +393 -0
  344. sage/dynamics/all__sagemath_combinat.py +14 -0
  345. sage/dynamics/cellular_automata/all.py +7 -0
  346. sage/dynamics/cellular_automata/catalog.py +34 -0
  347. sage/dynamics/cellular_automata/elementary.py +612 -0
  348. sage/dynamics/cellular_automata/glca.py +477 -0
  349. sage/dynamics/cellular_automata/solitons.py +1463 -0
  350. sage/dynamics/finite_dynamical_system.py +1249 -0
  351. sage/dynamics/finite_dynamical_system_catalog.py +382 -0
  352. sage/games/all.py +7 -0
  353. sage/games/hexad.py +704 -0
  354. sage/games/quantumino.py +591 -0
  355. sage/games/sudoku.py +889 -0
  356. sage/games/sudoku_backtrack.cp314t-win_amd64.pyd +0 -0
  357. sage/games/sudoku_backtrack.pyx +189 -0
  358. sage/groups/all__sagemath_combinat.py +1 -0
  359. sage/groups/indexed_free_group.py +489 -0
  360. sage/libs/all__sagemath_combinat.py +6 -0
  361. sage/libs/lrcalc/__init__.py +1 -0
  362. sage/libs/lrcalc/lrcalc.py +525 -0
  363. sage/libs/symmetrica/__init__.py +7 -0
  364. sage/libs/symmetrica/all.py +101 -0
  365. sage/libs/symmetrica/kostka.pxi +168 -0
  366. sage/libs/symmetrica/part.pxi +193 -0
  367. sage/libs/symmetrica/plet.pxi +42 -0
  368. sage/libs/symmetrica/sab.pxi +196 -0
  369. sage/libs/symmetrica/sb.pxi +332 -0
  370. sage/libs/symmetrica/sc.pxi +192 -0
  371. sage/libs/symmetrica/schur.pxi +956 -0
  372. sage/libs/symmetrica/symmetrica.cp314t-win_amd64.pyd +0 -0
  373. sage/libs/symmetrica/symmetrica.pxi +1172 -0
  374. sage/libs/symmetrica/symmetrica.pyx +39 -0
  375. sage/monoids/all.py +13 -0
  376. sage/monoids/automatic_semigroup.py +1054 -0
  377. sage/monoids/free_abelian_monoid.py +315 -0
  378. sage/monoids/free_abelian_monoid_element.cp314t-win_amd64.pyd +0 -0
  379. sage/monoids/free_abelian_monoid_element.pxd +16 -0
  380. sage/monoids/free_abelian_monoid_element.pyx +397 -0
  381. sage/monoids/free_monoid.py +335 -0
  382. sage/monoids/free_monoid_element.py +431 -0
  383. sage/monoids/hecke_monoid.py +65 -0
  384. sage/monoids/string_monoid.py +817 -0
  385. sage/monoids/string_monoid_element.py +547 -0
  386. sage/monoids/string_ops.py +143 -0
  387. sage/monoids/trace_monoid.py +972 -0
  388. sage/rings/all__sagemath_combinat.py +2 -0
  389. sage/sat/all.py +4 -0
  390. sage/sat/boolean_polynomials.py +405 -0
  391. sage/sat/converters/__init__.py +6 -0
  392. sage/sat/converters/anf2cnf.py +14 -0
  393. sage/sat/converters/polybori.py +611 -0
  394. sage/sat/solvers/__init__.py +5 -0
  395. sage/sat/solvers/cryptominisat.py +287 -0
  396. sage/sat/solvers/dimacs.py +783 -0
  397. sage/sat/solvers/picosat.py +228 -0
  398. sage/sat/solvers/sat_lp.py +156 -0
  399. sage/sat/solvers/satsolver.cp314t-win_amd64.pyd +0 -0
  400. sage/sat/solvers/satsolver.pxd +3 -0
  401. 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