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,4897 @@
1
+ # sage_setup: distribution = sagemath-combinat
2
+ # sage.doctest: needs requests sage.combinat
3
+ r"""
4
+ FindStat - the search engine for combinatorial statistics and maps
5
+
6
+ The interface to the FindStat database is ::
7
+
8
+ sage: findstat()
9
+ The Combinatorial Statistic Finder (https://www.findstat.org/)
10
+
11
+ We use the following three notions
12
+
13
+ - A *combinatorial collection* is a set `S` with interesting combinatorial properties,
14
+ - a *combinatorial map* is a combinatorially interesting map `f: S \to S'` between combinatorial collections, and
15
+ - a *combinatorial statistic* is a combinatorially interesting map `s: S \to \ZZ`.
16
+
17
+ You can use the FindStat interface to
18
+
19
+ - identify a combinatorial statistic or map given the values on a few small objects,
20
+ - obtain more terms, formulae, references, etc. for a given statistic or map,
21
+ - edit statistics and maps and submit new statistics.
22
+
23
+ The main entry points to the database are
24
+
25
+ - :func:`findstat` to search for matching statistics,
26
+ - :func:`findmap` to search for matching maps.
27
+
28
+ A guided tour
29
+ -------------
30
+
31
+ Retrieving information
32
+ ^^^^^^^^^^^^^^^^^^^^^^
33
+
34
+ The most straightforward application of the FindStat interface is to
35
+ gather information about a combinatorial statistic. To do this, we
36
+ supply :func:`findstat` with a list of ``(object, value)`` pairs.
37
+ For example::
38
+
39
+ sage: PM = PerfectMatchings
40
+ sage: r = findstat([(m, m.number_of_nestings()) for n in range(6) for m in PM(2*n)], depth=1); r # optional -- internet
41
+ 0: St000042oMp00116 (quality [100, 100])
42
+ 1: St000041 (quality [20, 100])
43
+ ...
44
+
45
+ The result of this query is a list (presented as a
46
+ :class:`sage.databases.oeis.FancyTuple`) of matches. Each match
47
+ consists of a :class:`FindStatCompoundStatistic` `s: S \to \ZZ` and
48
+ an indication of the quality of the match.
49
+
50
+ The precise meaning of the result is as follows:
51
+
52
+ The composition `f_n \circ ... \circ f_2 \circ f_1` applied to
53
+ the objects sent to FindStat agrees with all ``(object, value)``
54
+ pairs of `s` in the database. The optional parameter ``depth=1``
55
+ limits the output to `n=1`.
56
+
57
+ Suppose that the quality of the match is `(q_a, q_d)`. Then
58
+ `q_a` is the percentage of ``(object, value)`` pairs that are in
59
+ the database among those which were sent to FindStat, and `q_d`
60
+ is the percentage of ``(object, value)`` pairs with distinct
61
+ values in the database among those which were sent to FindStat.
62
+
63
+ Put differently, if ``quality`` is not too small it is likely that
64
+ the statistic sent to FindStat equals `s \circ f_n \circ ... \circ
65
+ f_2 \circ f_1`. If `q_a` is large, but `q_b` is small, then there
66
+ were many matches, but while the sought for statistic attains many
67
+ distinct values, the match found by FindStat covers only ``(object,
68
+ value)`` pairs for few values.
69
+
70
+ In the case at hand, for the match ``St000041``, the list of maps is
71
+ empty. We can retrieve the description of the statistic from the
72
+ database as follows::
73
+
74
+ sage: print(r[1].statistic().description()) # optional -- internet
75
+ The number of nestings of a perfect matching.
76
+ <BLANKLINE>
77
+ <BLANKLINE>
78
+ This is the number of pairs of edges $((a,b), (c,d))$ such that $a\le c\le d\le b$. i.e., the edge $(c,d)$ is nested inside $(a,b)$...
79
+
80
+ We can check the references::
81
+
82
+ sage: r[1].statistic().references() # optional -- internet
83
+ 0: [1] de Médicis, A., Viennot, X. G., Moments des $q$-polynômes de Laguerre et la bijection de Foata-Zeilberger [[MathSciNet:1288802]]
84
+ 1: [2] Simion, R., Stanton, D., Octabasic Laguerre polynomials and permutation statistics [[MathSciNet:1418763]]...
85
+
86
+ If you prefer, you can look at this information also in your browser::
87
+
88
+ sage: r[1].statistic().browse() # optional -- webbrowser
89
+
90
+ Another interesting possibility is to look for equidistributed
91
+ statistics. Instead of submitting a list of ``(object, value)``
92
+ pairs, we pass a list of pairs ``(objects, values)``::
93
+
94
+ sage: data = [(PM(2*n), [m.number_of_nestings() for m in PM(2*n)]) for n in range(5)]
95
+ sage: findstat(data, depth=0) # optional -- internet
96
+ 0: St000041 (quality [99, 100])
97
+ 1: St000042 (quality [99, 100])
98
+
99
+ This results tells us that the database contains another entry that
100
+ is equidistributed with the number of nestings on perfect matchings
101
+ of size at most `10`, namely the number of crossings. Note that
102
+ there is a limit on the number of elements FindStat accepts for a
103
+ query, which is currently `1000`. Queries with more than `1000`
104
+ elements are truncated.
105
+
106
+ Let us now look at a slightly more complicated example, where the
107
+ submitted statistic is the composition of a sequence of combinatorial
108
+ maps and a statistic known to FindStat. We use the occasion to
109
+ advertise yet another way to pass values to FindStat::
110
+
111
+ sage: r = findstat(Permutations, lambda pi: pi.saliances()[0], depth=2); r # optional -- internet
112
+ 0: St000740oMp00066 with offset 1 (quality [100, 100])
113
+ 1: St000740oMp00087 with offset 1 (quality [100, 100])
114
+ ...
115
+
116
+ Note that some of the matches are up to a global offset. For
117
+ example, we have::
118
+
119
+ sage: r[1].info() # optional -- internet
120
+ after adding 1 to every value
121
+ and applying
122
+ Mp00087: inverse first fundamental transformation: Permutations -> Permutations
123
+ to the objects (see `.compound_map()` for details)
124
+ <BLANKLINE>
125
+ your input matches
126
+ St000740: The last entry of a permutation.
127
+ <BLANKLINE>
128
+ among the values you sent, 100 percent are actually in the database,
129
+ among the distinct values you sent, 100 percent are actually in the database
130
+
131
+ Let us pick another particular result::
132
+
133
+ sage: s = findstat("St000051oMp00061oMp00069") # optional -- internet
134
+ sage: s.info() # optional -- internet
135
+ Mp00069: complement: Permutations -> Permutations
136
+ Mp00061: to increasing tree: Permutations -> Binary trees
137
+ St000051: The size of the left subtree of a binary tree.
138
+
139
+ To obtain the value of the statistic sent to FindStat on a given
140
+ object, apply the maps in the list in the given order to this object,
141
+ and evaluate the statistic on the result. For example, let us check
142
+ that the result given by FindStat agrees with our statistic on the
143
+ following permutation::
144
+
145
+ sage: pi = Permutation([3,1,4,5,2]); pi.saliances()[0]
146
+ 3
147
+
148
+ We first have to find out, what the maps and the statistic actually do::
149
+
150
+ sage: print(s.statistic().description()) # optional -- internet
151
+ The size of the left subtree of a binary tree.
152
+
153
+ sage: print(s.statistic().sage_code()) # optional -- internet
154
+ def statistic(T):
155
+ return T[0].node_number()
156
+
157
+ sage: print("\n\n".join(m.sage_code() for m in s.compound_map())) # optional -- internet
158
+ def mapping(sigma):
159
+ return sigma.complement()
160
+ <BLANKLINE>
161
+ def mapping(sigma):
162
+ return sigma.increasing_tree_shape()
163
+
164
+ So, the following should coincide with what we sent FindStat::
165
+
166
+ sage: pi.complement().increasing_tree_shape()[0].node_number()
167
+ 3
168
+
169
+ Editing and submitting statistics
170
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
171
+
172
+ Of course, often a statistic will not be in the database::
173
+
174
+ sage: s = findstat([(d, randint(1,1000)) for d in DyckWords(4)]); s # optional -- internet
175
+ St000000: a new statistic on Dyck paths
176
+
177
+ In this case, and if the statistic might be "interesting", please
178
+ consider submitting it to the database using
179
+ :meth:`FindStatStatistic.submit`.
180
+
181
+ Also, you may notice omissions, typos or even mistakes in the
182
+ description, the code and the references. In this case, simply
183
+ replace the value by using :meth:`FindStatFunction.set_description`,
184
+ :meth:`FindStatStatistic.set_code` or
185
+ :meth:`FindStatFunction.set_references_raw`, and then
186
+ :meth:`FindStatStatistic.submit` your changes for review by the
187
+ FindStat team.
188
+
189
+ AUTHORS:
190
+
191
+ - Martin Rubey (2015): initial version
192
+ - Martin Rubey (2020): rewrite, adapt to new FindStat API
193
+ """
194
+ # ****************************************************************************
195
+ # Copyright (C) 2015 Martin Rubey <martin.rubey@tuwien.ac.at>,
196
+ #
197
+ # Distributed under the terms of the GNU General Public License (GPL)
198
+ # as published by the Free Software Foundation; either version 2 of
199
+ # the License, or (at your option) any later version.
200
+ # https://www.gnu.org/licenses/
201
+ # ****************************************************************************
202
+ from sage.misc.lazy_list import lazy_list
203
+ from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass
204
+ from sage.structure.element import Element
205
+ from sage.structure.parent import Parent
206
+ from sage.structure.unique_representation import UniqueRepresentation
207
+
208
+ from sage.categories.sets_cat import Sets
209
+ from sage.structure.sage_object import SageObject
210
+ from sage.structure.richcmp import richcmp
211
+
212
+ from sage.misc.verbose import verbose
213
+ from sage.rings.integer import Integer
214
+ from sage.databases.oeis import FancyTuple
215
+
216
+ from ast import literal_eval
217
+ from copy import deepcopy
218
+ from pathlib import Path
219
+ import re
220
+ import webbrowser
221
+ import tempfile
222
+ import inspect
223
+ import html
224
+ import requests
225
+ from json.decoder import JSONDecodeError
226
+ import itertools
227
+
228
+ # Combinatorial collections
229
+ from sage.combinat.alternating_sign_matrix import AlternatingSignMatrix, AlternatingSignMatrices
230
+ from sage.combinat.binary_tree import BinaryTree, BinaryTrees
231
+ from sage.combinat.core import Core, Cores
232
+ from sage.combinat.dyck_word import DyckWord, DyckWords
233
+ from sage.combinat.root_system.cartan_type import CartanType_abstract, CartanType
234
+ from sage.combinat.gelfand_tsetlin_patterns import GelfandTsetlinPattern, GelfandTsetlinPatterns
235
+ from sage.graphs.graph import Graph
236
+ from sage.combinat.composition import Composition, Compositions
237
+ from sage.combinat.partition import Partition, Partitions
238
+ from sage.combinat.ordered_tree import OrderedTree, OrderedTrees
239
+ from sage.combinat.parking_functions import ParkingFunction, ParkingFunctions
240
+ from sage.combinat.perfect_matching import PerfectMatching, PerfectMatchings
241
+ from sage.combinat.permutation import Permutation, Permutations
242
+ from sage.combinat.posets.posets import Poset, FinitePoset
243
+ from sage.combinat.posets.lattices import LatticePoset, FiniteLatticePoset
244
+ from sage.combinat.posets.poset_examples import Posets
245
+ from sage.combinat.tableau import SemistandardTableau, SemistandardTableaux, StandardTableau, StandardTableaux
246
+ from sage.combinat.set_partition import SetPartition, SetPartitions
247
+ from sage.combinat.skew_partition import SkewPartition, SkewPartitions
248
+ from sage.graphs.graph_generators import graphs
249
+ from sage.combinat.words.word import Word
250
+ from sage.combinat.words.words import Words
251
+ from sage.combinat.words.abstract_word import Word_class
252
+ from sage.combinat.colored_permutations import SignedPermutation, SignedPermutations
253
+ from sage.combinat.plane_partition import PlanePartition
254
+ from sage.combinat.decorated_permutation import DecoratedPermutation, DecoratedPermutations
255
+ from sage.combinat.set_partition_ordered import OrderedSetPartition, OrderedSetPartitions
256
+
257
+ ######################################################################
258
+ # the FindStat URLs
259
+ FINDSTAT_URL = 'https://www.findstat.org/'
260
+ FINDSTAT_API = FINDSTAT_URL + "api/"
261
+ FINDSTAT_API_COLLECTIONS = FINDSTAT_API + 'CollectionsDatabase/'
262
+ FINDSTAT_API_STATISTICS = FINDSTAT_API + 'StatisticsDatabase/'
263
+ FINDSTAT_API_MAPS = FINDSTAT_API + 'MapsDatabase/'
264
+
265
+ FINDSTAT_URL_LOGIN = FINDSTAT_URL + "?action=login"
266
+ FINDSTAT_URL_COLLECTIONS = FINDSTAT_URL + 'CollectionsDatabase/'
267
+ FINDSTAT_URL_STATISTICS = FINDSTAT_URL + 'StatisticsDatabase/'
268
+ FINDSTAT_URL_EDIT_STATISTIC = FINDSTAT_URL + 'EditStatistic/'
269
+ FINDSTAT_URL_NEW_STATISTIC = FINDSTAT_URL + 'NewStatistic/'
270
+ FINDSTAT_URL_MAPS = FINDSTAT_URL + 'MapsDatabase/'
271
+ FINDSTAT_URL_EDIT_MAP = FINDSTAT_URL + 'EditMap/'
272
+ FINDSTAT_URL_NEW_MAP = FINDSTAT_URL + 'NewMap/'
273
+
274
+ ######################################################################
275
+ # the number of values FindStat allows to search for at most
276
+ FINDSTAT_MAX_VALUES = 1000
277
+ # the number of values FindStat needs at least to search for
278
+ FINDSTAT_MIN_VALUES = 3
279
+ # the maximal number of maps that FindStat composes by default to find a match
280
+ FINDSTAT_DEFAULT_DEPTH = 2
281
+ # the number of values FindStat allows to submit at most
282
+ FINDSTAT_MAX_SUBMISSION_VALUES = 1200
283
+
284
+ # separates name from description
285
+ FINDSTAT_SEPARATOR_NAME = "\n"
286
+ # separates values
287
+ FINDSTAT_VALUE_SEPARATOR = ";"
288
+ FINDSTAT_MAP_SEPARATOR = "o"
289
+ # regexp to recognize a statistic identifier
290
+ FINDSTAT_STATISTIC_REGEXP = '^St[0-9]{6}$'
291
+ FINDSTAT_MAP_REGEXP = '^Mp[0-9]{5}$'
292
+ FINDSTAT_COLLECTION_REGEXP = '^Cc[0-9]{4}$'
293
+ FINDSTAT_STATISTIC_PADDED_IDENTIFIER = "St%06d"
294
+ FINDSTAT_MAP_PADDED_IDENTIFIER = "Mp%05d"
295
+ FINDSTAT_COLLECTION_PADDED_IDENTIFIER = "Cc%04d"
296
+
297
+ ######################################################################
298
+
299
+ # the format string for using POST
300
+ # WARNING: we use html.escape to avoid injection problems, thus we expect double quotes as field delimiters.
301
+ FINDSTAT_POST_HEADER = """
302
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
303
+ <script>
304
+ $(document).ready(function() {$("#form").submit(); });
305
+ </script>
306
+ """
307
+ FINDSTAT_NEWSTATISTIC_FORM_HEADER = '<form id="form" name="NewStatistic" action="%s" enctype="multipart/form-data" method="post" />'
308
+ FINDSTAT_NEWMAP_FORM_HEADER = '<form id="form" name="NewMap" action="%s" enctype="multipart/form-data" method="post" />'
309
+ FINDSTAT_FORM_FORMAT = '<input type="hidden" name="%s" value="%s" />'
310
+ FINDSTAT_FORM_FOOTER = '</form>'
311
+
312
+ ######################################################################
313
+
314
+
315
+ class FindStat(UniqueRepresentation, SageObject):
316
+ r"""
317
+ The Combinatorial Statistic Finder.
318
+
319
+ :class:`FindStat` is a class preserving user information.
320
+ """
321
+ def __init__(self):
322
+ r"""
323
+ Initialize the database.
324
+
325
+ TESTS::
326
+
327
+ sage: findstat()
328
+ The Combinatorial Statistic Finder (https://www.findstat.org/)
329
+ """
330
+ # user credentials if provided
331
+ self._user_name = ""
332
+ self._user_email = ""
333
+ self._allow_execution = False
334
+
335
+ def __repr__(self):
336
+ r"""
337
+ Return the representation of ``self``.
338
+
339
+ EXAMPLES::
340
+
341
+ sage: findstat()
342
+ The Combinatorial Statistic Finder (https://www.findstat.org/)
343
+ """
344
+ return "The Combinatorial Statistic Finder (%s)" % FINDSTAT_URL
345
+
346
+ def browse(self):
347
+ r"""
348
+ Open the FindStat web page in a browser.
349
+
350
+ EXAMPLES::
351
+
352
+ sage: findstat().browse() # optional -- webbrowser
353
+ """
354
+ webbrowser.open(FINDSTAT_URL)
355
+
356
+ def set_user(self, name=None, email=None):
357
+ r"""
358
+ Set the user for the session.
359
+
360
+ INPUT:
361
+
362
+ - ``name`` -- the name of the user
363
+
364
+ - ``email`` -- an email address of the user
365
+
366
+ This information is used when submitting a statistic with
367
+ :meth:`FindStatStatistic.submit`.
368
+
369
+ EXAMPLES::
370
+
371
+ sage: findstat().set_user(name='Anonymous', email='invalid@org')
372
+
373
+ .. NOTE::
374
+
375
+ It is usually more convenient to login into the FindStat
376
+ web page using the :meth:`login` method.
377
+ """
378
+ if not isinstance(name, str):
379
+ raise ValueError("the given name (%s) should be a string" % name)
380
+ if not isinstance(email, str):
381
+ raise ValueError("the given email (%s) should be a string" % email)
382
+ self._user_name = name
383
+ self._user_email = email
384
+
385
+ def user_name(self):
386
+ """
387
+ Return the user name used for submissions.
388
+
389
+ EXAMPLES::
390
+
391
+ sage: findstat().set_user(name='Anonymous', email='invalid@org')
392
+ sage: findstat().user_name()
393
+ 'Anonymous'
394
+ """
395
+ return self._user_name
396
+
397
+ def user_email(self):
398
+ """
399
+ Return the user name used for submissions.
400
+
401
+ EXAMPLES::
402
+
403
+ sage: findstat().set_user(name='Anonymous', email='invalid@org')
404
+ sage: findstat().user_email()
405
+ 'invalid@org'
406
+ """
407
+ return self._user_email
408
+
409
+ def login(self):
410
+ r"""
411
+ Open the FindStat login page in a browser.
412
+
413
+ EXAMPLES::
414
+
415
+ sage: findstat().login() # optional -- webbrowser
416
+ """
417
+ webbrowser.open(FINDSTAT_URL_LOGIN)
418
+
419
+
420
+ ######################################################################
421
+ # tools
422
+ ######################################################################
423
+ def _get_json(url, **kwargs):
424
+ """
425
+ Return the json response or raise an error.
426
+
427
+ EXAMPLES::
428
+
429
+ sage: from sage.databases.findstat import _get_json, FINDSTAT_API_MAPS
430
+ sage: _get_json(FINDSTAT_API_MAPS + "?fields=yyy") # optional -- internet
431
+ Traceback (most recent call last):
432
+ ...
433
+ ValueError: E018: Unknown fields in Map, Combinatorial map: to semistandard tableau via monotone triangles: ['yyy']
434
+ """
435
+ response = requests.get(url)
436
+ if response.ok:
437
+ try:
438
+ result = response.json(**kwargs)
439
+ except JSONDecodeError:
440
+ raise ValueError(response.text)
441
+ if "error" in result:
442
+ raise ValueError(result["error"])
443
+ return result
444
+ raise ConnectionError(response.text)
445
+
446
+
447
+ def _post_json(url, data, **kwargs):
448
+ """
449
+ Return the json response or raise an error.
450
+
451
+ EXAMPLES::
452
+
453
+ sage: from sage.databases.findstat import _post_json, FINDSTAT_API_STATISTICS
454
+ sage: _post_json(FINDSTAT_API_STATISTICS, {"fields": "yyy"}) # optional -- internet
455
+ Traceback (most recent call last):
456
+ ...
457
+ ValueError: E018: Unknown fields in Statistic, Combinatorial statistic: St000001: ['yyy']
458
+ """
459
+ response = requests.post(url, data=data)
460
+ if response.ok:
461
+ try:
462
+ result = response.json(**kwargs)
463
+ except JSONDecodeError:
464
+ raise ValueError(response.text)
465
+ if "error" in result:
466
+ raise ValueError(result["error"])
467
+ return result
468
+ raise ConnectionError(response.text)
469
+
470
+
471
+ def _submit(args, url):
472
+ r"""
473
+ Open a post form containing fields for each of the arguments,
474
+ which is sent to the given url.
475
+
476
+ INPUT:
477
+
478
+ - args, a dictionary whose keys are the form fields
479
+
480
+ - url, the goal of the post request
481
+
482
+ EXAMPLES::
483
+
484
+ sage: from sage.databases.findstat import _submit, FINDSTAT_NEWSTATISTIC_FORM_HEADER, FINDSTAT_URL_NEW_STATISTIC
485
+ sage: url = FINDSTAT_NEWSTATISTIC_FORM_HEADER % FINDSTAT_URL_NEW_STATISTIC
486
+ sage: args = {"OriginalStatistic": "St000000",
487
+ ....: "Domain": "Cc0001",
488
+ ....: "Values": "[1,3,2]=>17\n[1,2,3]=>83",
489
+ ....: "Description": "Not a good statistic",
490
+ ....: "References": "[[arXiv:1102.4226]]",
491
+ ....: "Code": "def statistic(p):\n return 1",
492
+ ....: "SageCode": "def statistic(p):\n return 1",
493
+ ....: "CurrentAuthor": "",
494
+ ....: "CurrentEmail": ""}
495
+ sage: _submit(args, url) # optional -- webbrowser
496
+ """
497
+ f = tempfile.NamedTemporaryFile(mode='w', suffix='.html', encoding='utf-8', delete=False)
498
+ verbose("Created temporary file %s" % f.name, caller_name='FindStat')
499
+ f.write('<!doctype html>\n<html lang="en">\n<meta charset="utf-8">\n')
500
+ f.write(FINDSTAT_POST_HEADER)
501
+ f.write(url)
502
+ for key, value in args.items():
503
+ if value:
504
+ verbose("writing argument %s" % key, caller_name='FindStat')
505
+ value_encoded = html.escape(value, quote=True)
506
+ html_content = FINDSTAT_FORM_FORMAT % (key, value_encoded)
507
+ f.write(html_content)
508
+ else:
509
+ verbose("skipping argument %s because it is empty" % key, caller_name='FindStat')
510
+ f.write(FINDSTAT_FORM_FOOTER)
511
+ f.close()
512
+ verbose("Opening file with webbrowser", caller_name='FindStat')
513
+ webbrowser.open(Path(f.name).as_uri())
514
+
515
+
516
+ def _data_to_str(data, domain, codomain=None):
517
+ """
518
+ Return a string representation of the given list of ``(objects,
519
+ values)`` pairs suitable for a FindStat query.
520
+
521
+ INPUT:
522
+
523
+ - ``data`` -- list of lists of objects
524
+
525
+ - ``domain`` -- :class:`FindStatCollection`
526
+
527
+ - ``codomain`` -- (optional) :class:`FindStatCollection` or ``None``
528
+
529
+ If ``codomain`` is ``None``, the values are treated as integers.
530
+
531
+ TESTS::
532
+
533
+ sage: n = 3; l = lambda i: [pi for pi in Permutations(n) if pi(1) == i]
534
+ sage: data = [([pi for pi in l(i)], [pi(1) for pi in l(i)]) for i in range(1,n+1)]
535
+ sage: data.append(([Permutation([1,2])], [1]))
536
+ sage: from sage.databases.findstat import FindStatCollection, _data_to_str
537
+ sage: print(_data_to_str(data, FindStatCollection(1))) # optional -- internet
538
+ [1, 2, 3]
539
+ [1, 3, 2]
540
+ ====> 1;1
541
+ [2, 1, 3]
542
+ [2, 3, 1]
543
+ ====> 2;2
544
+ [3, 1, 2]
545
+ [3, 2, 1]
546
+ ====> 3;3
547
+ [1, 2]
548
+ ====> 1
549
+ """
550
+ to_str_dom = domain.to_string()
551
+ if codomain is None:
552
+ to_str_codom = str
553
+ else:
554
+ to_str_codom = codomain.to_string()
555
+
556
+ return "\n".join("\n".join(to_str_dom(element) for element in elements)
557
+ + "\n====> "
558
+ + FINDSTAT_VALUE_SEPARATOR.join(to_str_codom(value)
559
+ for value in values)
560
+ for elements, values in data)
561
+
562
+
563
+ def _data_from_iterable(iterable, mapping=False, domain=None,
564
+ codomain=None, check=True):
565
+ """
566
+ Return a list of pairs of lists of the same size, domain, and if
567
+ applicable, codomain.
568
+
569
+ INPUT:
570
+
571
+ - iterable, a pair of lists of the same size, or an iterable of
572
+ pairs, such that every pair consists of two iterables of the
573
+ same size, or a single element and a single value. Every
574
+ object must be a :class:`SageObject`.
575
+
576
+ - ``mapping`` -- boolean (default: ``False``); ``False``, if the codomain
577
+ is ``Integer`` and ``True`` if it is a FindStat collection
578
+
579
+ - ``domain`` -- (default: ``None``) a :class:`FindStatCollection`, if
580
+ ``None`` it is guessed from the iterable
581
+
582
+ - ``codomain`` -- (default: ``None``) a :class:`FindStatCollection`, if
583
+ ``None`` it is guessed from the iterable
584
+
585
+ TESTS::
586
+
587
+ sage: from sage.databases.findstat import FindStatCollection, _data_from_iterable
588
+ sage: PM = PerfectMatchings
589
+ sage: l = [(PM(2*n), [m.number_of_nestings() for m in PM(2*n)]) for n in range(5)]
590
+ sage: _data_from_iterable(l) # optional -- internet
591
+ (lazy list [([[]], [0]), ([[(1, 2)]], [0]), ([[(1, 2), (3, 4)], [(1, 3), (2, 4)], [(1, 4), (2, 3)]], [0, 0, 1]), ...],
592
+ a subset of Cc0012: Perfect matchings)
593
+ sage: domain = FindStatCollection("Set Partitions") # optional -- internet
594
+ sage: _data_from_iterable(l, domain=domain) # optional -- internet
595
+ (lazy list [([[]], [0]), ([[(1, 2)]], [0]), ([[(1, 2), (3, 4)], [(1, 3), (2, 4)], [(1, 4), (2, 3)]], [0, 0, 1]), ...],
596
+ Cc0009: Set partitions)
597
+
598
+ Check that passing a list with a single ``(elements, values)`` pair works::
599
+
600
+ sage: l = [(PM(4), [0, 0, 1])]
601
+ sage: _data_from_iterable(l, domain=domain) # optional -- internet
602
+ (lazy list [([[(1, 2), (3, 4)], [(1, 3), (2, 4)], [(1, 4), (2, 3)]], [0, 0, 1])],
603
+ Cc0009: Set partitions)
604
+ """
605
+ if isinstance(iterable, dict):
606
+ iterator = iter(iterable.items())
607
+ else:
608
+ iterator = iter(iterable)
609
+ query0 = next(iterator)
610
+ try:
611
+ query1 = next(iterator)
612
+ except StopIteration:
613
+ # query0 == (elts, vals), which we interpret as [(elts, vals)]
614
+ query0, query1 = query0[0], query0[1]
615
+ # next(iterator) will raise StopIteration
616
+ try:
617
+ query2 = next(iterator)
618
+ pre_data = [query0, query1, query2]
619
+ except StopIteration:
620
+ # (query0, query1) == (elts, vals) or
621
+ # (query0, query1) == [(elts, vals), (elts, vals)]
622
+ # in the former case, elts has at least 3 elements
623
+ # in both cases, query0 and query1 are not objects
624
+ # if query0 is a long list, we have to raise an error anyway
625
+ elts, vals = list(query0), list(query1)
626
+ if len(elts) == 2:
627
+ if len(vals) != 2:
628
+ raise ValueError("cannot interpret the given argument as a FindStat query")
629
+ pre_data = [elts, vals]
630
+ else:
631
+ if len(elts) != len(vals):
632
+ raise ValueError("FindStat expects the same number of objects (got %s) as values (got %s)" % (len(elts), len(vals)))
633
+ pre_data = [(elts, vals)]
634
+
635
+ # pre_data is a list of all elements of the iterator accessed so
636
+ # far, for each of its elements and also the remainder ot the
637
+ # iterator, each element is either a pair ``(object, value)`` or
638
+ # a pair ``(objects, values)``
639
+ elts, vals = pre_data[0]
640
+ if domain is None:
641
+ domain = FindStatCollection(elts)
642
+ if mapping and codomain is None:
643
+ codomain = FindStatCollection(vals)
644
+
645
+ all_elements = set()
646
+
647
+ def sanitize_pair(elts, vals):
648
+ if domain.is_element(elts):
649
+ elts = [elts]
650
+ if mapping:
651
+ vals = [vals]
652
+ else:
653
+ vals = [Integer(vals)]
654
+ else:
655
+ elts = list(elts)
656
+ if check:
657
+ bad = [elt for elt in elts if not domain.is_element(elt)]
658
+ assert not bad, "%s are not elements of %s" % (bad, domain)
659
+ if mapping:
660
+ vals = list(vals)
661
+ else:
662
+ vals = list(map(Integer, vals))
663
+ if len(elts) != len(vals):
664
+ raise ValueError("FindStat expects the same number of objects as values in each pair")
665
+ if check and mapping:
666
+ bad = [elt for elt in vals if not codomain.is_element(elt)]
667
+ assert not bad, "%s are not elements of %s" % (bad, codomain)
668
+ for elt in elts:
669
+ if elt in all_elements:
670
+ raise ValueError("FindStat expects that every object occurs at most once: %s" % elt)
671
+ all_elements.add(elt)
672
+
673
+ return elts, vals
674
+
675
+ lazy_data = lazy_list((sanitize_pair(elts, vals)
676
+ for elts, vals in iterator),
677
+ initial_values=[sanitize_pair(elts, vals)
678
+ for elts, vals in pre_data])
679
+ if mapping:
680
+ return lazy_data, domain, codomain
681
+ return lazy_data, domain
682
+
683
+
684
+ def _data_from_function(function, domain):
685
+ """
686
+ Return a lazy list of pairs of singleton lists of the same size,
687
+ domain.
688
+
689
+ INPUT:
690
+
691
+ - ``function`` -- a callable
692
+ - ``domain`` -- a :class:`FindStatCollection`
693
+
694
+ If ``function`` returns the value ``None``, the pair is omitted.
695
+
696
+ EXAMPLES::
697
+
698
+ sage: from sage.databases.findstat import FindStatCollection, _data_from_function
699
+ sage: domain = FindStatCollection(1) # optional -- internet
700
+ sage: _data_from_function(lambda pi: pi[0], domain) # optional -- internet
701
+ lazy list [([[1]], [1]), ([[1, 2]], [1]), ([[2, 1]], [2]), ...]
702
+ """
703
+ return lazy_list(([elt], [value])
704
+ for elt, value in domain.first_terms(function)
705
+ if value is not None)
706
+
707
+
708
+ def _data_from_data(data, max_values):
709
+ """
710
+ Return the first few pairs (of lists of the same size) with a
711
+ total of at most ``max_values`` objects in the range of the
712
+ collection.
713
+
714
+ INPUT:
715
+
716
+ - ``data`` -- an iterable over pairs of lists of the same size
717
+
718
+ - ``max_values`` -- the maximal number of objects (and values) to
719
+ return
720
+
721
+ We assume that the number of elements in each pair weakly
722
+ increases, to decide when to stop.
723
+
724
+ TESTS::
725
+
726
+ sage: from sage.databases.findstat import _data_from_data
727
+ sage: PM = PerfectMatchings
728
+ sage: l = [(PM(2*n), (m.number_of_nestings() for m in PM(2*n))) for n in range(2,100)]
729
+ sage: [(a, list(b)) for a, b in _data_from_data(l, 35)]
730
+ [(Perfect matchings of {1, 2, 3, 4}, [0, 0, 1]),
731
+ (Perfect matchings of {1, 2, 3, 4, 5, 6},
732
+ [0, 0, 1, 1, 2, 2, 1, 0, 0, 0, 1, 1, 1, 2, 3])]
733
+ """
734
+ query = []
735
+ total = min(max_values, FINDSTAT_MAX_VALUES)
736
+ iterator = iter(data)
737
+ while total > 0:
738
+ try:
739
+ elts, vals = next(iterator)
740
+ except StopIteration:
741
+ break
742
+ if total >= len(elts):
743
+ query.append((elts, vals))
744
+ total -= len(elts)
745
+ else:
746
+ break # assuming that the next pair is even larger
747
+
748
+ return query
749
+
750
+
751
+ def _distribution_from_data(data, domain, max_values, generating_functions=False):
752
+ """
753
+ Return the first few pairs (of lists of the same size) with a
754
+ total of at most ``max_values`` objects in the range of the
755
+ collection, combined by level.
756
+
757
+ INPUT:
758
+
759
+ - ``data`` -- an iterable over pairs of lists of the same size
760
+
761
+ - ``domain`` -- a :class:`FindStatCollection`
762
+
763
+ - ``max_values`` -- the maximal number of objects (and values) to
764
+ return
765
+
766
+ TESTS::
767
+
768
+ sage: from sage.databases.findstat import _distribution_from_data, FindStatCollection
769
+ sage: n = 3; l = lambda i: [pi for pi in Permutations(n) if pi(1) == i]
770
+ sage: data = [([pi for pi in l(i)], [pi(1) for pi in l(i)]) for i in range(1,n+1)]
771
+ sage: cc = FindStatCollection(1) # optional -- internet
772
+ sage: _distribution_from_data(data, cc, 10) # optional -- internet
773
+ [([[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]],
774
+ [1, 1, 2, 2, 3, 3])]
775
+ """
776
+ lvl_dict = {} # lvl: elts, vals
777
+ total = max_values
778
+ iterator = iter(data)
779
+ levels_with_sizes = domain.levels_with_sizes()
780
+ while total > 0:
781
+ try:
782
+ elts, vals = next(iterator)
783
+ except StopIteration:
784
+ break
785
+ if total < len(elts):
786
+ break
787
+ lvl = domain.element_level(elts[0])
788
+ if generating_functions and lvl not in levels_with_sizes:
789
+ continue
790
+ if levels_with_sizes[lvl] > total:
791
+ # we assume that from now on levels become even larger
792
+ break
793
+ if not all(domain.element_level(elt) == lvl for elt in elts[1:]):
794
+ raise ValueError("cannot combine %s into a distribution" % elts)
795
+ lvl_elts, lvl_vals = lvl_dict.get(lvl, [[], []])
796
+ lvl_dict[lvl] = (lvl_elts + elts, lvl_vals + vals)
797
+ if levels_with_sizes[lvl] == len(lvl_dict[lvl][0]):
798
+ total -= levels_with_sizes[lvl]
799
+
800
+ if generating_functions:
801
+ return {lvl: {val: vals.count(val) for val in set(vals)}
802
+ for lvl, (elts, vals) in lvl_dict.items()
803
+ if levels_with_sizes[lvl] == len(vals)}
804
+
805
+ return [(elts, vals) for lvl, (elts, vals) in lvl_dict.items()
806
+ if levels_with_sizes[lvl] == len(elts)]
807
+
808
+
809
+ def _generating_functions_from_dict(gfs, style):
810
+ """
811
+ Convert the generating functions given as a dictionary into a
812
+ desired style.
813
+
814
+ INPUT:
815
+
816
+ - ``gfs`` -- dictionary whose keys are the levels and whose values
817
+ are dictionaries from values to multiplicities
818
+
819
+ - ``style`` -- one of ``'dictionary'``, ``'list'`` or
820
+ ``'polynomial'``
821
+
822
+ .. SEEALSO::
823
+
824
+ - :meth:`FindStatCombinatorialStatistic.generating_functions()`
825
+
826
+ TESTS::
827
+
828
+ sage: from sage.databases.findstat import _generating_functions_from_dict
829
+ sage: d = {n: {2*i: n*i+1 for i in range(n, 2*n+1)} for n in range(3)}; d
830
+ {0: {0: 1}, 1: {2: 2, 4: 3}, 2: {4: 5, 6: 7, 8: 9}}
831
+
832
+ sage: _generating_functions_from_dict(d, "polynomial")
833
+ {0: 1, 1: 3*q^4 + 2*q^2, 2: 9*q^8 + 7*q^6 + 5*q^4}
834
+
835
+ sage: _generating_functions_from_dict(d, "list")
836
+ {0: [1], 1: [2, 0, 3], 2: [5, 0, 7, 0, 9]}
837
+ """
838
+ if style == "dictionary":
839
+ return gfs
840
+ if style == "list":
841
+ return {level: [gen_dict.get(deg, 0)
842
+ for deg in range(min(gen_dict),
843
+ max(gen_dict)+1)]
844
+ for level, gen_dict in gfs.items() if gen_dict}
845
+ if style == "polynomial":
846
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
847
+ from sage.rings.integer_ring import ZZ
848
+ P = PolynomialRing(ZZ, "q", sparse=True)
849
+ q = P.gen()
850
+ return {level: sum(coefficient * q**exponent
851
+ for exponent, coefficient in gen_dict.items())
852
+ for level, gen_dict in gfs.items()}
853
+
854
+ raise ValueError("the argument 'style' (='%s') must be 'dictionary', 'polynomial', or 'list'" % style)
855
+
856
+
857
+ def _get_code_from_callable(function):
858
+ """
859
+ Return code given a callable, if possible.
860
+
861
+ TESTS::
862
+
863
+ sage: from sage.databases.findstat import _get_code_from_callable
864
+ sage: @cached_function
865
+ ....: def statistic(pi):
866
+ ....: pi = Permutations(len(pi))(pi)
867
+ ....: if pi.is_one():
868
+ ....: return 1
869
+ ....: return sum(statistic(pi.apply_simple_reflection_right(i))
870
+ ....: for i in pi.descents())
871
+ sage: print(_get_code_from_callable(statistic))
872
+ @cached_function
873
+ def statistic(pi):
874
+ pi = Permutations(len(pi))(pi)
875
+ if pi.is_one():
876
+ return Integer(1)
877
+ return sum(statistic(pi.apply_simple_reflection_right(i))
878
+ for i in pi.descents())
879
+ """
880
+ code = ""
881
+ if function is not None:
882
+ from sage.misc.cachefunc import CachedFunction
883
+ try:
884
+ if isinstance(function, CachedFunction):
885
+ code = inspect.getsource(function.f)
886
+ else:
887
+ code = inspect.getsource(function)
888
+ except (OSError, TypeError):
889
+ verbose("inspect.getsource could not get code from function provided",
890
+ caller_name='FindStat')
891
+ return code
892
+
893
+
894
+ def findstat(query=None, values=None, distribution=None, domain=None,
895
+ depth=FINDSTAT_DEFAULT_DEPTH, max_values=FINDSTAT_MAX_VALUES):
896
+ r"""
897
+ Return matching statistics.
898
+
899
+ INPUT:
900
+
901
+ One of the following:
902
+
903
+ - an integer or a string representing a valid FindStat identifier
904
+ (e.g. 45 or 'St000045'). The keyword arguments ``depth`` and
905
+ ``max_values`` are ignored, ``values`` and ``distribution``
906
+ must be ``None``.
907
+
908
+ - a list of pairs of the form ``(object, value)``, or a
909
+ dictionary from Sage objects to integer values. The keyword
910
+ arguments ``depth`` and ``max_values`` are passed to the
911
+ finder, ``values`` and ``distribution`` must be ``None``.
912
+
913
+ - a list of pairs of the form (list of objects, list of values),
914
+ or a single pair of the form (list of objects, list of values).
915
+ In each pair there should be as many objects as values. The
916
+ keyword arguments ``depth`` and ``max_values`` are passed to
917
+ the finder.
918
+
919
+ - a collection and a list of pairs of the form (string, value),
920
+ or a dictionary from strings to integer values. The keyword
921
+ arguments ``depth`` and ``max_values`` are passed to the
922
+ finder. This should only be used if the collection is not yet
923
+ supported.
924
+
925
+ - a collection and a callable. The callable is used to generate
926
+ ``max_values`` ``(object, value)`` pairs. The number of terms
927
+ generated may also be controlled by passing an iterable
928
+ collection, such as ``Permutations(3)``. The keyword arguments
929
+ ``depth`` and ``max_values`` are passed to the finder.
930
+
931
+ OUTPUT: an instance of a :class:`FindStatStatistic`, represented by
932
+
933
+ - the FindStat identifier together with its name, or
934
+
935
+ - a list of triples, each consisting of
936
+
937
+ - the statistic
938
+
939
+ - a list of strings naming certain maps
940
+
941
+ - a number which says how many of the values submitted agree
942
+ with the values in the database, when applying the maps in
943
+ the given order to the object and then computing the
944
+ statistic on the result.
945
+
946
+ EXAMPLES:
947
+
948
+ A particular statistic can be retrieved by its St-identifier or
949
+ number::
950
+
951
+ sage: findstat('St000041') # optional -- internet
952
+ St000041: The number of nestings of a perfect matching.
953
+
954
+ sage: findstat(51) # optional -- internet
955
+ St000051: The size of the left subtree of a binary tree.
956
+
957
+ sage: findstat('St000042oMp00116') # optional -- internet
958
+ St000042oMp00116
959
+
960
+ The database can be searched by providing a list of pairs::
961
+
962
+ sage: l = [m for n in range(1, 4) for m in PerfectMatchings(2*n)]
963
+ sage: findstat([(m, m.number_of_nestings()) for m in l], depth=0) # optional -- internet
964
+ 0: St000041 (quality [100, 100])
965
+
966
+ or a dictionary::
967
+
968
+ sage: findstat({m: m.number_of_nestings() for m in l}, depth=0) # optional -- internet
969
+ 0: St000041 (quality [100, 100])
970
+
971
+ Note however, that the results of these two queries need not
972
+ compare equal, because we compare queries by the data
973
+ sent, and the ordering of the data might be different.
974
+
975
+ Another possibility is to send a collection and a function. In
976
+ this case, the function is applied to the first few objects of
977
+ the collection::
978
+
979
+ sage: findstat("Perfect Matchings", lambda m: m.number_of_nestings(), depth=0) # optional -- internet
980
+ 0: St000041 (quality [20, 100])
981
+
982
+ To search for a distribution, send a list of lists, or a single pair::
983
+
984
+ sage: PM = PerfectMatchings(10); findstat((PM, [m.number_of_nestings() for m in PM]), depth=0) # optional -- internet
985
+ 0: St000042 (quality [100, 100])
986
+ 1: St000041 (quality [9, 100])
987
+
988
+ Alternatively, specify the ``distribution`` parameter::
989
+
990
+ sage: findstat(12, distribution=lambda m: m.number_of_nestings(), depth=0) # optional -- internet
991
+ 0: St000041 (quality [100, 100])
992
+ 1: St000042 (quality [100, 100])
993
+
994
+ Note that there is a limit, ``FINDSTAT_MAX_VALUES``, on the number
995
+ of elements that may be submitted to FindStat, which is currently
996
+ 1000. Therefore, the interface tries to truncate queries
997
+ appropriately, but this may be impossible, especially with
998
+ distribution searches::
999
+
1000
+ sage: PM = PerfectMatchings(12); PM.cardinality() # optional -- internet
1001
+ 10395
1002
+ sage: findstat((PM, [1 for m in PM])) # optional -- internet
1003
+ Traceback (most recent call last):
1004
+ ...
1005
+ ValueError: E016: The statistic finder was unable to perform a search on your data. The following errors have occured:
1006
+ <BLANKLINE>
1007
+ You passed too few elements (0 < 3) to FindStat!
1008
+
1009
+ Finally, we can also retrieve all statistics with a given domain::
1010
+
1011
+ sage: findstat("Cc0024") # optional -- internet
1012
+ Set of combinatorial statistics with domain Cc0024: Binary words in FindStat
1013
+
1014
+ sage: findstat(domain='Cores') # optional -- internet
1015
+ Set of combinatorial statistics with domain Cc0013: Cores in FindStat
1016
+
1017
+ TESTS::
1018
+
1019
+ sage: findstat("Permutations", lambda x: 1, depth='x') # optional -- internet
1020
+ Traceback (most recent call last):
1021
+ ...
1022
+ ValueError: E021: Depth should be a nonnegative integer at most 9, but is x.
1023
+
1024
+ sage: findstat("Permutations", lambda x: 1, depth=100) # optional -- internet
1025
+ Traceback (most recent call last):
1026
+ ...
1027
+ ValueError: E021: Depth should be a nonnegative integer at most 9, but is 100.
1028
+
1029
+ sage: S = Permutation
1030
+ sage: findstat([(S([1,2]), 1), ([S([1,3,2]), S([1,2])], [2,3])]) # optional -- internet
1031
+ Traceback (most recent call last):
1032
+ ...
1033
+ ValueError: FindStat expects that every object occurs at most once: [1, 2]
1034
+
1035
+ Check that values which can be converted to integers are supported::
1036
+
1037
+ sage: findstat([(m, m.number_of_nestings()/1) for m in PerfectMatchings(10)], depth=0) # optional -- internet
1038
+ 0: St000041 (quality [9, 100])
1039
+
1040
+ Check that ``None`` values are omitted::
1041
+
1042
+ sage: findstat("graphs", lambda g: g.diameter() if g.is_connected() else None, max_values=100, depth=0) # optional -- internet
1043
+ 0: St000259 (quality [100, 100])
1044
+ """
1045
+ try:
1046
+ max_values = int(max_values)
1047
+ assert 0 <= max_values <= FINDSTAT_MAX_VALUES
1048
+ except (ValueError, AssertionError):
1049
+ raise ValueError("the maximal number of values for a FindStat query must be a nonnegative integer less than or equal to %i" % FINDSTAT_MAX_VALUES)
1050
+
1051
+ check_collection = True
1052
+
1053
+ def get_values(raw, domain=None):
1054
+ if callable(raw):
1055
+ known_terms = _data_from_function(raw, domain)
1056
+ function = raw
1057
+ else:
1058
+ known_terms, domain = _data_from_iterable(raw, domain=domain,
1059
+ mapping=False,
1060
+ check=check_collection)
1061
+ function = None
1062
+ data = _data_from_data(known_terms, max_values)
1063
+ return known_terms, data, domain, function
1064
+
1065
+ def get_distribution(raw, domain=None):
1066
+ if callable(raw):
1067
+ known_terms = _data_from_function(raw, domain)
1068
+ function = raw
1069
+ else:
1070
+ known_terms, domain = _data_from_iterable(raw, domain=domain,
1071
+ mapping=False,
1072
+ check=check_collection)
1073
+ function = None
1074
+ data = _distribution_from_data(known_terms, domain, max_values)
1075
+ return known_terms, data, domain, function
1076
+
1077
+ ######################################################################
1078
+ if query is None and values is None and distribution is None and domain is None:
1079
+ return FindStat()
1080
+
1081
+ if values is not None and distribution is not None:
1082
+ raise ValueError("not both of `values` and `distribution` may be given for a FindStat query")
1083
+
1084
+ if values is None and distribution is None:
1085
+ if query is None:
1086
+ return FindStatStatistics(domain=domain)
1087
+
1088
+ if domain is None:
1089
+ if isinstance(query, (int, Integer, FindStatCombinatorialStatistic)):
1090
+ return FindStatStatistic(query)
1091
+
1092
+ if isinstance(query, str):
1093
+ if re.match(FINDSTAT_COLLECTION_REGEXP, query):
1094
+ return FindStatStatistics(domain=query)
1095
+
1096
+ return FindStatStatistic(query)
1097
+
1098
+ values, query = query, None
1099
+
1100
+ if query is not None:
1101
+ if domain is None:
1102
+ domain, query = query, None
1103
+ else:
1104
+ raise ValueError("the given arguments cannot be used for a FindStat search")
1105
+
1106
+ if domain is not None:
1107
+ domain = FindStatCollection(domain)
1108
+
1109
+ if values is not None:
1110
+ if isinstance(values, (int, Integer, str, FindStatCombinatorialStatistic)):
1111
+ if domain is not None:
1112
+ raise ValueError("the domain must not be provided if a statistic identifier is given")
1113
+ return FindStatStatisticQuery(values_of=values, depth=depth)
1114
+
1115
+ known_terms, data, domain, function = get_values(values, domain)
1116
+ return FindStatStatisticQuery(data=data, domain=domain, depth=depth,
1117
+ known_terms=known_terms, function=function)
1118
+
1119
+ if distribution is not None:
1120
+ if isinstance(distribution, (int, Integer, str, FindStatCombinatorialStatistic)):
1121
+ if domain is not None:
1122
+ raise ValueError("the domain must not be provided if a statistic identifier is given")
1123
+ return FindStatStatisticQuery(distribution_of=distribution, depth=depth)
1124
+
1125
+ known_terms, data, domain, function = get_distribution(distribution, domain)
1126
+ return FindStatStatisticQuery(data=data, domain=domain, depth=depth,
1127
+ known_terms=known_terms, function=function)
1128
+
1129
+ raise ValueError("the given arguments cannot be used for a FindStat search")
1130
+
1131
+
1132
+ def findmap(*args, **kwargs):
1133
+ r"""
1134
+ Return matching maps.
1135
+
1136
+ INPUT:
1137
+
1138
+ Valid keywords are: ``domain``, ``codomain``, ``values``,
1139
+ ``distribution``, ``depth`` and ``max_values``. They have the
1140
+ following meanings:
1141
+
1142
+ - ``depth`` -- nonnegative integer (default: ``FINDSTAT_DEFAULT_DEPTH``);
1143
+ specifying how many maps to apply to generate the given map
1144
+
1145
+ - ``max_values`` -- integer (default: ``FINDSTAT_MAX_VALUES``); specifying
1146
+ how many values are sent to the finder
1147
+
1148
+ - ``domain``, ``codomain`` -- integer or string of the form
1149
+ ``Cc1234``, designates the domain and codomain of the sought
1150
+ for maps
1151
+
1152
+ - ``values``, ``distribution`` -- data specifying the values or
1153
+ distribution of values of the sought for maps. The keyword
1154
+ arguments ``depth`` and ``max_values`` are passed to the
1155
+ finder. The data may be specified in one of the following
1156
+ forms:
1157
+
1158
+ - a list of pairs of the form ``(object, value)``, or a
1159
+ dictionary from Sage objects to Sage objects.
1160
+
1161
+ - a list of pairs of the form ``(list of objects, list of
1162
+ values)``, or a single pair of the form ``(list of objects,
1163
+ list of values)``. In each pair there should be as many
1164
+ objects as values.
1165
+
1166
+ - a callable. In this case, the domain must be specified,
1167
+ also. The callable is then used to generate ``max_values``
1168
+ ``(object, value)`` pairs.
1169
+
1170
+ The number of terms generated may also be controlled by
1171
+ passing an iterable collection, such as
1172
+ ``Permutations(3)``.
1173
+
1174
+ ``findmap`` also accepts at most three positional arguments as
1175
+ follows:
1176
+
1177
+ - a single positional argument, if none of ``domain``,
1178
+ ``codomain``, ``values`` or ``distribution`` are specified, is
1179
+ interpreted as a FindStat map identifier. If further arguments
1180
+ are given and it is a string, it is interpreted as a domain.
1181
+ If all this fails, it is interpreted as the specification of
1182
+ values.
1183
+
1184
+ - if two positional arguments are given, the first is interpreted
1185
+ as domain, and the second either as codomain or the
1186
+ specification of values.
1187
+
1188
+ - if three positional arguments are given, the first two are
1189
+ interpreted as domain and codomain, and the third as the
1190
+ specification of values.
1191
+
1192
+ OUTPUT:
1193
+
1194
+ An instance of a :class:`FindStatMap`, :class:`FindStatMapQuery`
1195
+ or :class:`FindStatMaps`.
1196
+
1197
+ EXAMPLES:
1198
+
1199
+ A particular map can be retrieved by its Mp-identifier or
1200
+ number::
1201
+
1202
+ sage: findmap('Mp00062') # optional -- internet
1203
+ Mp00062: Lehmer-code to major-code bijection
1204
+
1205
+ sage: findmap(62) # optional -- internet
1206
+ Mp00062: Lehmer-code to major-code bijection
1207
+
1208
+ sage: findmap("Mp00099oMp00127") # optional -- internet
1209
+ Mp00099oMp00127
1210
+
1211
+ The database can be searched by providing a list of pairs::
1212
+
1213
+ sage: l = [pi for n in range(5) for pi in Permutations(n)]
1214
+ sage: findmap([(pi, pi.complement().increasing_tree_shape()) for pi in l], depth=2) # optional -- internet
1215
+ 0: Mp00061oMp00069 (quality [...])
1216
+
1217
+ or a dictionary::
1218
+
1219
+ sage: findmap({pi: pi.complement().increasing_tree_shape() for pi in l}, depth=2) # optional -- internet
1220
+ 0: Mp00061oMp00069 (quality [...])
1221
+
1222
+ Note however, that the results of these two queries need not
1223
+ compare equal, because we compare queries by the data
1224
+ sent, and the ordering of the data might be different.
1225
+
1226
+ Another possibility is to send a collection and a function. In
1227
+ this case, the function is applied to the first few objects of
1228
+ the collection::
1229
+
1230
+ sage: findmap("Permutations", lambda pi: pi.increasing_tree_shape(), depth=1) # optional -- internet
1231
+ 0: Mp00061 (quality [100])
1232
+
1233
+ In rare cases, it may not be possible to guess the codomain of a
1234
+ map, in which case it can be provided as second argument or
1235
+ keyword argument::
1236
+
1237
+ sage: findmap("Dyck paths", "Perfect matchings", lambda D: [(a+1, b) for a,b in D.tunnels()]) # optional -- internet
1238
+ 0: Mp00146 (quality [100])
1239
+
1240
+ sage: findmap("Dyck paths", "Set partitions", lambda D: [(a+1, b) for a,b in D.tunnels()]) # optional -- internet
1241
+ 0: Mp00092oMp00146 (quality [...])
1242
+
1243
+ Finally, we can also retrieve all maps with a given domain or codomain::
1244
+
1245
+ sage: findmap("Cc0024") # optional -- internet
1246
+ Set of combinatorial maps with domain Cc0024: Binary words used by FindStat
1247
+
1248
+ sage: findmap(codomain='Cores') # optional -- internet
1249
+ Set of combinatorial maps with codomain Cc0013: Cores used by FindStat
1250
+ """
1251
+ if len(args) > 3:
1252
+ raise TypeError("findmap takes at most 3 positional arguments (%s given)" % len(args))
1253
+
1254
+ bad_args = set(kwargs).difference(["values", "distribution",
1255
+ "domain", "codomain",
1256
+ "depth", "max_values"])
1257
+ if bad_args:
1258
+ raise TypeError("findmap got unexpected keyword arguments '%s'" % bad_args)
1259
+
1260
+ max_values = kwargs.get("max_values", FINDSTAT_MAX_VALUES)
1261
+ depth = kwargs.get("depth", FINDSTAT_DEFAULT_DEPTH)
1262
+ values = kwargs.get("values", None)
1263
+ distribution = kwargs.get("distribution", None)
1264
+ domain = kwargs.get("domain", None)
1265
+ codomain = kwargs.get("codomain", None)
1266
+
1267
+ try:
1268
+ max_values = int(max_values)
1269
+ assert 0 <= max_values <= FINDSTAT_MAX_VALUES
1270
+ except (ValueError, AssertionError):
1271
+ raise ValueError("the maximal number of values for a FindStat query must be a nonnegative integer less than or equal to %i" % FINDSTAT_MAX_VALUES)
1272
+
1273
+ check_collection = True
1274
+
1275
+ def get_values(raw, domain=None, codomain=None):
1276
+ if callable(raw):
1277
+ known_terms = _data_from_function(raw, domain)
1278
+ if codomain is None:
1279
+ codomain = FindStatCollection(known_terms[0][1][0])
1280
+ function = raw
1281
+ else:
1282
+ known_terms, domain, codomain = _data_from_iterable(raw, domain=domain,
1283
+ codomain=codomain,
1284
+ mapping=True,
1285
+ check=check_collection)
1286
+ function = None
1287
+ data = _data_from_data(known_terms, max_values)
1288
+ return known_terms, data, domain, codomain, function
1289
+
1290
+ def get_distribution(raw, domain=None, codomain=None):
1291
+ if callable(raw):
1292
+ known_terms = _data_from_function(raw, domain)
1293
+ function = raw
1294
+ else:
1295
+ known_terms, domain, codomain = _data_from_iterable(raw, domain=domain,
1296
+ codomain=codomain,
1297
+ mapping=True,
1298
+ check=check_collection)
1299
+ function = None
1300
+ data = _distribution_from_data(known_terms, domain, max_values)
1301
+ return known_terms, data, domain, codomain, function
1302
+
1303
+ def is_collection(arg):
1304
+ try:
1305
+ FindStatCollection(arg)
1306
+ return True
1307
+ except ValueError:
1308
+ return False
1309
+
1310
+ def check_domain(arg, domain):
1311
+ if domain is not None:
1312
+ raise TypeError("the domain was specified twice, as positional argument (%s) and as keyword domain=%s" % (arg, domain))
1313
+ return arg
1314
+
1315
+ def check_codomain(arg, codomain):
1316
+ if codomain is not None:
1317
+ raise TypeError("the codomain was specified twice, as positional argument (%s) and as keyword codomain=%s" % (arg, codomain))
1318
+ return arg
1319
+
1320
+ def check_values(arg, values):
1321
+ if values is not None:
1322
+ raise TypeError("values were specified twice, as positional argument (%s) and as keyword values=%s" % (arg, values))
1323
+ return arg
1324
+
1325
+ ######################################################################
1326
+ if values is not None and distribution is not None:
1327
+ raise ValueError("not both of `values` and `distribution` may be given for a FindStat query")
1328
+
1329
+ if len(args) == 1:
1330
+ if (values is None and distribution is None
1331
+ and domain is None and codomain is None
1332
+ and (isinstance(args[0], (int, Integer, FindStatCombinatorialMap))
1333
+ or (isinstance(args[0], str)
1334
+ and not is_collection(args[0])))):
1335
+ return FindStatMap(args[0])
1336
+
1337
+ if (isinstance(args[0], str) and
1338
+ is_collection(args[0])):
1339
+ domain = check_domain(args[0], domain)
1340
+
1341
+ else:
1342
+ values = check_values(args[0], values)
1343
+
1344
+ elif len(args) == 2:
1345
+ domain = check_domain(args[0], domain)
1346
+ if isinstance(args[1], (int, Integer, str)):
1347
+ codomain = check_codomain(args[1], codomain)
1348
+ else:
1349
+ values = check_values(args[1], values)
1350
+
1351
+ elif len(args) == 3:
1352
+ domain = check_domain(args[0], domain)
1353
+ codomain = check_codomain(args[1], codomain)
1354
+ values = check_values(args[2], values)
1355
+
1356
+ if domain is not None:
1357
+ domain = FindStatCollection(domain)
1358
+ if codomain is not None:
1359
+ codomain = FindStatCollection(codomain)
1360
+
1361
+ if (values is None and distribution is None
1362
+ and (domain is not None or codomain is not None)):
1363
+ return FindStatMaps(domain=domain, codomain=codomain)
1364
+
1365
+ if values is not None:
1366
+ if isinstance(values, (int, Integer, str, FindStatCombinatorialMap)):
1367
+ if domain is not None or codomain is not None:
1368
+ raise ValueError("domain and codomain must not be provided if a map identifier is given")
1369
+ return FindStatMapQuery(values_of=values, depth=depth)
1370
+
1371
+ known_terms, data, domain, codomain, function = get_values(values, domain, codomain)
1372
+ return FindStatMapQuery(data=data, domain=domain, codomain=codomain, depth=depth,
1373
+ known_terms=known_terms, function=function)
1374
+
1375
+ if distribution is not None:
1376
+ if isinstance(distribution, (int, Integer, str, FindStatCombinatorialMap)):
1377
+ if domain is not None or codomain is not None:
1378
+ raise ValueError("domain and codomain must not be provided if a map identifier is given")
1379
+ return FindStatMapQuery(distribution_of=distribution, depth=depth)
1380
+
1381
+ known_terms, data, domain, function = get_distribution(distribution, domain)
1382
+ return FindStatMapQuery(data=data, domain=domain, codomain=codomain, depth=depth,
1383
+ known_terms=known_terms, function=function)
1384
+
1385
+ raise ValueError("the given arguments cannot be used for a FindStat search")
1386
+
1387
+
1388
+ ######################################################################
1389
+ # common methods for statistic and maps in FindStat
1390
+ ######################################################################
1391
+ class FindStatFunction(SageObject):
1392
+ """
1393
+ A class providing the common methods of :class:`FindStatMap` and
1394
+ :class:`FindStatStatistic`.
1395
+
1396
+ This class provides methods to access and modify properties of a
1397
+ single statistic or map of the FindStat database.
1398
+ """
1399
+ def __init__(self, id, data=None, function=None):
1400
+ """
1401
+ Initialize a statistic or map.
1402
+
1403
+ INPUT:
1404
+
1405
+ - ``id`` -- a padded identifier, with number 0 reserved for new
1406
+ statistics or maps.
1407
+
1408
+ - ``data`` -- dictionary with "Description", "Code", etc.
1409
+
1410
+ - ``function`` -- (optional) a callable implementing the
1411
+ statistic or map, or ``None``
1412
+
1413
+ ``data`` should be provided if and only if ``id`` refers to a
1414
+ new statistic or map (with identifier 0).
1415
+
1416
+ TESTS::
1417
+
1418
+ sage: from sage.databases.findstat import FindStatFunction, FindStatCollection
1419
+ sage: FindStatFunction("St000000", # optional -- internet
1420
+ ....: data={"Bibliography": {},
1421
+ ....: "Code": "",
1422
+ ....: "Description" : "",
1423
+ ....: "Domain": FindStatCollection(1),
1424
+ ....: "Name": "a new statistic",
1425
+ ....: "References": "",
1426
+ ....: "SageCode": ""})
1427
+ St000000: a new statistic
1428
+ """
1429
+ self._id = id # as padded identifier, with number 0 reserved for new statistics or maps
1430
+ self._modified = False # set in every method modifying the data
1431
+ if callable(function):
1432
+ self._function = function
1433
+ else:
1434
+ self._function = False # determines that FindStat code may not be executed
1435
+ if self.id() != 0 and data is not None:
1436
+ raise ValueError("data (%s) may be provided if and only if id (%s) is %s or %s" %
1437
+ (data, id,
1438
+ FINDSTAT_STATISTIC_PADDED_IDENTIFIER % 0,
1439
+ FINDSTAT_MAP_PADDED_IDENTIFIER % 0))
1440
+ self._data_cache = data # a dictionary with "Description", "Code", etc.
1441
+
1442
+ def _data(self):
1443
+ """
1444
+ Return a copy of the data defining the statistic or map.
1445
+
1446
+ The first terms of a statistic are provided separately, by
1447
+ :meth:`first_terms_raw`, to save bandwidth.
1448
+
1449
+ Any method modifying ``self._data_cache`` should first
1450
+ call - directly or indirectly - this method.
1451
+
1452
+ TESTS::
1453
+
1454
+ sage: from sage.databases.findstat import FindStatFunction, FindStatCollection
1455
+ sage: FindStatFunction("St000000", # optional -- internet
1456
+ ....: data={"Bibliography": {},
1457
+ ....: "Code": "",
1458
+ ....: "Description" : "",
1459
+ ....: "Domain": FindStatCollection(1),
1460
+ ....: "Name": "a new statistic",
1461
+ ....: "References": "",
1462
+ ....: "SageCode": ""})._data()
1463
+ {'Bibliography': {},
1464
+ 'Code': '',
1465
+ 'Description': '',
1466
+ 'Domain': Cc0001: Permutations,
1467
+ 'Name': 'a new statistic',
1468
+ 'References': '',
1469
+ 'SageCode': ''}
1470
+ """
1471
+ # initializes self._data_cache on first call
1472
+ if self._data_cache is None:
1473
+ self._data_cache = self._fetch_data()
1474
+ # some of the data are lists, so we need to deepcopy
1475
+ return deepcopy(self._data_cache)
1476
+
1477
+ def __call__(self, elt):
1478
+ """
1479
+ Apply the function to a given element.
1480
+
1481
+ EXAMPLES::
1482
+
1483
+ sage: s = lambda g: g.diameter() if g.is_connected() else None
1484
+ sage: q = findstat("graphs", s, max_values=100) # optional -- internet
1485
+ sage: q(graphs.PetersenGraph().copy(immutable=True)) # optional -- internet
1486
+ 2
1487
+ """
1488
+ if self._function is False and FindStat()._allow_execution is False:
1489
+ raise ValueError("execution of verified code provided by FindStat is not enabled for %s" % self)
1490
+ if self._function is True or (self._function is False and FindStat()._allow_execution is True):
1491
+ if not self.sage_code():
1492
+ raise ValueError("there is no verified code available for %s" % self)
1493
+ from sage.repl.preparse import preparse
1494
+ try:
1495
+ l = {}
1496
+ environment = 'sage.all'
1497
+ code = f"from {environment} import *\n" + preparse(self.sage_code())
1498
+ exec(code, l)
1499
+ except SyntaxError:
1500
+ raise ValueError("could not execute verified code for %s" % self)
1501
+ if isinstance(self, FindStatStatistic):
1502
+ self._function = eval("statistic", l)
1503
+ elif isinstance(self, FindStatMap):
1504
+ self._function = eval("mapping", l)
1505
+ else:
1506
+ raise ValueError("cannot execute verified code for %s" % self)
1507
+
1508
+ return self._function(elt)
1509
+
1510
+ def __repr__(self):
1511
+ r"""
1512
+ Return the representation of the FindStat statistic or map.
1513
+
1514
+ OUTPUT:
1515
+
1516
+ A string, the identifier and the name of the statistic. If
1517
+ the statistic was modified (see :meth:`modified`) this is
1518
+ also indicated.
1519
+
1520
+ EXAMPLES::
1521
+
1522
+ sage: findstat(51) # optional -- internet
1523
+ St000051: The size of the left subtree of a binary tree.
1524
+
1525
+ sage: findstat(914) # optional -- internet
1526
+ St000914: The sum of the values of the Möbius function of a poset.
1527
+
1528
+ sage: findmap(85) # optional -- internet
1529
+ Mp00085: Schützenberger involution
1530
+ """
1531
+ if self._modified:
1532
+ s = "%s(modified): %s" % (self.id_str(), self.name())
1533
+ else:
1534
+ s = "%s: %s" % (self.id_str(), self.name())
1535
+ return s
1536
+
1537
+ def reset(self):
1538
+ """
1539
+ Discard all modification of the statistic or map.
1540
+
1541
+ EXAMPLES::
1542
+
1543
+ sage: s = findmap(62) # optional -- internet
1544
+ sage: s.set_name(u"Möbius"); s # optional -- internet
1545
+ Mp00062(modified): Möbius
1546
+ sage: s.reset(); s # optional -- internet
1547
+ Mp00062: Lehmer-code to major-code bijection
1548
+
1549
+ TESTS:
1550
+
1551
+ Check that new statistics and maps cannot be reset::
1552
+
1553
+ sage: # optional - internet
1554
+ sage: q = findstat([(d, randint(1, 1000)) for d in DyckWords(4)])
1555
+ sage: q.set_description("Random values on Dyck paths.")
1556
+ sage: print(q.description())
1557
+ Random values on Dyck paths.
1558
+ sage: q.reset()
1559
+ Traceback (most recent call last):
1560
+ ...
1561
+ ValueError: cannot reset values of St000000: a new statistic on Dyck paths
1562
+ """
1563
+ if isinstance(self, FindStatStatistic) and self.id_str() in _all_statistics:
1564
+ del _all_statistics[self.id_str()]
1565
+ elif isinstance(self, FindStatMap) and self.id_str() in _all_maps:
1566
+ del _all_maps[self.id_str()]
1567
+ else:
1568
+ raise ValueError("cannot reset values of %s" % self)
1569
+ self.__init__(self.parent(), self.id_str())
1570
+
1571
+ def id(self):
1572
+ r"""
1573
+ Return the FindStat identifier of the statistic or map.
1574
+
1575
+ OUTPUT: the FindStat identifier of the statistic or map, as an integer
1576
+
1577
+ EXAMPLES::
1578
+
1579
+ sage: findstat(51).id() # optional -- internet
1580
+ 51
1581
+ """
1582
+ return int(self._id[2:])
1583
+
1584
+ def id_str(self):
1585
+ r"""
1586
+ Return the FindStat identifier of the statistic or map.
1587
+
1588
+ OUTPUT: the FindStat identifier of the statistic or map, as a string
1589
+
1590
+ EXAMPLES::
1591
+
1592
+ sage: findstat(51).id_str() # optional -- internet
1593
+ 'St000051'
1594
+ """
1595
+ return self._id
1596
+
1597
+ def domain(self):
1598
+ r"""
1599
+ Return the FindStat domain of the statistic or map.
1600
+
1601
+ OUTPUT:
1602
+
1603
+ The domain of the statistic or map as an instance of
1604
+ :class:`FindStatCollection`.
1605
+
1606
+ EXAMPLES::
1607
+
1608
+ sage: findstat(51).domain() # optional -- internet
1609
+ Cc0010: Binary trees
1610
+
1611
+ sage: findmap(62).domain() # optional -- internet
1612
+ Cc0001: Permutations
1613
+ """
1614
+ return FindStatCollection(self._data()["Domain"])
1615
+
1616
+ def description(self):
1617
+ r"""
1618
+ Return the description of the statistic or map.
1619
+
1620
+ OUTPUT: string; for statistics, the first line is used as name
1621
+
1622
+ EXAMPLES::
1623
+
1624
+ sage: print(findstat(51).description()) # optional -- internet
1625
+ The size of the left subtree of a binary tree.
1626
+ """
1627
+ return self._data()["Description"]
1628
+
1629
+ def set_description(self, value):
1630
+ r"""
1631
+ Set the description of the statistic or map.
1632
+
1633
+ INPUT:
1634
+
1635
+ - ``value`` -- string; for statistics, this is the name of the
1636
+ statistic followed by its description on a separate line
1637
+
1638
+ This information is used when submitting the statistic or map with
1639
+ :meth:`submit`.
1640
+
1641
+ EXAMPLES::
1642
+
1643
+ sage: q = findstat([(d, randint(1, 1000)) for d in DyckWords(4)]) # optional -- internet
1644
+ sage: q.set_description("Random values on Dyck paths.\nNot for submission.") # optional -- internet
1645
+ sage: print(q.description()) # optional -- internet
1646
+ Random values on Dyck paths.
1647
+ Not for submission.
1648
+ """
1649
+ if value != self.description():
1650
+ self._modified = True
1651
+ self._data_cache["Description"] = value
1652
+
1653
+ def name(self):
1654
+ r"""
1655
+ Return the name of the statistic or map.
1656
+
1657
+ OUTPUT:
1658
+
1659
+ A string. For statistics, this is just the first line of the
1660
+ description.
1661
+
1662
+ EXAMPLES::
1663
+
1664
+ sage: findstat(51).name() # optional -- internet
1665
+ 'The size of the left subtree of a binary tree.'
1666
+ """
1667
+ return self._data()["Name"]
1668
+
1669
+ def references(self):
1670
+ r"""
1671
+ Return the references associated with the statistic or map.
1672
+
1673
+ OUTPUT:
1674
+
1675
+ An instance of :class:`sage.databases.oeis.FancyTuple`, each
1676
+ item corresponds to a reference.
1677
+
1678
+ EXAMPLES::
1679
+
1680
+ sage: findstat(41).references() # optional -- internet
1681
+ 0: [1] de Médicis, A., Viennot, X. G., Moments des $q$-polynômes de Laguerre et la bijection de Foata-Zeilberger [[MathSciNet:1288802]]
1682
+ 1: [2] Simion, R., Stanton, D., Octabasic Laguerre polynomials and permutation statistics [[MathSciNet:1418763]]
1683
+ """
1684
+ result = []
1685
+ refs = self.references_raw()
1686
+ if refs:
1687
+ refs = refs.splitlines()
1688
+ else:
1689
+ return FancyTuple([])
1690
+ bibs = self._data()["Bibliography"]
1691
+ for ref in refs:
1692
+ parts = ref.partition("[[")
1693
+ parts = parts[:-1] + parts[2].partition("]]")
1694
+ comment = parts[0]
1695
+ link = parts[2]
1696
+ if link == "":
1697
+ result.append(ref)
1698
+ else:
1699
+ try:
1700
+ bibitem = bibs[link]
1701
+ except KeyError:
1702
+ # this means that the link is unhandled
1703
+ result.append(ref)
1704
+ else:
1705
+ author_title = ", ".join(e for e in [bibitem["Author"], bibitem["Title"]]
1706
+ if e)
1707
+ result.append(comment + author_title + " " + "".join(parts[1:]))
1708
+
1709
+ return FancyTuple(result)
1710
+
1711
+ def references_raw(self):
1712
+ r"""
1713
+ Return the unrendered references associated with the statistic or map.
1714
+
1715
+ EXAMPLES::
1716
+
1717
+ sage: print(findstat(41).references_raw()) # optional -- internet
1718
+ [1] [[MathSciNet:1288802]]
1719
+ [2] [[MathSciNet:1418763]]
1720
+ """
1721
+ return self._data()["References"]
1722
+
1723
+ def set_references_raw(self, value):
1724
+ r"""
1725
+ Set the references associated with the statistic or map.
1726
+
1727
+ INPUT:
1728
+
1729
+ - ``value`` -- string; each reference should be on a single line, and
1730
+ consist of one or more links to the same item
1731
+
1732
+ FindStat will automatically resolve the links, if possible.
1733
+ A complete list of supported services can be found at
1734
+ <https://findstat.org/NewStatistic>.
1735
+
1736
+ This information is used when submitting the statistic with
1737
+ :meth:`submit`.
1738
+
1739
+ EXAMPLES::
1740
+
1741
+ sage: q = findstat([(d, randint(1, 1000)) for d in DyckWords(4)]) # optional -- internet
1742
+ sage: q.set_references_raw("[[arXiv:1102.4226]]\n[[oeis:A000001]]") # optional -- internet
1743
+ sage: q.references() # optional -- internet
1744
+ 0: [[arXiv:1102.4226]]
1745
+ 1: [[oeis:A000001]]
1746
+ """
1747
+ if value != self.references_raw():
1748
+ self._modified = True
1749
+ self._data_cache["References"] = value
1750
+
1751
+ def sage_code(self):
1752
+ r"""
1753
+ Return the Sage code associated with the statistic or map.
1754
+
1755
+ OUTPUT: an empty string or a string of the form::
1756
+
1757
+ def statistic(x):
1758
+ ...
1759
+
1760
+ or::
1761
+
1762
+ def mapping(x):
1763
+ ...
1764
+
1765
+ EXAMPLES::
1766
+
1767
+ sage: print(findstat(51).sage_code()) # optional -- internet
1768
+ def statistic(T):
1769
+ return T[0].node_number()
1770
+ """
1771
+ return self._data()["SageCode"]
1772
+
1773
+ def set_sage_code(self, value):
1774
+ r"""
1775
+ Set the code associated with the statistic or map.
1776
+
1777
+ INPUT:
1778
+
1779
+ - ``value`` -- string; SageMath code producing the values of the
1780
+ statistic or map
1781
+
1782
+ Contributors are encouraged to submit code for statistics
1783
+ using :meth:`FindStatStatistic.set_code`. Modifying the
1784
+ "verified" SageMath code using this method is restricted to
1785
+ members of the FindStatCrew, for all other contributors this
1786
+ method has no effect.
1787
+
1788
+ EXAMPLES::
1789
+
1790
+ sage: q = findstat([(d, randint(1,1000)) for d in DyckWords(4)]) # optional -- internet
1791
+ sage: q.set_sage_code("def statistic(x):\n return randint(1, 1000)") # optional -- internet
1792
+ sage: print(q.sage_code()) # optional -- internet
1793
+ def statistic(x):
1794
+ return randint(1,1000)
1795
+ """
1796
+ if value != self.sage_code():
1797
+ self._modified = True
1798
+ self._data_cache["SageCode"] = value
1799
+
1800
+ ######################################################################
1801
+ # statistics
1802
+ ######################################################################
1803
+
1804
+
1805
+ class FindStatCombinatorialStatistic(SageObject):
1806
+ """
1807
+ A class providing methods to retrieve the first terms of a statistic.
1808
+
1809
+ This class provides methods applicable to instances of
1810
+ :class:`FindStatStatistic`, :class:`FindStatCompoundStatistic`
1811
+ and :class:`FindStatStatisticQuery`.
1812
+ """
1813
+ def __init__(self):
1814
+ """
1815
+ Initialize the combinatorial statistic.
1816
+
1817
+ TESTS::
1818
+
1819
+ sage: from sage.databases.findstat import FindStatCombinatorialStatistic
1820
+ sage: FindStatCombinatorialStatistic()
1821
+ <sage.databases.findstat.FindStatCombinatorialStatistic object at 0x...>
1822
+ """
1823
+ self._first_terms_cache = None
1824
+ self._first_terms_raw_cache = None
1825
+
1826
+ def first_terms(self):
1827
+ r"""
1828
+ Return the first terms of the (compound) statistic as a
1829
+ dictionary.
1830
+
1831
+ OUTPUT:
1832
+
1833
+ A dictionary from Sage objects representing an element of the
1834
+ appropriate collection to integers.
1835
+
1836
+ This method is overridden in :class:`FindStatStatisticQuery`.
1837
+
1838
+ EXAMPLES::
1839
+
1840
+ sage: findstat(41).first_terms()[PerfectMatching([(1,6),(2,5),(3,4)])] # optional -- internet
1841
+ 3
1842
+ """
1843
+ # initialize self._first_terms_cache and
1844
+ # self._first_terms_raw_cache on first call
1845
+ if self._first_terms_cache is None:
1846
+ self._first_terms_cache = self._fetch_first_terms()
1847
+ # a shallow copy suffices - tuples are immutable
1848
+ return dict(self._first_terms_cache)
1849
+
1850
+ def _first_terms_raw(self, max_values):
1851
+ """
1852
+ Return the first terms of the (compound) statistic as a
1853
+ list of ``(string, value)`` pairs.
1854
+
1855
+ INPUT:
1856
+
1857
+ - ``max_values`` -- integer determining how many terms to
1858
+ return at most
1859
+
1860
+ OUTPUT: list of ``(string, value)`` pairs
1861
+
1862
+ This method is overridden in :class:`FindStatStatisticQuery`.
1863
+
1864
+ TESTS::
1865
+
1866
+ sage: findstat(41)._first_terms_raw(4) # optional -- internet
1867
+ [('[(1,2)]', 0),
1868
+ ('[(1,2),(3,4)]', 0),
1869
+ ('[(1,3),(2,4)]', 0),
1870
+ ('[(1,4),(2,3)]', 1)]
1871
+ """
1872
+ # initialize self._first_terms_raw_cache on first call
1873
+ if self._first_terms_raw_cache is None:
1874
+ self._first_terms_raw_cache = self._fetch_first_terms_raw()
1875
+ # a shallow copy suffices - tuples are immutable
1876
+ return self._first_terms_raw_cache[:max_values]
1877
+
1878
+ def first_terms_str(self, max_values=FINDSTAT_MAX_SUBMISSION_VALUES):
1879
+ r"""
1880
+ Return the first terms of the statistic in the format needed
1881
+ for a FindStat query.
1882
+
1883
+ OUTPUT:
1884
+
1885
+ A string, where each line is of the form ``object => value``,
1886
+ where ``object`` is the string representation of an element
1887
+ of the appropriate collection as used by FindStat and value
1888
+ is an integer.
1889
+
1890
+ EXAMPLES::
1891
+
1892
+ sage: print(findstat(41).first_terms_str(max_values=4)) # optional -- internet
1893
+ [(1,2)] => 0
1894
+ [(1,2),(3,4)] => 0
1895
+ [(1,3),(2,4)] => 0
1896
+ [(1,4),(2,3)] => 1
1897
+
1898
+ TESTS:
1899
+
1900
+ Check that no more terms than asked for are computed::
1901
+
1902
+ sage: st = cached_function(lambda d: randint(1,1000))
1903
+ sage: s = findstat("Dyck paths", st, max_values=100, depth=0) # optional -- internet
1904
+ sage: len(st.cache) # optional -- internet
1905
+ 100
1906
+ sage: _ = s.first_terms_str(max_values=100) # optional -- internet
1907
+ sage: len(st.cache) # optional -- internet
1908
+ 100
1909
+ """
1910
+ return "\n".join(key + " => " + str(val)
1911
+ for key, val in self._first_terms_raw(max_values=max_values))
1912
+
1913
+ def _fetch_first_terms(self):
1914
+ r"""
1915
+ Return the first terms of the statistic as a list of ``(object,
1916
+ value)`` pairs.
1917
+
1918
+ TESTS::
1919
+
1920
+ sage: findstat(41)._fetch_first_terms()[:4] # optional -- internet
1921
+ [([(1, 2)], 0),
1922
+ ([(1, 2), (3, 4)], 0),
1923
+ ([(1, 3), (2, 4)], 0),
1924
+ ([(1, 4), (2, 3)], 1)]
1925
+ """
1926
+ from_str = self.domain().from_string()
1927
+ if self._first_terms_raw_cache is None:
1928
+ self._first_terms_raw_cache = self._fetch_first_terms_raw()
1929
+ return [(from_str(obj), Integer(val))
1930
+ for obj, val in self._first_terms_raw_cache]
1931
+
1932
+ def _generating_functions_dict(self,
1933
+ max_values=FINDSTAT_MAX_SUBMISSION_VALUES):
1934
+ r"""
1935
+ Return the generating functions of ``self`` as dictionary of
1936
+ dictionaries, computed from ``self.first_terms``.
1937
+
1938
+ TESTS:
1939
+
1940
+ sage: q = findstat((BinaryTrees(5), list(range(0,24,4))*7)) # optional -- internet
1941
+ sage: q._generating_functions_dict() # optional -- internet
1942
+ {5: {0: 7, 4: 7, 8: 7, 12: 7, 16: 7, 20: 7}}
1943
+ """
1944
+ gfs = {}
1945
+ lvls = {}
1946
+ domain = self.domain()
1947
+ levels_with_sizes = domain.levels_with_sizes()
1948
+ total = 0
1949
+ for elt, val in self.first_terms().items():
1950
+ if total == max_values:
1951
+ break
1952
+ lvl = domain.element_level(elt)
1953
+ if lvl not in levels_with_sizes:
1954
+ continue
1955
+ total += 1
1956
+ if lvl not in gfs:
1957
+ gfs[lvl] = {}
1958
+ gfs[lvl][val] = gfs[lvl].get(val, 0) + 1
1959
+ lvls[lvl] = lvls.get(lvl, 0) + 1
1960
+
1961
+ for lvl, size in lvls.items():
1962
+ if size < levels_with_sizes[lvl]:
1963
+ del gfs[lvl]
1964
+ return gfs
1965
+
1966
+ def generating_functions(self, style='polynomial',
1967
+ max_values=FINDSTAT_MAX_SUBMISSION_VALUES):
1968
+ r"""
1969
+ Return the generating functions of the statistic as a dictionary.
1970
+
1971
+ The keys of this dictionary are the levels for which the
1972
+ generating function of the statistic can be computed from
1973
+ the known data. Each value represents a generating function
1974
+ for one level, as a polynomial, as a dictionary, or as a list
1975
+ of coefficients.
1976
+
1977
+ INPUT:
1978
+
1979
+ - ``style`` -- string (default: ``'polynomial'``); can be
1980
+ ``'polynomial'``, ``'dictionary'``, or ``'list'``
1981
+
1982
+ OUTPUT:
1983
+
1984
+ - if ``style`` is ``'polynomial'``, the generating function is
1985
+ returned as a polynomial
1986
+
1987
+ - if ``style`` is ``'dictionary'``, the generating function is
1988
+ returned as a dictionary representing the monomials of the
1989
+ generating function
1990
+
1991
+ - if ``style`` is ``'list'``, the generating function is
1992
+ returned as a list of coefficients of the generating
1993
+ function. In this case, leading and trailing zeros are
1994
+ omitted.
1995
+
1996
+ EXAMPLES::
1997
+
1998
+ sage: st = findstat(41) # optional -- internet
1999
+ sage: st.generating_functions() # optional -- internet
2000
+ {2: 1,
2001
+ 4: q + 2,
2002
+ 6: q^3 + 3*q^2 + 6*q + 5,
2003
+ 8: q^6 + 4*q^5 + 10*q^4 + 20*q^3 + 28*q^2 + 28*q + 14}
2004
+
2005
+ sage: st.generating_functions(style='dictionary') # optional -- internet
2006
+ {2: {0: 1},
2007
+ 4: {0: 2, 1: 1},
2008
+ 6: {0: 5, 1: 6, 2: 3, 3: 1},
2009
+ 8: {0: 14, 1: 28, 2: 28, 3: 20, 4: 10, 5: 4, 6: 1}}
2010
+
2011
+ sage: st.generating_functions(style='list') # optional -- internet
2012
+ {2: [1], 4: [2, 1], 6: [5, 6, 3, 1], 8: [14, 28, 28, 20, 10, 4, 1]}
2013
+
2014
+ TESTS::
2015
+
2016
+ sage: st = findstat(41) # optional -- internet
2017
+ sage: st.generating_functions(max_values=19) # optional -- internet
2018
+ {2: 1, 4: q + 2, 6: q^3 + 3*q^2 + 6*q + 5}
2019
+
2020
+ sage: st = findstat("graphs", lambda G: G.size(), max_values=100) # optional -- internet
2021
+ sage: st.generating_functions(max_values=18) # optional -- internet
2022
+ {1: 1,
2023
+ 2: q + 1,
2024
+ 3: q^3 + q^2 + q + 1,
2025
+ 4: q^6 + q^5 + 2*q^4 + 3*q^3 + 2*q^2 + q + 1}
2026
+ sage: st.generating_functions(max_values=1252) # optional -- internet
2027
+ {1: 1,
2028
+ 2: q + 1,
2029
+ 3: q^3 + q^2 + q + 1,
2030
+ 4: q^6 + q^5 + 2*q^4 + 3*q^3 + 2*q^2 + q + 1,
2031
+ 5: q^10 + q^9 + 2*q^8 + 4*q^7 + 6*q^6 + 6*q^5 + 6*q^4 + 4*q^3 + 2*q^2 + q + 1,
2032
+ 6: q^15 + q^14 + 2*q^13 + 5*q^12 + 9*q^11 + 15*q^10 + 21*q^9 + 24*q^8 + 24*q^7 + 21*q^6 + 15*q^5 + 9*q^4 + 5*q^3 + 2*q^2 + q + 1,
2033
+ 7: q^21 + q^20 + 2*q^19 + 5*q^18 + 10*q^17 + 21*q^16 + 41*q^15 + 65*q^14 + 97*q^13 + 131*q^12 + 148*q^11 + 148*q^10 + 131*q^9 + 97*q^8 + 65*q^7 + 41*q^6 + 21*q^5 + 10*q^4 + 5*q^3 + 2*q^2 + q + 1}
2034
+ """
2035
+ d = self._generating_functions_dict(max_values=max_values)
2036
+ return _generating_functions_from_dict(d, style)
2037
+
2038
+ def oeis_search(self, search_size=32, verbose=True):
2039
+ r"""
2040
+ Search the OEIS for the generating function of the statistic.
2041
+
2042
+ INPUT:
2043
+
2044
+ - ``search_size`` -- (default: 32) the number of integers in the
2045
+ sequence. If this is chosen too big, the OEIS result may be
2046
+ corrupted.
2047
+
2048
+ - ``verbose`` -- boolean (default: ``True``); if ``True``, some
2049
+ information about the search are printed
2050
+
2051
+ OUTPUT: a tuple of OEIS sequences, see
2052
+ :meth:`sage.databases.oeis.OEIS.find_by_description` for more
2053
+ information
2054
+
2055
+ EXAMPLES::
2056
+
2057
+ sage: st = findstat(41) # optional -- internet
2058
+
2059
+ sage: st.oeis_search() # optional -- internet
2060
+ Searching the OEIS for "1 2,1 5,6,3,1 14,28,28,20,10,4,1"
2061
+ 0: A067311: Triangle read by rows: T(n,k) gives number of ways of arranging n chords on a circle with k simple intersections ...
2062
+ """
2063
+ from sage.databases.oeis import oeis
2064
+ gen_funcs = self.generating_functions(style='list')
2065
+
2066
+ OEIS_string = ""
2067
+ keys = sorted(gen_funcs.keys())
2068
+ counter = 0
2069
+ for key in keys:
2070
+ gen_func = gen_funcs[key]
2071
+ while gen_func[0] == 0:
2072
+ gen_func.pop(0)
2073
+ # we strip the result according to the search size. -- stumpc5, 2015-09-27
2074
+ gen_func = gen_func[:search_size]
2075
+ counter += len(gen_func)
2076
+ if search_size > 0:
2077
+ search_size -= len(gen_func)
2078
+ OEIS_func_string = ",".join(str(coefficient) for coefficient in gen_func)
2079
+ OEIS_string += OEIS_func_string + " "
2080
+ OEIS_string = OEIS_string.strip()
2081
+ if counter >= 4:
2082
+ if verbose:
2083
+ print('Searching the OEIS for "%s"' % OEIS_string)
2084
+ return oeis(OEIS_string)
2085
+
2086
+ if verbose:
2087
+ print("Too little information to search the OEIS for this statistic (only %s values given)." % counter)
2088
+
2089
+
2090
+ class FindStatStatistic(Element,
2091
+ FindStatFunction,
2092
+ FindStatCombinatorialStatistic,
2093
+ metaclass=InheritComparisonClasscallMetaclass):
2094
+ r"""
2095
+ A FindStat statistic.
2096
+
2097
+ :class:`FindStatStatistic` is a class representing a
2098
+ combinatorial statistic available in the FindStat database.
2099
+
2100
+ This class provides methods to inspect and update various
2101
+ properties of these statistics.
2102
+
2103
+ EXAMPLES::
2104
+
2105
+ sage: from sage.databases.findstat import FindStatStatistic
2106
+ sage: FindStatStatistic(41) # optional -- internet
2107
+ St000041: The number of nestings of a perfect matching.
2108
+
2109
+ .. SEEALSO::
2110
+
2111
+ :class:`FindStatStatistics`
2112
+ """
2113
+ @staticmethod
2114
+ def __classcall_private__(cls, entry):
2115
+ """
2116
+ Retrieve a statistic from the database.
2117
+
2118
+ TESTS::
2119
+
2120
+ sage: from sage.databases.findstat import FindStatStatistic
2121
+ sage: FindStatStatistic("abcdefgh") # optional -- internet
2122
+ Traceback (most recent call last):
2123
+ ...
2124
+ ValueError: the value 'abcdefgh' is not a valid FindStat statistic identifier
2125
+ """
2126
+ return FindStatStatistics()(entry)
2127
+
2128
+ def __init__(self, parent, id):
2129
+ """
2130
+ Initialize a FindStat statistic from an identifier.
2131
+
2132
+ INPUT:
2133
+
2134
+ - ``parent`` -- :class:`FindStatStatistics`
2135
+
2136
+ - ``id`` -- the (padded) FindStat identifier of the statistic
2137
+
2138
+ EXAMPLES::
2139
+
2140
+ sage: findstat(41) # optional -- internet, indirect doctest
2141
+ St000041: The number of nestings of a perfect matching.
2142
+ """
2143
+ FindStatFunction.__init__(self, id)
2144
+ FindStatCombinatorialStatistic.__init__(self)
2145
+ Element.__init__(self, parent)
2146
+
2147
+ def __call__(self, elt):
2148
+ """
2149
+ Apply the statistic to a given element.
2150
+
2151
+ EXAMPLES::
2152
+
2153
+ sage: s = lambda g: g.diameter() if g.is_connected() else None
2154
+ sage: q = findstat("graphs", s, max_values=100) # optional -- internet
2155
+ sage: q(graphs.PetersenGraph().copy(immutable=True)) # optional -- internet
2156
+ 2
2157
+ """
2158
+ val = self.first_terms().get(elt, None)
2159
+ if val is None:
2160
+ return FindStatFunction.__call__(self, elt)
2161
+ return val
2162
+
2163
+ def __reduce__(self):
2164
+ """
2165
+ Return a function and its arguments needed to create the
2166
+ statistic.
2167
+
2168
+ TESTS::
2169
+
2170
+ sage: from sage.databases.findstat import FindStatStatistic
2171
+ sage: c = FindStatStatistic(41) # optional -- internet
2172
+ sage: loads(dumps(c)) == c # optional -- internet
2173
+ True
2174
+ """
2175
+ return FindStatStatistic, (self.id(),)
2176
+
2177
+ def _richcmp_(self, other, op):
2178
+ """
2179
+ Compare two statistics by identifier.
2180
+
2181
+ TESTS::
2182
+
2183
+ sage: findstat(41) != findstat(42) # optional -- internet
2184
+ True
2185
+ sage: findstat(41) == findstat(41) # optional -- internet
2186
+ True
2187
+ """
2188
+ return richcmp(self.id(), other.id(), op)
2189
+
2190
+ def _fetch_data(self):
2191
+ r"""
2192
+ Return a dictionary containing the data of the statistic, except
2193
+ for the values, fetched from FindStat.
2194
+
2195
+ TESTS::
2196
+
2197
+ sage: findstat(41)._data() # optional -- internet, indirect doctest
2198
+ {'Bibliography': {'MathSciNet:1288802': {'Author': 'de Médicis, A., Viennot, X. G.',
2199
+ 'Title': 'Moments des $q$-polynômes de Laguerre et la bijection de Foata-Zeilberger'},
2200
+ 'MathSciNet:1418763': {'Author': 'Simion, R., Stanton, D.',
2201
+ 'Title': 'Octabasic Laguerre polynomials and permutation statistics'}},
2202
+ 'Code': 'def statistic(x):\r\n return len(x.nestings())',
2203
+ 'Description': 'The number of nestings of a perfect matching.\r\n\r\nThis is the number of pairs of edges $((a,b), (c,d))$ such that $a\\le c\\le d\\le b$. i.e., the edge $(c,d)$ is nested inside $(a,b)$.',
2204
+ 'Domain': 'Cc0012',
2205
+ 'Name': 'The number of nestings of a perfect matching.',
2206
+ 'References': '[1] [[MathSciNet:1288802]]\r\n[2] [[MathSciNet:1418763]]',
2207
+ 'SageCode': 'def statistic(x):\r\n return len(x.nestings())'}
2208
+ """
2209
+ fields = "Bibliography,Code,Description,Domain,Name,References,SageCode"
2210
+ fields_Bibliography = "Author,Title"
2211
+ url = (FINDSTAT_API_STATISTICS + self.id_str()
2212
+ + "?fields=" + fields
2213
+ + "&fields[Bibliography]=" + fields_Bibliography)
2214
+ verbose("fetching statistic data %s" % url, caller_name='FindStatStatistic')
2215
+
2216
+ included = _get_json(url)["included"]
2217
+ # slightly simplify the representation
2218
+ data = dict(included["Statistics"][self.id_str()].items())
2219
+ # we replace the list of identifiers in Bibliography with the dictionary
2220
+ data["Bibliography"] = included["References"]
2221
+ return data
2222
+
2223
+ def _fetch_first_terms_raw(self):
2224
+ r"""
2225
+ Return the first terms of the statistic, as ``(string,
2226
+ value)`` pairs, fetched from FindStat
2227
+
2228
+ TESTS::
2229
+
2230
+ sage: findstat(41)._first_terms_raw(4) # optional -- internet, indirect doctest
2231
+ [('[(1,2)]', 0),
2232
+ ('[(1,2),(3,4)]', 0),
2233
+ ('[(1,3),(2,4)]', 0),
2234
+ ('[(1,4),(2,3)]', 1)]
2235
+ """
2236
+ fields = "Values"
2237
+ url = FINDSTAT_API_STATISTICS + self.id_str() + "?fields=" + fields
2238
+ values = _get_json(url)["included"]["Statistics"][self.id_str()]["Values"]
2239
+ return [tuple(pair) for pair in values]
2240
+
2241
+ def set_first_terms(self, values):
2242
+ r"""
2243
+ Update the first terms of the statistic.
2244
+
2245
+ INPUT:
2246
+
2247
+ - ``values`` -- list of pairs of the form ``(object, value)`` where
2248
+ ``object`` is a Sage object representing an element of the
2249
+ appropriate collection and ``value`` is an integer
2250
+
2251
+ This information is used when submitting the statistic with
2252
+ :meth:`submit`.
2253
+
2254
+ .. WARNING::
2255
+
2256
+ This method cannot check whether the given values are
2257
+ actually correct. Moreover, it does not even perform any
2258
+ sanity checks.
2259
+
2260
+ TESTS::
2261
+
2262
+ sage: s = findstat(41) # optional -- internet
2263
+ sage: l = [([(1,2)], 1), ([(1,2),(3,4)], 7), ([(1,3),(2,4)], 8), ([(1,4),(2,3)], 3)]
2264
+ sage: s.set_first_terms(l) # optional -- internet
2265
+ sage: print(s.first_terms_str()) # optional -- internet
2266
+ [(1, 2)] => 1
2267
+ [(1, 2), (3, 4)] => 7
2268
+ [(1, 3), (2, 4)] => 8
2269
+ [(1, 4), (2, 3)] => 3
2270
+ sage: s.reset() # optional -- internet
2271
+ """
2272
+ to_str = self.domain().to_string()
2273
+ new = [(to_str(obj), value) for obj, value in values]
2274
+ if sorted(new) != sorted(self.first_terms_str()):
2275
+ self._modified = True
2276
+ self._first_terms_raw_cache = new
2277
+ self._first_terms_cache = values
2278
+
2279
+ def code(self):
2280
+ r"""
2281
+ Return the code associated with the statistic or map.
2282
+
2283
+ OUTPUT: string
2284
+
2285
+ Contributors are encouraged to submit Sage code in the form::
2286
+
2287
+ def statistic(x):
2288
+ ...
2289
+
2290
+ but the string may also contain code for other computer
2291
+ algebra systems.
2292
+
2293
+ EXAMPLES::
2294
+
2295
+ sage: print(findstat(41).code()) # optional -- internet
2296
+ def statistic(x):
2297
+ return len(x.nestings())
2298
+
2299
+ sage: print(findstat(118).code()) # optional -- internet
2300
+ (* in Mathematica *)
2301
+ tree = {{{{}, {}}, {{}, {}}}, {{{}, {}}, {{}, {}}}};
2302
+ Count[tree, {{___}, {{___}, {{___}, {___}}}}, {0, Infinity}]
2303
+ """
2304
+ return self._data()["Code"]
2305
+
2306
+ def set_code(self, value):
2307
+ r"""
2308
+ Set the code associated with the statistic.
2309
+
2310
+ INPUT:
2311
+
2312
+ - ``value`` -- string; code producing the values of the statistic
2313
+
2314
+ Contributors are encouraged to submit SageMath code in the form::
2315
+
2316
+ def statistic(x):
2317
+ ...
2318
+
2319
+ However, code for any other platform is accepted also.
2320
+
2321
+ This information is used when submitting the statistic with
2322
+ :meth:`submit`.
2323
+
2324
+ EXAMPLES::
2325
+
2326
+ sage: q = findstat([(d, randint(1,1000)) for d in DyckWords(4)]) # optional -- internet
2327
+ sage: q.set_code("def statistic(x):\n return randint(1,1000)") # optional -- internet
2328
+ sage: print(q.code()) # optional -- internet
2329
+ def statistic(x):
2330
+ return randint(1,1000)
2331
+ """
2332
+ if value != self.code():
2333
+ self._modified = True
2334
+ self._data_cache["Code"] = value
2335
+
2336
+ def browse(self):
2337
+ r"""
2338
+ Open the FindStat web page of the statistic in a browser.
2339
+
2340
+ EXAMPLES::
2341
+
2342
+ sage: findstat(41).browse() # optional -- webbrowser
2343
+ """
2344
+ if self.id() == 0:
2345
+ self.submit()
2346
+ else:
2347
+ webbrowser.open(FINDSTAT_URL_STATISTICS + self.id_str())
2348
+
2349
+ def submit(self, max_values=FINDSTAT_MAX_SUBMISSION_VALUES):
2350
+ r"""
2351
+ Open the FindStat web page for editing the statistic or
2352
+ submitting a new statistic in a browser.
2353
+
2354
+ TESTS::
2355
+
2356
+ sage: s = findstat([(d, randint(1,1000)) for d in DyckWords(4)]) # optional -- internet
2357
+ sage: s.set_description(u"Möbius") # optional -- internet
2358
+ sage: s.submit() # optional -- webbrowser
2359
+ """
2360
+ args = {}
2361
+ args["OriginalStatistic"] = self.id_str()
2362
+ args["Domain"] = self.domain().id_str()
2363
+ args["Values"] = self.first_terms_str(max_values=max_values)
2364
+ args["Description"] = self.description()
2365
+ args["References"] = self.references_raw()
2366
+ args["Code"] = self.code()
2367
+ args["SageCode"] = self.sage_code()
2368
+ args["CurrentAuthor"] = FindStat().user_name()
2369
+ args["CurrentEmail"] = FindStat().user_email()
2370
+
2371
+ if not self.id():
2372
+ url = FINDSTAT_NEWSTATISTIC_FORM_HEADER % FINDSTAT_URL_NEW_STATISTIC
2373
+ else:
2374
+ url = FINDSTAT_NEWSTATISTIC_FORM_HEADER % (FINDSTAT_URL_EDIT_STATISTIC + self.id_str())
2375
+ _submit(args, url)
2376
+
2377
+ # editing and submitting is really the same thing
2378
+ edit = submit
2379
+
2380
+ def __hash__(self):
2381
+ """
2382
+ Return a hash value for the statistic.
2383
+
2384
+ EXAMPLES::
2385
+
2386
+ sage: from sage.databases.findstat import FindStatMaps
2387
+ sage: list(FindStatMaps(domain=1, codomain=10)) # optional -- internet
2388
+ [Mp00061: to increasing tree, Mp00072: binary search tree: left to right]
2389
+ """
2390
+ return self.id()
2391
+
2392
+ def info(self):
2393
+ """
2394
+ Print a detailed description of the statistic.
2395
+
2396
+ EXAMPLES::
2397
+
2398
+ sage: findstat("St000042").info() # optional -- internet
2399
+ St000042: The number of crossings of a perfect matching.
2400
+ """
2401
+ print(" %s" % self)
2402
+
2403
+
2404
+ _all_statistics = {}
2405
+
2406
+
2407
+ class FindStatStatistics(UniqueRepresentation, Parent):
2408
+ r"""
2409
+ The class of FindStat statistics.
2410
+
2411
+ The elements of this class are combinatorial statistics currently
2412
+ in FindStat.
2413
+
2414
+ EXAMPLES:
2415
+
2416
+ We can print a list of the first few statistics currently in
2417
+ FindStat in a given domain::
2418
+
2419
+ sage: from sage.databases.findstat import FindStatStatistics
2420
+ sage: for st, _ in zip(FindStatStatistics("Perfect Matchings"), range(3)): # optional -- internet
2421
+ ....: print(" " + st.name())
2422
+ The number of nestings of a perfect matching.
2423
+ The number of crossings of a perfect matching.
2424
+ The number of crossings plus two-nestings of a perfect matching.
2425
+ """
2426
+ def __init__(self, domain=None):
2427
+ """
2428
+ TESTS::
2429
+
2430
+ sage: from sage.databases.findstat import FindStatStatistics
2431
+ sage: M = FindStatStatistics() # optional -- internet
2432
+ sage: TestSuite(M).run() # optional -- internet
2433
+ """
2434
+ if domain is None:
2435
+ self._domain = None
2436
+ else:
2437
+ self._domain = FindStatCollection(domain)
2438
+ self._identifiers = None
2439
+ Parent.__init__(self, category=Sets())
2440
+
2441
+ def _element_constructor_(self, id):
2442
+ """
2443
+ Initialize a FindStat statistic.
2444
+
2445
+ INPUT:
2446
+
2447
+ - ``id`` -- string containing the FindStat identifier of
2448
+ the statistic, or the corresponding integer
2449
+
2450
+ EXAMPLES::
2451
+
2452
+ sage: from sage.databases.findstat import FindStatStatistic
2453
+ sage: FindStatStatistic(41) # optional -- internet
2454
+ St000041: The number of nestings of a perfect matching.
2455
+ """
2456
+ if isinstance(id, self.Element):
2457
+ return id
2458
+ if isinstance(id, (int, Integer)):
2459
+ id = FINDSTAT_STATISTIC_PADDED_IDENTIFIER % id
2460
+ elif isinstance(id, FindStatCombinatorialStatistic):
2461
+ id = id.id_str()
2462
+ if not isinstance(id, str):
2463
+ raise TypeError("the value '%s' is not a valid FindStat statistic identifier, nor a FindStat statistic query" % id)
2464
+ else:
2465
+ id = id.strip()
2466
+ if FINDSTAT_MAP_SEPARATOR in id:
2467
+ return FindStatCompoundStatistic(id)
2468
+ if not re.match(FINDSTAT_STATISTIC_REGEXP, id) or int(id[2:]) <= 0:
2469
+ raise ValueError("the value '%s' is not a valid FindStat statistic identifier" % id)
2470
+ if id not in _all_statistics or _all_statistics[id] is None:
2471
+ _all_statistics[id] = self.element_class(self, id)
2472
+
2473
+ return _all_statistics[id]
2474
+
2475
+ def _repr_(self):
2476
+ """
2477
+ Return a short description of the set of FindStat statistics.
2478
+
2479
+ EXAMPLES::
2480
+
2481
+ sage: from sage.databases.findstat import FindStatStatistics
2482
+ sage: FindStatStatistics() # optional -- internet
2483
+ Set of combinatorial statistics in FindStat
2484
+
2485
+ sage: FindStatStatistics(12) # optional -- internet
2486
+ Set of combinatorial statistics with domain Cc0012: Perfect matchings in FindStat
2487
+ """
2488
+ if self._domain is None:
2489
+ return "Set of combinatorial statistics in FindStat"
2490
+ return "Set of combinatorial statistics with domain %s in FindStat" % self._domain
2491
+
2492
+ def __iter__(self):
2493
+ """
2494
+ Return an iterator over all FindStat statistics.
2495
+
2496
+ EXAMPLES::
2497
+
2498
+ sage: from sage.databases.findstat import FindStatStatistics
2499
+ sage: next(iter(FindStatStatistics("Perfect Matchings"))) # optional -- internet
2500
+ St000041: The number of nestings of a perfect matching.
2501
+ """
2502
+ if self._identifiers is None:
2503
+ if self._domain is None:
2504
+ url = FINDSTAT_API_STATISTICS
2505
+ else:
2506
+ url = FINDSTAT_API_STATISTICS + "?Domain=%s" % self._domain.id_str()
2507
+
2508
+ self._identifiers = _get_json(url)["data"]
2509
+
2510
+ for st in self._identifiers:
2511
+ yield FindStatStatistic(st)
2512
+
2513
+ def _an_element_(self):
2514
+ """
2515
+ Return a FindStat statistic.
2516
+
2517
+ EXAMPLES::
2518
+
2519
+ sage: findstat(domain='Permutations').an_element() # optional -- internet
2520
+ St000001: The number of reduced words for a permutation.
2521
+ """
2522
+ try:
2523
+ return next(iter(self))
2524
+ except StopIteration:
2525
+ from sage.categories.sets_cat import EmptySetError
2526
+ raise EmptySetError
2527
+
2528
+ Element = FindStatStatistic
2529
+
2530
+
2531
+ class FindStatStatisticQuery(FindStatStatistic):
2532
+ """
2533
+ A class representing a query for FindStat (compound) statistics.
2534
+ """
2535
+ def __init__(self, data=None, values_of=None, distribution_of=None,
2536
+ domain=None, known_terms=None, function=None,
2537
+ depth=FINDSTAT_DEFAULT_DEPTH,
2538
+ debug=False):
2539
+ """
2540
+ Initialize a query for FindStat (compound) statistics.
2541
+
2542
+ INPUT:
2543
+
2544
+ - ``data`` -- (optional), a list of pairs ``(objects,
2545
+ values)``, where ``objects`` and ``values`` are all lists
2546
+ of the same length, the former are elements in the FindStat
2547
+ collection, the latter are integers
2548
+
2549
+ - ``known_terms`` -- (optional), a lazy list in the same format
2550
+ as ``data``, which agrees with ``data``, and may be used
2551
+ for submission
2552
+
2553
+ - ``values_of`` -- (optional), anything accepted by
2554
+ :class:`FindStatCompoundStatistic`
2555
+
2556
+ - ``distribution_of`` -- (optional), anything accepted by
2557
+ :class:`FindStatCompoundStatistic`
2558
+
2559
+ - ``domain`` -- (optional), anything accepted by
2560
+ :class:`FindStatCollection`
2561
+
2562
+ - ``depth`` -- (optional), the number of maps to apply before
2563
+ applying the statistic
2564
+
2565
+
2566
+ Only one of ``data``, ``values_of`` and ``distribution_of``
2567
+ may be provided. The parameter ``domain`` must be provided
2568
+ if and only if ``data`` is provided, or ``values_of`` or
2569
+ ``distribution_of`` are given as a function.
2570
+
2571
+ The parameter ``known_terms`` is only allowed, if ``data`` is
2572
+ provided. It defaults to ``data``.
2573
+
2574
+ EXAMPLES::
2575
+
2576
+ sage: from sage.databases.findstat import FindStatStatisticQuery
2577
+ sage: data = [[[m], [m.number_of_nestings()]] for n in range(5) for m in PerfectMatchings(2*n)]
2578
+ sage: FindStatStatisticQuery(domain=12, data=data, depth=1) # optional -- internet
2579
+ 0: St000041 (quality [99, 100])
2580
+ 1: St000041oMp00113 (quality [99, 100])
2581
+ 2: St000042oMp00116 (quality [99, 100])
2582
+ ...
2583
+ """
2584
+ self._first_terms = data
2585
+ if data is not None and known_terms is None:
2586
+ self._known_terms = data
2587
+ else:
2588
+ self._known_terms = known_terms
2589
+ self._values_of = None
2590
+ self._distribution_of = None
2591
+ self._depth = depth
2592
+
2593
+ if data is not None:
2594
+ assert all(param is None for param in [distribution_of, values_of])
2595
+
2596
+ domain = FindStatCollection(domain)
2597
+ query = {"Domain": domain.id_str(),
2598
+ "Data": _data_to_str(self._first_terms, domain)}
2599
+
2600
+ elif distribution_of is not None:
2601
+ assert all(param is None for param in [data, known_terms, values_of])
2602
+
2603
+ self._distribution_of = FindStatCompoundStatistic(distribution_of)
2604
+ domain = self._distribution_of.domain()
2605
+ query = {"DistributionOf": self._distribution_of.id_str()}
2606
+
2607
+ elif values_of is not None:
2608
+ assert all(param is None for param in [data, known_terms, distribution_of])
2609
+
2610
+ self._values_of = FindStatCompoundStatistic(values_of)
2611
+ domain = self._values_of.domain()
2612
+ query = {"ValuesOf": self._values_of.id_str()}
2613
+
2614
+ else:
2615
+ raise ValueError("incompatible set of parameters: data: %s, distribution_of: %s, values_of: %s" % ((data, distribution_of, values_of)))
2616
+
2617
+ if depth is not None:
2618
+ query["Depth"] = depth
2619
+
2620
+ query["fields"] = "MatchingStatistic,Offset,Quality"
2621
+ if debug:
2622
+ print(query)
2623
+ verbose("querying FindStat %s" % query, caller_name='FindStatStatisticQuery')
2624
+ response = _post_json(FINDSTAT_API_STATISTICS, query)
2625
+
2626
+ if debug:
2627
+ print(response)
2628
+ if "data" not in response:
2629
+ raise ValueError(response["error"])
2630
+
2631
+ result = []
2632
+ for match in response["data"]:
2633
+ entry = response["included"]["MatchingStatistics"][match]
2634
+ result.append(FindStatMatchingStatistic(entry["MatchingStatistic"],
2635
+ entry["Offset"],
2636
+ entry["Quality"],
2637
+ domain=domain))
2638
+
2639
+ self._result = FancyTuple(result)
2640
+
2641
+ FindStatFunction.__init__(self, FINDSTAT_STATISTIC_PADDED_IDENTIFIER % 0,
2642
+ data={"Bibliography": {},
2643
+ "Code": _get_code_from_callable(function),
2644
+ "Description": "",
2645
+ "Domain": domain,
2646
+ "Name": "a new statistic on %s" % domain.name("plural"),
2647
+ "References": "",
2648
+ "SageCode": ""},
2649
+ function=function)
2650
+ Element.__init__(self, FindStatStatistics()) # this is not completely correct, but it works
2651
+
2652
+ def first_terms(self, max_values=FINDSTAT_MAX_SUBMISSION_VALUES):
2653
+ """
2654
+ Return the pairs of the known terms which contain singletons as a dictionary.
2655
+
2656
+ EXAMPLES::
2657
+
2658
+ sage: PM = PerfectMatchings
2659
+ sage: l = [(PM(2*n), [m.number_of_nestings() for m in PM(2*n)]) for n in range(5)]
2660
+ sage: r = findstat(l, depth=0); r # optional -- internet
2661
+ 0: St000041 (quality [99, 100])
2662
+ 1: St000042 (quality [99, 100])
2663
+ sage: r.first_terms() # optional -- internet
2664
+ {[]: 0, [(1, 2)]: 0}
2665
+ """
2666
+ return dict(itertools.islice(((objs[0], vals[0])
2667
+ for objs, vals in self._known_terms
2668
+ if len(vals) == 1), max_values))
2669
+
2670
+ def _first_terms_raw(self, max_values):
2671
+ """
2672
+ Return the first terms as ``(string, value)`` pairs.
2673
+
2674
+ EXAMPLES::
2675
+
2676
+ sage: PM = PerfectMatchings
2677
+ sage: l = [(PM(2*n), [m.number_of_nestings() for m in PM(2*n)]) for n in range(5)]
2678
+ sage: r = findstat(l, depth=0); r # optional -- internet
2679
+ 0: St000041 (quality [99, 100])
2680
+ 1: St000042 (quality [99, 100])
2681
+ sage: r._first_terms_raw(100) # optional -- internet
2682
+ [('[]', 0), ('[(1, 2)]', 0)]
2683
+ """
2684
+ to_str = self.domain().to_string()
2685
+ return [(to_str(obj), val)
2686
+ for obj, val in self.first_terms(max_values=max_values).items()]
2687
+
2688
+ def _generating_functions_dict(self,
2689
+ max_values=FINDSTAT_MAX_SUBMISSION_VALUES):
2690
+ """
2691
+ Return the generating functions of the levels where all values
2692
+ can be determined.
2693
+
2694
+ TESTS::
2695
+
2696
+ sage: n = 3; l = lambda i: [pi for pi in Permutations(n) if pi(1) == i]
2697
+ sage: data = [([pi for pi in l(i)], [pi(1) for pi in l(i)]) for i in range(1,n+1)]
2698
+ sage: data.append((Permutation([1,2]), 1))
2699
+ sage: q = findstat(data, depth=0); q # optional -- internet
2700
+ 0: St000054 (quality [100, 100])
2701
+ sage: q.first_terms() # optional -- internet
2702
+ {[1, 2]: 1}
2703
+ sage: q.generating_functions() # optional -- internet, indirect doctest
2704
+ {3: 2*q^3 + 2*q^2 + 2*q}
2705
+ """
2706
+ return _distribution_from_data(self._known_terms,
2707
+ self.domain(),
2708
+ max_values,
2709
+ generating_functions=True)
2710
+
2711
+ def __repr__(self):
2712
+ """
2713
+ Return a string representation of the query.
2714
+
2715
+ EXAMPLES::
2716
+
2717
+ sage: PM = PerfectMatchings
2718
+ sage: data = [(m, m.number_of_nestings()) for n in range(6) for m in PM(2*n)]
2719
+ sage: findstat(data, depth=1) # optional -- internet
2720
+ 0: St000042oMp00116 (quality [100, 100])
2721
+ 1: St000041 (quality [20, 100])
2722
+ ...
2723
+ """
2724
+ if self._result:
2725
+ return repr(self._result)
2726
+ return "%s: %s" % (self.id_str(), self.name())
2727
+
2728
+ def __getitem__(self, i):
2729
+ """
2730
+ Return the `t`-th result in the query.
2731
+
2732
+ EXAMPLES::
2733
+
2734
+ sage: PM = PerfectMatchings
2735
+ sage: data = [(m, m.number_of_nestings()) for n in range(6) for m in PM(2*n)]
2736
+ sage: r = findstat(data, depth=1) # optional -- internet
2737
+ sage: r[1] # optional -- internet
2738
+ St000041 (quality [20, 100])
2739
+ """
2740
+ return self._result[i]
2741
+
2742
+ def __len__(self):
2743
+ """
2744
+ Return the number of results in the query.
2745
+
2746
+ EXAMPLES::
2747
+
2748
+ sage: r = findstat(Permutations, lambda pi: pi.saliances()[0], depth=1) # optional -- internet
2749
+ sage: len(r) > 4 # optional -- internet
2750
+ True
2751
+ """
2752
+ return len(self._result)
2753
+
2754
+
2755
+ class FindStatCompoundStatistic(Element, FindStatCombinatorialStatistic):
2756
+ def __init__(self, id, domain=None, check=True):
2757
+ """
2758
+ Initialize a compound statistic.
2759
+
2760
+ A compound statistic is a sequence of maps followed by a statistic.
2761
+
2762
+ INPUT:
2763
+
2764
+ - ``id`` -- a padded identifier
2765
+
2766
+ - ``domain`` -- (optional), the domain of the compound statistic
2767
+
2768
+ - ``check`` -- whether to check that domains and codomains fit
2769
+
2770
+ If the domain is given and ``check`` is ``False``, it is not
2771
+ fetched from FindStat.
2772
+
2773
+ TESTS::
2774
+
2775
+ sage: findstat("St000041oMp00127") # optional -- internet
2776
+ Traceback (most recent call last):
2777
+ ...
2778
+ ValueError: the statistic St000041: The number of nestings of a perfect matching. cannot be composed with the map Mp00127
2779
+ """
2780
+ if isinstance(id, (int, Integer)):
2781
+ id = FINDSTAT_STATISTIC_PADDED_IDENTIFIER % id
2782
+ elif isinstance(id, FindStatCombinatorialStatistic):
2783
+ id = id.id_str()
2784
+ if domain is not None:
2785
+ self._domain = FindStatCollection(domain)
2786
+ else:
2787
+ self._domain = None
2788
+ composition = id.partition(FINDSTAT_MAP_SEPARATOR)
2789
+ self._statistic = FindStatStatistic(composition[0])
2790
+ if composition[2]:
2791
+ self._maps = FindStatCompoundMap(composition[2], domain=self._domain)
2792
+ self._id = self._statistic.id_str() + FINDSTAT_MAP_SEPARATOR + self._maps.id_str()
2793
+ if self._domain is None:
2794
+ self._domain = self._maps.domain()
2795
+ else:
2796
+ if self._domain is None:
2797
+ self._domain = self._statistic.domain()
2798
+ self._maps = FindStatCompoundMap("", domain=self._domain, codomain=self._domain)
2799
+ self._id = self._statistic.id_str()
2800
+ if (check
2801
+ and self._maps.codomain() != self._statistic.domain()):
2802
+ raise ValueError("the statistic %s cannot be composed with the map %s" % (self._statistic, self._maps))
2803
+
2804
+ FindStatCombinatorialStatistic.__init__(self)
2805
+ Element.__init__(self, FindStatStatistics()) # this is not completely correct, but it works
2806
+
2807
+ def _fetch_first_terms_raw(self):
2808
+ r"""
2809
+ Return the first terms of the compound statistic, as ``(string,
2810
+ value)`` pairs, fetched from FindStat.
2811
+
2812
+ TESTS::
2813
+
2814
+ sage: findstat("St000042oMp00116")._first_terms_raw(4) # optional -- internet, indirect doctest
2815
+ [('[(1,2)]', 0),
2816
+ ('[(1,2),(3,4)]', 0),
2817
+ ('[(1,3),(2,4)]', 0),
2818
+ ('[(1,4),(2,3)]', 1)]
2819
+ """
2820
+ fields = "Values"
2821
+ url = FINDSTAT_API_STATISTICS + self.id_str() + "?fields=" + fields
2822
+ if len(self._maps):
2823
+ values = _get_json(url)["included"]["CompoundStatistics"][self.id_str()]["Values"]
2824
+ else:
2825
+ values = _get_json(url)["included"]["Statistics"][self.id_str()]["Values"]
2826
+ return [(sequence[0], sequence[-1]) for sequence in values]
2827
+
2828
+ def domain(self):
2829
+ """
2830
+ Return the domain of the compound statistic.
2831
+
2832
+ EXAMPLES::
2833
+
2834
+ sage: findstat("St000042oMp00116").domain() # optional -- internet
2835
+ Cc0012: Perfect matchings
2836
+ """
2837
+ return self._domain
2838
+
2839
+ def __call__(self, elt):
2840
+ """
2841
+ Apply the compound statistic to the given element.
2842
+
2843
+ Note that this is only possible if execution of code is
2844
+ enabled, by setting the attribute ``_function`` of each map
2845
+ and the statistic to ``True``.
2846
+
2847
+ EXAMPLES::
2848
+
2849
+ sage: findstat("St000042oMp00116")(PerfectMatching([(1,2)])) # optional -- internet
2850
+ Traceback (most recent call last):
2851
+ ...
2852
+ ValueError: execution of verified code provided by FindStat is not enabled for Mp00116: Kasraoui-Zeng
2853
+ """
2854
+ return self.statistic()(self.compound_map()(elt))
2855
+
2856
+ def id_str(self):
2857
+ """
2858
+ Return the padded identifier of the compound statistic.
2859
+
2860
+ EXAMPLES::
2861
+
2862
+ sage: findstat("St000042oMp00116").id_str() # optional -- internet
2863
+ 'St000042oMp00116'
2864
+ """
2865
+ return self._id
2866
+
2867
+ def _repr_(self):
2868
+ """
2869
+ Return a string representation of the compound statistic.
2870
+
2871
+ EXAMPLES::
2872
+
2873
+ sage: findstat("St000042oMp00116") # optional -- internet
2874
+ St000042oMp00116
2875
+ """
2876
+ return self.id_str()
2877
+
2878
+ def statistic(self):
2879
+ """
2880
+ Return the statistic of the compound statistic.
2881
+
2882
+ EXAMPLES::
2883
+
2884
+ sage: findstat("St000041oMp00116").statistic() # optional -- internet
2885
+ St000041: The number of nestings of a perfect matching.
2886
+ """
2887
+ return self._statistic
2888
+
2889
+ def compound_map(self):
2890
+ """
2891
+ Return the compound map which is part of the compound statistic.
2892
+
2893
+ EXAMPLES::
2894
+
2895
+ sage: findstat("St000051oMp00061oMp00069").compound_map() # optional -- internet
2896
+ Mp00061oMp00069
2897
+ """
2898
+ return self._maps
2899
+
2900
+ def browse(self):
2901
+ r"""
2902
+ Open the FindStat web page of the compound statistic in a browser.
2903
+
2904
+ EXAMPLES::
2905
+
2906
+ sage: from sage.databases.findstat import FindStatCompoundStatistic
2907
+ sage: FindStatCompoundStatistic("St000042oMp00116").browse() # optional -- webbrowser
2908
+ """
2909
+ webbrowser.open(FINDSTAT_URL_STATISTICS + self.id_str())
2910
+
2911
+ def info(self):
2912
+ """
2913
+ Print a detailed description of the compound statistic.
2914
+
2915
+ EXAMPLES::
2916
+
2917
+ sage: findstat("St000042oMp00116").info() # optional -- internet
2918
+ Mp00116: Kasraoui-Zeng: Perfect matchings -> Perfect matchings
2919
+ St000042: The number of crossings of a perfect matching.
2920
+ """
2921
+ if len(self.compound_map()):
2922
+ self.compound_map().info()
2923
+ self.statistic().info()
2924
+
2925
+
2926
+ class FindStatMatchingStatistic(FindStatCompoundStatistic):
2927
+ def __init__(self, matching_statistic, offset, quality, domain=None):
2928
+ """
2929
+ Initialize a FindStat statistic match.
2930
+
2931
+ INPUT:
2932
+
2933
+ - ``matching_statistic`` -- a compound statistic identifier
2934
+
2935
+ - ``offset`` -- the offset of the values, as provided by FindStat
2936
+
2937
+ - ``quality`` -- the quality of the match, as provided by FindStat
2938
+
2939
+ - ``domain`` -- (optional) the domain of the compound statistic
2940
+
2941
+ EXAMPLES::
2942
+
2943
+ sage: from sage.databases.findstat import FindStatMatchingStatistic
2944
+ sage: FindStatMatchingStatistic("St000042oMp00116", 1, [17, 83]) # optional -- internet
2945
+ St000042oMp00116 with offset 1 (quality [17, 83])
2946
+ """
2947
+ self._quality = quality
2948
+ self._offset = offset
2949
+ # we can trust that matches have fitting domain / codomain sequence
2950
+ FindStatCompoundStatistic.__init__(self, matching_statistic, domain=domain, check=False)
2951
+
2952
+ def _repr_(self):
2953
+ """
2954
+ Return a string representation of the match.
2955
+
2956
+ EXAMPLES::
2957
+
2958
+ sage: from sage.databases.findstat import FindStatMatchingStatistic
2959
+ sage: FindStatMatchingStatistic("St000042oMp00116", 1, [17, 83]) # optional -- internet
2960
+ St000042oMp00116 with offset 1 (quality [17, 83])
2961
+ """
2962
+ if self._offset:
2963
+ return "%s with offset %s (quality %s)" % (self.id_str(), self._offset, self._quality)
2964
+ return "%s (quality %s)" % (self.id_str(), self.quality())
2965
+
2966
+ def offset(self):
2967
+ """
2968
+ Return the offset which has to be added to each value of the
2969
+ compound statistic to obtain the desired value.
2970
+
2971
+ EXAMPLES::
2972
+
2973
+ sage: from sage.databases.findstat import FindStatMatchingStatistic
2974
+ sage: r = FindStatMatchingStatistic("St000042oMp00116", 1, [17, 83]) # optional -- internet
2975
+ sage: r.offset() # optional -- internet
2976
+ 1
2977
+ """
2978
+ return self._offset
2979
+
2980
+ def quality(self):
2981
+ """
2982
+ Return the quality of the match, as provided by FindStat.
2983
+
2984
+ The quality of a statistic match is a pair of percentages
2985
+ `(q_a, q_d)`, where `q_a` is the percentage of ``(object,
2986
+ value)`` pairs that are in the database among those which
2987
+ were sent to FindStat, and `q_d` is the percentage of
2988
+ ``(object, value)`` pairs with distinct values in the
2989
+ database among those which were sent to FindStat.
2990
+
2991
+ EXAMPLES::
2992
+
2993
+ sage: from sage.databases.findstat import FindStatMatchingStatistic
2994
+ sage: r = FindStatMatchingStatistic("St000042oMp00116", 1, [17, 83]) # optional -- internet
2995
+ sage: r.quality() # optional -- internet
2996
+ [17, 83]
2997
+ """
2998
+ return self._quality[:]
2999
+
3000
+ def info(self):
3001
+ """
3002
+ Print a detailed explanation of the match.
3003
+
3004
+ EXAMPLES::
3005
+
3006
+ sage: from sage.databases.findstat import FindStatMatchingStatistic
3007
+ sage: r = FindStatMatchingStatistic("St000042oMp00116", 1, [17, 83]) # optional -- internet
3008
+ sage: r.info() # optional -- internet
3009
+ after adding 1 to every value
3010
+ and applying
3011
+ Mp00116: Kasraoui-Zeng: Perfect matchings -> Perfect matchings
3012
+ to the objects (see `.compound_map()` for details)
3013
+ <BLANKLINE>
3014
+ your input matches
3015
+ St000042: The number of crossings of a perfect matching.
3016
+ <BLANKLINE>
3017
+ among the values you sent, 17 percent are actually in the database,
3018
+ among the distinct values you sent, 83 percent are actually in the database
3019
+
3020
+ sage: r = FindStatMatchingStatistic("St000042", 1, [17, 83]) # optional -- internet
3021
+ sage: r.info() # optional -- internet
3022
+ after adding 1 to every value
3023
+ <BLANKLINE>
3024
+ your input matches
3025
+ St000042: The number of crossings of a perfect matching.
3026
+ <BLANKLINE>
3027
+ among the values you sent, 17 percent are actually in the database,
3028
+ among the distinct values you sent, 83 percent are actually in the database
3029
+ """
3030
+ if self.offset() < 0:
3031
+ print("after subtracting %s from every value" % (-self.offset()))
3032
+ if self.offset() > 0:
3033
+ print("after adding %s to every value" % self.offset())
3034
+ if len(self.compound_map()):
3035
+ if self.offset():
3036
+ print("and applying")
3037
+ else:
3038
+ print("after applying")
3039
+ self.compound_map().info()
3040
+ print("to the objects (see `.compound_map()` for details)")
3041
+ print()
3042
+ print("your input matches")
3043
+ self.statistic().info()
3044
+ print()
3045
+ print("among the values you sent, %s percent are actually in the database," % self.quality()[0])
3046
+ print("among the distinct values you sent, %s percent are actually in the database" % self.quality()[1])
3047
+
3048
+ ######################################################################
3049
+ # maps
3050
+ ######################################################################
3051
+
3052
+
3053
+ class FindStatCombinatorialMap(SageObject):
3054
+ """
3055
+ A class serving as common ancestor of :class:`FindStatStatistic`
3056
+ and :class:`FindStatCompoundStatistic`.
3057
+ """
3058
+ pass
3059
+
3060
+
3061
+ class FindStatMap(Element,
3062
+ FindStatFunction,
3063
+ FindStatCombinatorialMap,
3064
+ metaclass=InheritComparisonClasscallMetaclass):
3065
+ r"""
3066
+ A FindStat map.
3067
+
3068
+ :class:`FindStatMap` is a class representing a combinatorial
3069
+ map available in the FindStat database.
3070
+
3071
+ This class provides methods to inspect various properties of
3072
+ these maps, in particular :meth:`code`.
3073
+
3074
+ EXAMPLES::
3075
+
3076
+ sage: from sage.databases.findstat import FindStatMap
3077
+ sage: FindStatMap(116) # optional -- internet
3078
+ Mp00116: Kasraoui-Zeng
3079
+
3080
+ .. SEEALSO::
3081
+
3082
+ :class:`FindStatMaps`
3083
+ """
3084
+ @staticmethod
3085
+ def __classcall_private__(cls, entry):
3086
+ """
3087
+ Retrieve a map from the database.
3088
+
3089
+ TESTS::
3090
+
3091
+ sage: from sage.databases.findstat import FindStatMap
3092
+ sage: FindStatMap("abcdefgh") # optional -- internet
3093
+ Traceback (most recent call last):
3094
+ ...
3095
+ ValueError: the value 'abcdefgh' is not a valid FindStat map identifier
3096
+ """
3097
+ return FindStatMaps()(entry)
3098
+
3099
+ def __init__(self, parent, id):
3100
+ """
3101
+ Initialize the map.
3102
+
3103
+ This should only be called in
3104
+ :meth:`FindStatMaps()._element_constructor_` via
3105
+ :meth:`FindStatMaps().element_class`.
3106
+
3107
+ INPUT:
3108
+
3109
+ - ``parent`` -- :class:`FindStatMaps`
3110
+
3111
+ - ``id`` -- the (padded) FindStat identifier of the statistic
3112
+
3113
+ TESTS::
3114
+
3115
+ sage: from sage.databases.findstat import FindStatMap
3116
+ sage: FindStatMap(116).parent() # optional -- internet
3117
+ Set of combinatorial maps used by FindStat
3118
+ """
3119
+ FindStatFunction.__init__(self, id)
3120
+ Element.__init__(self, parent)
3121
+
3122
+ def __reduce__(self):
3123
+ """
3124
+ Return a function and its arguments needed to create the map.
3125
+
3126
+ TESTS::
3127
+
3128
+ sage: from sage.databases.findstat import FindStatMap
3129
+ sage: c = FindStatMap(116) # optional -- internet
3130
+ sage: loads(dumps(c)) == c # optional -- internet
3131
+ True
3132
+ """
3133
+ return (FindStatMap, (self.id(),))
3134
+
3135
+ def _richcmp_(self, other, op):
3136
+ """
3137
+ Compare two maps by identifier.
3138
+
3139
+ TESTS::
3140
+
3141
+ sage: findmap(61) != findmap(62) # optional -- internet
3142
+ True
3143
+ sage: findmap(61) == findstat(61) # optional -- internet
3144
+ False
3145
+ """
3146
+ return richcmp(self.id(), other.id(), op)
3147
+
3148
+ def _fetch_data(self):
3149
+ r"""
3150
+ Return a dictionary containing the data of the map, fetched from
3151
+ FindStat.
3152
+
3153
+ TESTS::
3154
+
3155
+ sage: findmap(64)._data() # optional -- internet, indirect doctest
3156
+ {'Bibliography': {},
3157
+ 'Codomain': 'Cc0001',
3158
+ 'Description': 'Sends a permutation to its reverse.\r\n\r\nThe reverse of a permutation $\\sigma$ of length $n$ is given by $\\tau$ with $\\tau(i) = \\sigma(n+1-i)$.',
3159
+ 'Domain': 'Cc0001',
3160
+ 'Name': 'reverse',
3161
+ 'Properties': 'bijective, graded, involutive',
3162
+ 'References': '',
3163
+ 'SageCode': 'def mapping(sigma):\r\n return sigma.reverse()'}
3164
+ """
3165
+ fields = "Bibliography,Codomain,Description,Domain,Name,Properties,References,SageCode"
3166
+ fields_Bibliography = "Author,Title"
3167
+ url = (FINDSTAT_API_MAPS + self.id_str()
3168
+ + "?fields=" + fields
3169
+ + "&fields[Bibliography]=" + fields_Bibliography)
3170
+ verbose("fetching map data %s" % url, caller_name='FindStatMap')
3171
+ included = _get_json(url)["included"]
3172
+ # slightly simplify the representation
3173
+ data = included["Maps"][self.id_str()]
3174
+ # we replace the list of identifiers in Bibliography with the dictionary
3175
+ data["Bibliography"] = included["References"]
3176
+ return data
3177
+
3178
+ def browse(self):
3179
+ r"""
3180
+ Open the FindStat web page of the map in a browser.
3181
+
3182
+ EXAMPLES::
3183
+
3184
+ sage: from sage.databases.findstat import FindStatMap
3185
+ sage: FindStatMap(116).browse() # optional -- webbrowser
3186
+ """
3187
+ if self.id() == 0:
3188
+ self.submit()
3189
+ else:
3190
+ webbrowser.open(FINDSTAT_URL_MAPS + self.id_str())
3191
+
3192
+ def submit(self):
3193
+ r"""
3194
+ Open the FindStat web page for editing the map in a browser.
3195
+
3196
+ TESTS::
3197
+
3198
+ sage: s = findmap(62) # optional -- internet
3199
+ sage: s.set_name(u"Möbius") # optional -- internet
3200
+ sage: s.submit() # optional -- webbrowser
3201
+ sage: s.reset() # optional -- internet
3202
+ """
3203
+ args = dict()
3204
+ args["OriginalMap"] = self.id_str()
3205
+ args["Domain"] = self.domain().id_str()
3206
+ args["Codomain"] = self.codomain().id_str()
3207
+ args["Name"] = self.name()
3208
+ args["Description"] = self.description()
3209
+ args["References"] = self.references_raw()
3210
+ args["Properties"] = self.properties_raw()
3211
+ args["SageCode"] = self.sage_code()
3212
+ args["CurrentAuthor"] = FindStat().user_name()
3213
+ args["CurrentEmail"] = FindStat().user_email()
3214
+
3215
+ if not self.id():
3216
+ url = FINDSTAT_NEWMAP_FORM_HEADER % FINDSTAT_URL_NEW_MAP
3217
+ else:
3218
+ url = FINDSTAT_NEWMAP_FORM_HEADER % (FINDSTAT_URL_EDIT_MAP + self.id_str())
3219
+ _submit(args, url)
3220
+
3221
+ # editing and submitting is really the same thing
3222
+ edit = submit
3223
+
3224
+ def __hash__(self):
3225
+ """
3226
+ Return a hash value for the map.
3227
+
3228
+ EXAMPLES::
3229
+
3230
+ sage: from sage.databases.findstat import FindStatMaps
3231
+ sage: sorted(list(FindStatMaps(domain=1, codomain=10))) # optional -- internet, indirect doctest
3232
+ [Mp00061: to increasing tree, Mp00072: binary search tree: left to right]
3233
+ """
3234
+ return self.id()
3235
+
3236
+ def codomain(self):
3237
+ r"""
3238
+ Return the FindStat collection which is the codomain of the map.
3239
+
3240
+ OUTPUT: the codomain of the map as a :class:`FindStatCollection`
3241
+
3242
+ EXAMPLES::
3243
+
3244
+ sage: from sage.databases.findstat import FindStatMap # optional -- internet
3245
+ sage: FindStatMap(27).codomain() # optional -- internet
3246
+ Cc0002: Integer partitions
3247
+ """
3248
+ return FindStatCollection(self._data()["Codomain"])
3249
+
3250
+ def properties_raw(self):
3251
+ r"""
3252
+ Return the properties of the map.
3253
+
3254
+ OUTPUT: the properties as a string
3255
+
3256
+ EXAMPLES::
3257
+
3258
+ sage: from sage.databases.findstat import FindStatMap # optional -- internet
3259
+ sage: FindStatMap(61).properties_raw() # optional -- internet
3260
+ 'surjective, graded'
3261
+ """
3262
+ return self._data()["Properties"]
3263
+
3264
+ def set_properties_raw(self, value):
3265
+ r"""
3266
+ Set the properties of the map.
3267
+
3268
+ EXAMPLES::
3269
+
3270
+ sage: # optional - internet
3271
+ sage: from sage.databases.findstat import FindStatMap
3272
+ sage: FindStatMap(61).set_properties_raw('surjective')
3273
+ sage: FindStatMap(61).properties_raw()
3274
+ 'surjective'
3275
+ sage: FindStatMap(61)
3276
+ Mp00061(modified): to increasing tree
3277
+ sage: FindStatMap(61).reset()
3278
+ sage: FindStatMap(61)
3279
+ Mp00061: to increasing tree
3280
+ """
3281
+ if value != self.properties_raw():
3282
+ self._modified = True
3283
+ self._data_cache["Properties"] = value
3284
+
3285
+ def set_name(self, value):
3286
+ r"""
3287
+ Set the name of the map.
3288
+
3289
+ INPUT:
3290
+
3291
+ - ``value`` -- string; the new name of the map
3292
+
3293
+ This information is used when submitting the map with
3294
+ :meth:`submit`.
3295
+
3296
+ TESTS::
3297
+
3298
+ sage: s = findmap(62) # optional -- internet
3299
+ sage: s.set_name(u"Möbius"); s # optional -- internet
3300
+ Mp00062(modified): Möbius
3301
+ sage: s.reset(); s # optional -- internet
3302
+ Mp00062: Lehmer-code to major-code bijection
3303
+ """
3304
+ if value != self.name():
3305
+ self._modified = True
3306
+ self._data_cache["Name"] = value
3307
+
3308
+ def info(self):
3309
+ """
3310
+ Print a detailed description of the map.
3311
+
3312
+ EXAMPLES::
3313
+
3314
+ sage: findmap("Mp00116").info() # optional -- internet
3315
+ Mp00116: Kasraoui-Zeng: Perfect matchings -> Perfect matchings
3316
+ """
3317
+ print(" %s: %s -> %s" % (self,
3318
+ self.domain().name("plural"),
3319
+ self.codomain().name("plural")))
3320
+
3321
+
3322
+ _all_maps = {}
3323
+
3324
+
3325
+ class FindStatMaps(UniqueRepresentation, Parent):
3326
+ r"""
3327
+ The class of FindStat maps.
3328
+
3329
+ The elements of this class are combinatorial maps currently in
3330
+ FindStat.
3331
+
3332
+ EXAMPLES:
3333
+
3334
+ We can print a sample map for each domain and codomain::
3335
+
3336
+ sage: from sage.databases.findstat import FindStatCollections, FindStatMaps
3337
+ sage: ccs = sorted(FindStatCollections())[:3] # optional -- internet
3338
+ sage: for cc_dom in ccs: # optional -- internet
3339
+ ....: for cc_codom in ccs:
3340
+ ....: print(cc_dom.name(style='plural') + " -> " + cc_codom.name(style='plural'))
3341
+ ....: try:
3342
+ ....: print(" " + next(iter(FindStatMaps(cc_dom, cc_codom))).name())
3343
+ ....: except StopIteration:
3344
+ ....: pass
3345
+ Permutations -> Permutations
3346
+ Lehmer-code to major-code bijection
3347
+ Permutations -> Integer partitions
3348
+ Robinson-Schensted tableau shape
3349
+ Permutations -> Dyck paths
3350
+ left-to-right-maxima to Dyck path
3351
+ Integer partitions -> Permutations
3352
+ Integer partitions -> Integer partitions
3353
+ conjugate
3354
+ Integer partitions -> Dyck paths
3355
+ to Dyck path
3356
+ Dyck paths -> Permutations
3357
+ to non-crossing permutation
3358
+ Dyck paths -> Integer partitions
3359
+ to partition
3360
+ Dyck paths -> Dyck paths
3361
+ reverse
3362
+ """
3363
+ def __init__(self, domain=None, codomain=None):
3364
+ """
3365
+ TESTS::
3366
+
3367
+ sage: from sage.databases.findstat import FindStatMaps
3368
+ sage: M = FindStatMaps() # optional -- internet
3369
+ sage: TestSuite(M).run() # optional -- internet
3370
+ """
3371
+ if domain is None:
3372
+ self._domain = None
3373
+ else:
3374
+ self._domain = FindStatCollection(domain)
3375
+ if codomain is None:
3376
+ self._codomain = None
3377
+ else:
3378
+ self._codomain = FindStatCollection(codomain)
3379
+ self._identifiers = None
3380
+ Parent.__init__(self, category=Sets())
3381
+
3382
+ def _element_constructor_(self, id):
3383
+ """
3384
+ Initialize a FindStat map.
3385
+
3386
+ INPUT:
3387
+
3388
+ - ``id`` -- string containing the FindStat identifier of
3389
+ the map, or an integer giving its id
3390
+
3391
+ EXAMPLES::
3392
+
3393
+ sage: from sage.databases.findstat import FindStatMap
3394
+ sage: FindStatMap(61) # optional -- internet, indirect doctest
3395
+ Mp00061: to increasing tree
3396
+ """
3397
+ if isinstance(id, self.Element):
3398
+ return id
3399
+ if isinstance(id, (int, Integer)):
3400
+ id = FINDSTAT_MAP_PADDED_IDENTIFIER % id
3401
+ elif isinstance(id, FindStatCombinatorialMap):
3402
+ id = id.id_str()
3403
+ if not isinstance(id, str):
3404
+ raise TypeError("the value %s is neither an integer nor a string" % id)
3405
+ else:
3406
+ id = id.strip()
3407
+ if FINDSTAT_MAP_SEPARATOR in id:
3408
+ return FindStatCompoundMap(id)
3409
+ if not re.match(FINDSTAT_MAP_REGEXP, id) or id == FINDSTAT_MAP_PADDED_IDENTIFIER % 0:
3410
+ raise ValueError("the value '%s' is not a valid FindStat map identifier" % id)
3411
+ if id not in _all_maps or _all_maps[id] is None:
3412
+ _all_maps[id] = self.element_class(self, id)
3413
+
3414
+ return _all_maps[id]
3415
+
3416
+ def _repr_(self):
3417
+ """
3418
+ Return the representation of the set of FindStat maps.
3419
+
3420
+ EXAMPLES::
3421
+
3422
+ sage: from sage.databases.findstat import FindStatMaps
3423
+ sage: FindStatMaps() # optional -- internet
3424
+ Set of combinatorial maps used by FindStat
3425
+ """
3426
+ if self._domain is None:
3427
+ text = []
3428
+ else:
3429
+ text = ["domain %s" % self._domain]
3430
+ if self._codomain is not None:
3431
+ text.append("codomain %s" % self._codomain)
3432
+
3433
+ if text:
3434
+ return "Set of combinatorial maps with " + " and ".join(text) + " used by FindStat"
3435
+ return "Set of combinatorial maps used by FindStat"
3436
+
3437
+ def __iter__(self):
3438
+ """
3439
+ Return an iterator over all FindStat maps.
3440
+
3441
+ EXAMPLES::
3442
+
3443
+ sage: from sage.databases.findstat import FindStatMaps
3444
+ sage: next(iter(FindStatMaps(domain=1, codomain=10))) # optional -- internet
3445
+ Mp00061: to increasing tree
3446
+ """
3447
+ if self._identifiers is None:
3448
+ query = []
3449
+ if self._domain is not None:
3450
+ query.append("Domain=%s" % self._domain.id_str())
3451
+ if self._codomain is not None:
3452
+ query.append("Codomain=%s" % self._codomain.id_str())
3453
+ if query:
3454
+ url = FINDSTAT_API_MAPS + "?" + "&".join(query)
3455
+ else:
3456
+ url = FINDSTAT_API_MAPS
3457
+ self._identifiers = _get_json(url)["data"]
3458
+
3459
+ for mp in self._identifiers:
3460
+ yield FindStatMap(mp)
3461
+
3462
+ def _an_element_(self):
3463
+ """
3464
+ Return a FindStat map.
3465
+
3466
+ EXAMPLES::
3467
+
3468
+ sage: findmap(domain="Dyck paths", codomain='Posets').an_element() # optional -- internet
3469
+ Mp00232: parallelogram poset
3470
+ """
3471
+ try:
3472
+ return next(iter(self))
3473
+ except StopIteration:
3474
+ from sage.categories.sets_cat import EmptySetError
3475
+ raise EmptySetError
3476
+
3477
+ Element = FindStatMap
3478
+
3479
+
3480
+ class FindStatMapQuery(FindStatMap):
3481
+ """
3482
+ A class representing a query for FindStat (compound) maps.
3483
+ """
3484
+ def __init__(self, data=None, values_of=None, distribution_of=None,
3485
+ domain=None, codomain=None, known_terms=None, function=None,
3486
+ depth=FINDSTAT_DEFAULT_DEPTH,
3487
+ debug=False):
3488
+ """
3489
+ Initialize a query for FindStat (compound) maps.
3490
+
3491
+ INPUT:
3492
+
3493
+ - ``data`` -- (optional), a list of pairs ``(objects,
3494
+ values)``, where ``objects`` and ``values`` are all lists
3495
+ of the same length, the former are elements in the domain
3496
+ and the latter in the codomain
3497
+
3498
+ - ``known_terms`` -- (optional), a lazy list in the same format
3499
+ as ``data``, which agrees with ``data``, and may be used
3500
+ for submission
3501
+
3502
+ - ``values_of`` -- (optional), anything accepted by
3503
+ :class:`FindStatCompoundStatistic`
3504
+
3505
+ - ``distribution_of`` -- (optional), anything accepted by
3506
+ :class:`FindStatCompoundStatistic`
3507
+
3508
+ - ``domain``, ``codomain`` -- (optional), anything accepted by
3509
+ :class:`FindStatCollection`
3510
+
3511
+ - ``depth`` -- (optional), the number of maps to apply before
3512
+ applying the statistic
3513
+
3514
+ - ``function`` -- (optional), a callable producing the terms
3515
+
3516
+ Only one of ``data``, ``values_of`` and ``distribution_of``
3517
+ may be provided. The parameter ``domain`` must be provided
3518
+ if and only if ``data`` is provided, or ``values_of`` or
3519
+ ``distribution_of`` are given as a function.
3520
+
3521
+ The parameter ``known_terms`` is only allowed, if ``data`` is
3522
+ provided. It defaults to ``data``.
3523
+
3524
+ EXAMPLES::
3525
+
3526
+ sage: from sage.databases.findstat import FindStatMapQuery
3527
+ sage: data = [[[pi], [pi.complement().increasing_tree_shape()]] for pi in Permutations(4)]
3528
+ sage: FindStatMapQuery(domain=1, codomain=10, data=data) # optional -- internet
3529
+ 0: Mp00061oMp00069 (quality [100])
3530
+ """
3531
+ self._first_terms = data
3532
+ if data is not None and known_terms is None:
3533
+ self._known_terms = data
3534
+ else:
3535
+ self._known_terms = known_terms
3536
+ self._values_of = None
3537
+ self._distribution_of = None
3538
+ self._depth = depth
3539
+
3540
+ if data is not None:
3541
+ assert all(param is None for param in [distribution_of, values_of])
3542
+
3543
+ domain = FindStatCollection(domain)
3544
+ codomain = FindStatCollection(codomain)
3545
+ query = {"Domain": domain.id_str(),
3546
+ "Codomain": codomain.id_str(),
3547
+ "Data": _data_to_str(self._first_terms, domain, codomain)}
3548
+
3549
+ elif distribution_of is not None:
3550
+ assert all(param is None for param in [data, known_terms, values_of])
3551
+
3552
+ self._distribution_of = FindStatCompoundMap(distribution_of)
3553
+ domain = self._distribution_of.domain()
3554
+ codomain = self._distribution_of.codomain()
3555
+ query = {"DistributionOf": self._distribution_of.id_str()}
3556
+
3557
+ elif values_of is not None:
3558
+ assert all(param is None for param in [data, known_terms, distribution_of])
3559
+
3560
+ self._values_of = FindStatCompoundMap(values_of)
3561
+ domain = self._values_of.domain()
3562
+ codomain = self._values_of.codomain()
3563
+ query = {"ValuesOf": self._values_of.id_str()}
3564
+
3565
+ else:
3566
+ raise ValueError("incompatible set of parameters: data: %s, distribution_of: %s, values_of: %s" % ((data, distribution_of, values_of)))
3567
+
3568
+ if depth is not None:
3569
+ query["Depth"] = depth
3570
+
3571
+ query["fields"] = "MatchingMap,Quality"
3572
+ if debug:
3573
+ print(query)
3574
+ verbose("querying FindStat %s" % query, caller_name='FindStatMapQuery')
3575
+ response = _post_json(FINDSTAT_API_MAPS, query)
3576
+
3577
+ if debug:
3578
+ print(response)
3579
+ if "data" not in response:
3580
+ raise ValueError(response["error"])
3581
+
3582
+ result = []
3583
+ for match in response["data"]:
3584
+ entry = response["included"]["MatchingMaps"][match]
3585
+ result.append(FindStatMatchingMap(entry["MatchingMap"],
3586
+ entry["Quality"]))
3587
+ self._result = FancyTuple(result)
3588
+
3589
+ FindStatFunction.__init__(self, FINDSTAT_MAP_PADDED_IDENTIFIER % 0,
3590
+ data={"Bibliography": {},
3591
+ "Code": _get_code_from_callable(function),
3592
+ "Description": "",
3593
+ "Domain": domain,
3594
+ "Codomain": codomain,
3595
+ "Name": "a new map from %s to %s" % (domain.name("plural"), codomain.name("plural")),
3596
+ "References": "",
3597
+ "Properties": "",
3598
+ "SageCode": ""},
3599
+ function=function)
3600
+ Element.__init__(self, FindStatMaps()) # this is not completely correct, but it works
3601
+
3602
+ def __repr__(self):
3603
+ """
3604
+ Return a string representation of the query.
3605
+
3606
+ EXAMPLES::
3607
+
3608
+ sage: from sage.databases.findstat import FindStatMapQuery
3609
+ sage: data = [[[pi],[pi.complement().increasing_tree_shape()]] for pi in Permutations(4)]
3610
+ sage: FindStatMapQuery(domain=1, codomain=10, data=data) # optional -- internet
3611
+ 0: Mp00061oMp00069 (quality [100])
3612
+ """
3613
+ if self._result:
3614
+ return repr(self._result)
3615
+ return "%s: %s" % (self.id_str(), self.name())
3616
+
3617
+ def __getitem__(self, i):
3618
+ """
3619
+ Return the `i`-th result in the query.
3620
+
3621
+ EXAMPLES::
3622
+
3623
+ sage: from sage.databases.findstat import FindStatMapQuery
3624
+ sage: data = [[[pi],[pi.complement().increasing_tree_shape()]] for pi in Permutations(4)]
3625
+ sage: r = FindStatMapQuery(domain=1, codomain=10, data=data) # optional -- internet
3626
+ sage: r[0] # optional -- internet
3627
+ Mp00061oMp00069 (quality [100])
3628
+ """
3629
+ return self._result[i]
3630
+
3631
+ def __len__(self):
3632
+ """
3633
+ Return the number of results in the query.
3634
+
3635
+ EXAMPLES::
3636
+
3637
+ sage: r = findmap("Permutations", lambda pi: pi.descents_composition()) # optional -- internet
3638
+ sage: len(r) > 0 # optional -- internet
3639
+ True
3640
+ """
3641
+ return len(self._result)
3642
+
3643
+
3644
+ class FindStatCompoundMap(Element, FindStatCombinatorialMap):
3645
+ def __init__(self, id, domain=None, codomain=None, check=True):
3646
+ """
3647
+ Initialize a compound statistic.
3648
+
3649
+ INPUT:
3650
+
3651
+ - ``id`` -- a padded identifier
3652
+
3653
+ - ``domain`` -- (optional), the domain of the compound map
3654
+
3655
+ - ``codomain`` -- (optional), the codomain of the compound map
3656
+
3657
+ - ``check`` -- whether to check that domains and codomains fit
3658
+
3659
+ If domain and codomain are given and ``check`` is ``False``,
3660
+ they are not fetched from FindStat.
3661
+
3662
+ If ``id`` is the empty string, ``domain`` must be provided,
3663
+ and the identity map on this collection is returned.
3664
+
3665
+ TESTS::
3666
+
3667
+ sage: findmap("Mp00146oMp00127").domain() # optional -- internet
3668
+ Cc0001: Permutations
3669
+
3670
+ sage: findmap("Mp00146oMp00127").codomain() # optional -- internet
3671
+ Cc0012: Perfect matchings
3672
+
3673
+ sage: findmap("Mp00127oMp00146") # optional -- internet
3674
+ Traceback (most recent call last):
3675
+ ...
3676
+ ValueError: the sequence of maps [Mp00146: to tunnel matching, Mp00127: left-to-right-maxima to Dyck path] cannot be composed
3677
+ """
3678
+ if isinstance(id, (int, Integer)):
3679
+ id = FINDSTAT_MAP_PADDED_IDENTIFIER % id
3680
+ elif isinstance(id, FindStatCombinatorialMap):
3681
+ id = id.id_str()
3682
+ if id == "":
3683
+ self._id = "id"
3684
+ self._maps = []
3685
+ self._domain = FindStatCollection(domain)
3686
+ self._codomain = self._domain
3687
+ else:
3688
+ self._maps = [FindStatMap(m) for m in id.split(FINDSTAT_MAP_SEPARATOR)][::-1]
3689
+ if (check
3690
+ and not all(self._maps[i].codomain() == self._maps[i+1].domain()
3691
+ for i in range(len(self._maps)-1))):
3692
+ raise ValueError("the sequence of maps %s cannot be composed" % self._maps)
3693
+ if domain is None:
3694
+ self._domain = self._maps[0].domain()
3695
+ else:
3696
+ self._domain = FindStatCollection(domain)
3697
+ if codomain is None:
3698
+ self._codomain = self._maps[-1].codomain()
3699
+ else:
3700
+ self._codomain = FindStatCollection(codomain)
3701
+ self._id = FINDSTAT_MAP_SEPARATOR.join(m.id_str() for m in reversed(self._maps))
3702
+
3703
+ Element.__init__(self, FindStatMaps()) # this is not completely correct, but it works
3704
+
3705
+ def domain(self):
3706
+ """
3707
+ Return the domain of the compound map.
3708
+
3709
+ EXAMPLES::
3710
+
3711
+ sage: findmap("Mp00099oMp00127").domain() # optional -- internet
3712
+ Cc0001: Permutations
3713
+ """
3714
+ return self._domain
3715
+
3716
+ def codomain(self):
3717
+ """
3718
+ Return the codomain of the compound map.
3719
+
3720
+ EXAMPLES::
3721
+
3722
+ sage: findmap("Mp00099oMp00127").codomain() # optional -- internet
3723
+ Cc0005: Dyck paths
3724
+ """
3725
+ return self._codomain
3726
+
3727
+ def __call__(self, elt):
3728
+ """
3729
+ Apply the compound map to the given element.
3730
+
3731
+ Note that this is only possible if execution of code is
3732
+ enabled, by setting the attribute ``_function`` of each map
3733
+ to ``True``.
3734
+
3735
+ EXAMPLES::
3736
+
3737
+ sage: findmap("Mp00099oMp00127")(Permutation([1,3,2])) # optional -- internet
3738
+ Traceback (most recent call last):
3739
+ ...
3740
+ ValueError: execution of verified code provided by FindStat is not enabled for Mp00127: left-to-right-maxima to Dyck path
3741
+ """
3742
+ for m in self.maps():
3743
+ elt = m(elt)
3744
+ return elt
3745
+
3746
+ def __getitem__(self, i):
3747
+ """
3748
+ Return the `i`-th map in the compound map.
3749
+
3750
+ EXAMPLES::
3751
+
3752
+ sage: findmap("Mp00099oMp00127")[1] # optional -- internet
3753
+ Mp00099: bounce path
3754
+ """
3755
+ return self._maps[i]
3756
+
3757
+ def id_str(self):
3758
+ """
3759
+ Return the padded identifier of the compound map.
3760
+
3761
+ EXAMPLES::
3762
+
3763
+ sage: findmap("Mp00099oMp00127").id_str() # optional -- internet
3764
+ 'Mp00099oMp00127'
3765
+ """
3766
+ return self._id
3767
+
3768
+ def _repr_(self):
3769
+ """
3770
+ Return a string representation of the compound statistic.
3771
+
3772
+ EXAMPLES::
3773
+
3774
+ sage: findmap("Mp00099oMp00127") # optional -- internet
3775
+ Mp00099oMp00127
3776
+ """
3777
+ return self.id_str()
3778
+
3779
+ def maps(self):
3780
+ """
3781
+ Return the maps occurring in the compound map as a list.
3782
+
3783
+ EXAMPLES::
3784
+
3785
+ sage: findmap("Mp00099oMp00127").maps() # optional -- internet
3786
+ [Mp00127: left-to-right-maxima to Dyck path, Mp00099: bounce path]
3787
+ """
3788
+ return self._maps
3789
+
3790
+ def __len__(self):
3791
+ """
3792
+ Return the number of maps occurring in the compound map.
3793
+
3794
+ .. WARNING::
3795
+
3796
+ Do not confuse this with the number of results in a query.
3797
+
3798
+ EXAMPLES::
3799
+
3800
+ sage: len(findmap("Mp00099oMp00127")) # optional -- internet
3801
+ 2
3802
+ """
3803
+ return len(self._maps)
3804
+
3805
+ def browse(self):
3806
+ r"""
3807
+ Open the FindStat web page of the compound map in a browser.
3808
+
3809
+ EXAMPLES::
3810
+
3811
+ sage: findmap(62).browse() # optional -- webbrowser
3812
+ """
3813
+ webbrowser.open(FINDSTAT_URL_MAPS + self.id_str())
3814
+
3815
+ def info(self):
3816
+ """
3817
+ Print a detailed explanation of the compound map.
3818
+
3819
+ EXAMPLES::
3820
+
3821
+ sage: findmap("Mp00099oMp00127").info() # optional -- internet
3822
+ Mp00127: left-to-right-maxima to Dyck path: Permutations -> Dyck paths
3823
+ Mp00099: bounce path: Dyck paths -> Dyck paths
3824
+ """
3825
+ for mp in self:
3826
+ mp.info()
3827
+
3828
+
3829
+ class FindStatMatchingMap(FindStatCompoundMap):
3830
+ def __init__(self, matching_map, quality, domain=None, codomain=None):
3831
+ """
3832
+ Initialize a FindStat map match.
3833
+
3834
+ INPUT:
3835
+
3836
+ - ``matching_map`` -- a compound map identifier
3837
+
3838
+ - ``quality`` -- the quality of the match, as provided by FindStat
3839
+
3840
+ - ``domain`` -- (optional) the domain of the compound map
3841
+
3842
+ - ``codomain`` -- (optional), the codomain of the compound map
3843
+
3844
+ EXAMPLES::
3845
+
3846
+ sage: from sage.databases.findstat import FindStatMatchingMap
3847
+ sage: FindStatMatchingMap("Mp00099oMp00127", [83]) # optional -- internet
3848
+ Mp00099oMp00127 (quality [83])
3849
+ """
3850
+ self._quality = quality
3851
+ # we can trust that matches have fitting domain / codomain sequence
3852
+ FindStatCompoundMap.__init__(self, matching_map, domain=domain, codomain=codomain, check=False)
3853
+
3854
+ def _repr_(self):
3855
+ """
3856
+ Return a string representation of the match.
3857
+
3858
+ EXAMPLES::
3859
+
3860
+ sage: from sage.databases.findstat import FindStatMatchingMap
3861
+ sage: FindStatMatchingMap("Mp00099oMp00127", [83]) # optional -- internet
3862
+ Mp00099oMp00127 (quality [83])
3863
+ """
3864
+ return "%s (quality %s)" % (self.id_str(), self.quality())
3865
+
3866
+ def quality(self):
3867
+ """
3868
+ Return the quality of the match, as provided by FindStat.
3869
+
3870
+ EXAMPLES::
3871
+
3872
+ sage: from sage.databases.findstat import FindStatMatchingMap
3873
+ sage: FindStatMatchingMap("Mp00099oMp00127", [83]).quality() # optional -- internet
3874
+ [83]
3875
+ """
3876
+ return self._quality[:]
3877
+
3878
+ def info(self):
3879
+ """
3880
+ Print a detailed explanation of the match.
3881
+
3882
+ EXAMPLES::
3883
+
3884
+ sage: from sage.databases.findstat import FindStatMatchingMap
3885
+ sage: FindStatMatchingMap("Mp00099oMp00127", [83]).info() # optional -- internet
3886
+ your input matches
3887
+ Mp00127: left-to-right-maxima to Dyck path: Permutations -> Dyck paths
3888
+ Mp00099: bounce path: Dyck paths -> Dyck paths
3889
+ <BLANKLINE>
3890
+ among the values you sent, 83 percent are actually in the database
3891
+ """
3892
+ print("your input matches")
3893
+ super().info()
3894
+ print()
3895
+ print("among the values you sent, %s percent are actually in the database" % self.quality()[0])
3896
+
3897
+
3898
+ ######################################################################
3899
+ # collections
3900
+ ######################################################################
3901
+
3902
+ # helper for generation of CartanTypes
3903
+ def _finite_irreducible_cartan_types_by_rank(n):
3904
+ """
3905
+ Return the Cartan types of rank `n`.
3906
+
3907
+ INPUT:
3908
+
3909
+ - ``n`` -- integer
3910
+
3911
+ OUTPUT: the list of Cartan types of rank `n`
3912
+
3913
+ TESTS::
3914
+
3915
+ sage: from sage.databases.findstat import _finite_irreducible_cartan_types_by_rank
3916
+ sage: _finite_irreducible_cartan_types_by_rank(2)
3917
+ [['A', 2], ['B', 2], ['G', 2]]
3918
+ """
3919
+ cartan_types = [CartanType(['A',n])]
3920
+ if n >= 2:
3921
+ cartan_types += [CartanType(['B',n])]
3922
+ if n >= 3:
3923
+ cartan_types += [CartanType(['C',n])]
3924
+ if n >= 4:
3925
+ cartan_types += [CartanType(['D',n])]
3926
+ if 6 <= n <= 8:
3927
+ cartan_types += [CartanType(['E',n])]
3928
+ if n == 4:
3929
+ cartan_types += [CartanType(['F',n])]
3930
+ if n == 2:
3931
+ cartan_types += [CartanType(['G',n])]
3932
+ return cartan_types
3933
+
3934
+ # helper for generation of PlanePartitions
3935
+
3936
+
3937
+ def _plane_partitions_by_size_aux(n, outer=None):
3938
+ """
3939
+ Iterate over the plane partitions with `n` boxes, as lists.
3940
+
3941
+ INPUT:
3942
+
3943
+ - ``n`` -- integer
3944
+
3945
+ OUTPUT: the plane partitions with `n` boxes as lists
3946
+
3947
+ TESTS::
3948
+
3949
+ sage: from sage.databases.findstat import _plane_partitions_by_size_aux
3950
+ sage: list(_plane_partitions_by_size_aux(3))
3951
+ [[[1], [1], [1]], [[2], [1]], [[1, 1], [1]], [[3]], [[2, 1]], [[1, 1, 1]]]
3952
+ """
3953
+ if n == 0:
3954
+ yield []
3955
+ return
3956
+ if outer is None:
3957
+ outer = [n]*n
3958
+ for k in range(1, n+1):
3959
+ for la in Partitions(k, outer=outer):
3960
+ for pp in _plane_partitions_by_size_aux(n-k, outer=la):
3961
+ pp = [la] + pp
3962
+ yield pp
3963
+
3964
+
3965
+ def _plane_partitions_by_size(n):
3966
+ """
3967
+ Iterate over the plane partitions with `n` boxes.
3968
+
3969
+ .. TODO::
3970
+
3971
+ This can be replaced when :issue:`28244` is merged.
3972
+
3973
+ INPUT:
3974
+
3975
+ - ``n`` -- integer
3976
+
3977
+ OUTPUT: the plane partitions with `n` boxes
3978
+
3979
+ TESTS::
3980
+
3981
+ sage: from sage.databases.findstat import _plane_partitions_by_size
3982
+ sage: list(_plane_partitions_by_size(3))
3983
+ [Plane partition [[1], [1], [1]],
3984
+ Plane partition [[2], [1]],
3985
+ Plane partition [[1, 1], [1]],
3986
+ Plane partition [[3]],
3987
+ Plane partition [[2, 1]],
3988
+ Plane partition [[1, 1, 1]]]
3989
+ """
3990
+ for pp in _plane_partitions_by_size_aux(n):
3991
+ yield PlanePartition(pp)
3992
+
3993
+ # helper for generation of Lattices
3994
+
3995
+
3996
+ def _finite_lattices(n):
3997
+ """
3998
+ Iterate over the lattices with `n` elements.
3999
+
4000
+ INPUT:
4001
+
4002
+ - ``n`` -- integer
4003
+
4004
+ OUTPUT: the lattices with `n` elements
4005
+
4006
+ TESTS::
4007
+
4008
+ sage: from sage.databases.findstat import _finite_lattices
4009
+ sage: sorted((L.cover_relations() for L in _finite_lattices(4)), # needs nauty
4010
+ ....: key=len)
4011
+ [[['bottom', 0], [0, 1], [1, 'top']],
4012
+ [['bottom', 0], ['bottom', 1], [0, 'top'], [1, 'top']]]
4013
+ """
4014
+ if n <= 2:
4015
+ for P in Posets(n):
4016
+ if P.is_lattice():
4017
+ yield LatticePoset(P)
4018
+ else:
4019
+ for P in Posets(n - 2):
4020
+ Q = P.with_bounds()
4021
+ if Q.is_lattice():
4022
+ yield LatticePoset(Q)
4023
+
4024
+
4025
+ class FindStatCollection(Element,
4026
+ metaclass=InheritComparisonClasscallMetaclass):
4027
+ r"""
4028
+ A FindStat collection.
4029
+
4030
+ :class:`FindStatCollection` is a class representing a
4031
+ combinatorial collection available in the FindStat database.
4032
+
4033
+ Its main use is to allow easy specification of the combinatorial
4034
+ collection when using :class:`findstat<FindStat>`. It also
4035
+ provides methods to quickly access its FindStat web page
4036
+ (:meth:`browse`), check whether a particular element is actually
4037
+ in the range considered by FindStat (:meth:`in_range`), etc.
4038
+
4039
+ INPUT:
4040
+
4041
+ One of the following:
4042
+
4043
+ - a string eg. 'Dyck paths' or 'DyckPaths', case-insensitive, or
4044
+
4045
+ - an integer designating the FindStat id of the collection, or
4046
+
4047
+ - a Sage object belonging to a collection, or
4048
+
4049
+ - an iterable producing a Sage object belonging to a collection.
4050
+
4051
+ EXAMPLES::
4052
+
4053
+ sage: from sage.databases.findstat import FindStatCollection
4054
+ sage: FindStatCollection("Dyck paths") # optional -- internet
4055
+ Cc0005: Dyck paths
4056
+
4057
+ sage: FindStatCollection(5) # optional -- internet
4058
+ Cc0005: Dyck paths
4059
+
4060
+ sage: FindStatCollection(DyckWord([1,0,1,0])) # optional -- internet
4061
+ Cc0005: Dyck paths
4062
+
4063
+ sage: FindStatCollection(DyckWords(2)) # optional -- internet
4064
+ a subset of Cc0005: Dyck paths
4065
+
4066
+ sage: FindStatCollection(DyckWords) # optional -- internet
4067
+ Cc0005: Dyck paths
4068
+
4069
+ .. SEEALSO::
4070
+
4071
+ :class:`FindStatCollections`
4072
+ """
4073
+ @staticmethod
4074
+ def __classcall_private__(cls, entry):
4075
+ """
4076
+ Retrieve a collection from the database.
4077
+
4078
+ TESTS::
4079
+
4080
+ sage: from sage.databases.findstat import FindStatCollection
4081
+ sage: FindStatCollection(0) # optional -- internet
4082
+ Traceback (most recent call last):
4083
+ ...
4084
+ ValueError: could not find FindStat collection for 0
4085
+ """
4086
+ return FindStatCollections()(entry)
4087
+
4088
+ def __init__(self, parent, id, data, sageconstructor_overridden):
4089
+ """
4090
+ Initialize the collection.
4091
+
4092
+ This should only be called in
4093
+ :meth:`FindStatCollections()._element_constructor_` via
4094
+ :meth:`FindStatCollections().element_class`.
4095
+
4096
+ INPUT:
4097
+
4098
+ - ``parent`` -- :class:`FindStatCollections`
4099
+
4100
+ - ``id`` -- the (padded) FindStat identifier of the collection
4101
+
4102
+ - ``data`` -- dictionary containing the properties of the
4103
+ collection, such as its name, the corresponding class in
4104
+ sage, and so on.
4105
+
4106
+ - ``sageconstructor_overridden`` -- either ``None`` or an
4107
+ iterable which yields a subset of the elements of the
4108
+ collection.
4109
+
4110
+ TESTS::
4111
+
4112
+ sage: from sage.databases.findstat import FindStatCollection
4113
+ sage: FindStatCollection(5).parent() # optional -- internet
4114
+ Set of combinatorial collections used by FindStat
4115
+ """
4116
+ self._id = id
4117
+ self._data = data
4118
+ self._sageconstructor_overridden = sageconstructor_overridden
4119
+
4120
+ Element.__init__(self, parent)
4121
+
4122
+ def __reduce__(self):
4123
+ """
4124
+ Return a function and its arguments needed to create the
4125
+ collection.
4126
+
4127
+ TESTS::
4128
+
4129
+ sage: from sage.databases.findstat import FindStatCollection
4130
+ sage: c = FindStatCollection("Permutations") # optional -- internet
4131
+ sage: loads(dumps(c)) == c # optional -- internet
4132
+ True
4133
+ """
4134
+ return FindStatCollection, (self.id(),)
4135
+
4136
+ def _richcmp_(self, other, op):
4137
+ """
4138
+ TESTS::
4139
+
4140
+ sage: from sage.databases.findstat import FindStatCollection, FindStatCollections
4141
+ sage: FindStatCollection("Permutations") == FindStatCollection("Permutations") # optional -- internet
4142
+ True
4143
+
4144
+ sage: FindStatCollection("Permutations") == FindStatCollection("Integer Partitions") # optional -- internet
4145
+ False
4146
+
4147
+ sage: FindStatCollection("Permutations") != FindStatCollection("Permutations") # optional -- internet
4148
+ False
4149
+
4150
+ sage: FindStatCollection("Permutations") != FindStatCollection("Integer Partitions") # optional -- internet
4151
+ True
4152
+
4153
+ sage: FindStatCollection("Permutations") == 1 # optional -- internet
4154
+ False
4155
+
4156
+ sage: FindStatCollection("Permutations") != 1 # optional -- internet
4157
+ True
4158
+
4159
+ sage: sorted(c for c in FindStatCollections())[0] # optional -- internet
4160
+ Cc0001: Permutations
4161
+
4162
+ It is not clear what we want here, but equality may be better::
4163
+
4164
+ sage: FindStatCollection(graphs(3)) == FindStatCollection("Graphs") # optional -- internet
4165
+ True
4166
+ """
4167
+ return richcmp(self.id(), other.id(), op)
4168
+
4169
+ def __hash__(self):
4170
+ """
4171
+ Return a hash value for the collection.
4172
+
4173
+ EXAMPLES::
4174
+
4175
+ sage: from sage.databases.findstat import FindStatCollections
4176
+ sage: set(FindStatCollections()) # optional -- internet
4177
+ {Cc0001: Permutations,
4178
+ Cc0002: Integer partitions,
4179
+ ...
4180
+ """
4181
+ return self.id()
4182
+
4183
+ def is_supported(self):
4184
+ """
4185
+ Check whether the collection is fully supported by the interface.
4186
+
4187
+ EXAMPLES::
4188
+
4189
+ sage: from sage.databases.findstat import FindStatCollection
4190
+ sage: FindStatCollection(1).is_supported() # optional -- internet
4191
+ True
4192
+
4193
+ sage: FindStatCollection(24).is_supported() # optional -- internet, random
4194
+ False
4195
+ """
4196
+ return "Code" in self._data
4197
+
4198
+ def elements_on_level(self, level):
4199
+ """
4200
+ Return an iterable over the elements on the given level.
4201
+
4202
+ EXAMPLES::
4203
+
4204
+ sage: from sage.databases.findstat import FindStatCollection
4205
+ sage: FindStatCollection("Perfect Matchings").elements_on_level(4) # optional -- internet
4206
+ Perfect matchings of {1, 2, 3, 4}
4207
+ """
4208
+ return self._data["Code"].elements_on_level(level)
4209
+
4210
+ def element_level(self, element):
4211
+ """
4212
+ Return the level of an element.
4213
+
4214
+ EXAMPLES::
4215
+
4216
+ sage: from sage.databases.findstat import FindStatCollection
4217
+ sage: cc = FindStatCollection("Perfect Matchings") # optional -- internet
4218
+ sage: cc.element_level(PerfectMatching([[1,2],[3,4],[5,6]])) # optional -- internet
4219
+ 6
4220
+ """
4221
+ return self._data["Code"].element_level(element)
4222
+
4223
+ def is_element(self, element):
4224
+ """
4225
+ Return whether the element belongs to the collection.
4226
+
4227
+ If the collection is not yet supported, return whether element is a string.
4228
+
4229
+ EXAMPLES::
4230
+
4231
+ sage: from sage.databases.findstat import FindStatCollection
4232
+ sage: cc = FindStatCollection("Perfect Matchings") # optional -- internet
4233
+ sage: cc.is_element(PerfectMatching([[1,2],[3,4],[5,6]])) # optional -- internet
4234
+ True
4235
+
4236
+ sage: cc.is_element(SetPartition([[1,2],[3,4],[5,6]])) # optional -- internet
4237
+ False
4238
+ """
4239
+ if self.is_supported():
4240
+ return self._data["Code"].is_element(element)
4241
+
4242
+ return isinstance(element, str)
4243
+
4244
+ def levels_with_sizes(self):
4245
+ """
4246
+ Return a dictionary from levels to level sizes.
4247
+
4248
+ EXAMPLES::
4249
+
4250
+ sage: from sage.databases.findstat import FindStatCollection
4251
+ sage: cc = FindStatCollection("Perfect Matchings") # optional -- internet
4252
+ sage: cc.levels_with_sizes() # optional -- internet
4253
+ {2: 1, 4: 3, 6: 15, 8: 105, 10: 945}
4254
+ """
4255
+ return self._data["LevelsWithSizes"]
4256
+
4257
+ def in_range(self, element):
4258
+ r"""
4259
+ Check whether an element of the collection is in FindStat's precomputed range.
4260
+
4261
+ INPUT:
4262
+
4263
+ - ``element`` -- a Sage object that belongs to the collection
4264
+
4265
+ OUTPUT: ``True``, if ``element`` is used by the FindStat search
4266
+ engine, and ``False`` if it is ignored
4267
+
4268
+ EXAMPLES::
4269
+
4270
+ sage: # optional - internet
4271
+ sage: from sage.databases.findstat import FindStatCollection
4272
+ sage: c = FindStatCollection("GelfandTsetlinPatterns")
4273
+ sage: c.in_range(GelfandTsetlinPattern([[2, 1], [1]]))
4274
+ True
4275
+ sage: c.in_range(GelfandTsetlinPattern([[3, 1], [1]]))
4276
+ True
4277
+ sage: c.in_range(GelfandTsetlinPattern([[7, 1], [1]]))
4278
+ False
4279
+
4280
+ TESTS::
4281
+
4282
+ sage: from sage.databases.findstat import FindStatCollections
4283
+ sage: l = FindStatCollections() # optional -- internet
4284
+ sage: long = [14, 20]
4285
+ sage: for c in sorted(l): # optional -- internet
4286
+ ....: if c.id() not in long and c.is_supported():
4287
+ ....: f = c.first_terms(lambda x: 1)
4288
+ ....: print("{} {} {}".format(c, len(list(f)), all(c.in_range(e) for e, _ in f)))
4289
+ Cc0001: Permutations 5913 True
4290
+ Cc0002: Integer partitions 1211 True
4291
+ Cc0005: Dyck paths 2055 True
4292
+ Cc0006: Integer compositions 1023 True
4293
+ Cc0007: Standard tableaux 1115 True
4294
+ Cc0009: Set partitions 1155 True
4295
+ Cc0010: Binary trees 2055 True
4296
+ Cc0012: Perfect matchings 1069 True
4297
+ Cc0013: Cores 100 True
4298
+ Cc0017: Alternating sign matrices 7917 True
4299
+ Cc0018: Gelfand-Tsetlin patterns 1409 True
4300
+ Cc0019: Semistandard tableaux 2374 True
4301
+ Cc0021: Ordered trees 2056 True
4302
+ Cc0022: Finite Cartan types 31 True
4303
+ Cc0023: Parking functions 18248 True
4304
+ Cc0024: Binary words 1022 True
4305
+ Cc0025: Plane partitions 1123 True
4306
+ Cc0026: Decorated permutations 2371 True
4307
+ Cc0027: Signed permutations 4282 True
4308
+ Cc0028: Skew partitions 1250 True
4309
+ Cc0029: Lattices 1378 True
4310
+ Cc0030: Ordered set partitions 5316 True
4311
+ """
4312
+ return self._data["Code"].element_level(element) in self._data["LevelsWithSizes"]
4313
+
4314
+ def first_terms(self, function, level=None):
4315
+ r"""
4316
+ Compute the first few terms of the given function, possibly
4317
+ restricted to a level, as a lazy list.
4318
+
4319
+ INPUT:
4320
+
4321
+ - ``function`` -- a callable
4322
+
4323
+ - ``level`` -- (optional), the level to restrict to
4324
+
4325
+ OUTPUT:
4326
+
4327
+ A lazy list of pairs of the form ``(object, value)``.
4328
+
4329
+ EXAMPLES::
4330
+
4331
+ sage: from sage.databases.findstat import FindStatCollection
4332
+ sage: c = FindStatCollection("GelfandTsetlinPatterns") # optional -- internet
4333
+ sage: c.first_terms(lambda x: 1)[:10].list() # optional -- internet
4334
+ [([[1, 0], [0]], 1),
4335
+ ([[1, 0], [1]], 1),
4336
+ ([[2, 0], [0]], 1),
4337
+ ([[2, 0], [1]], 1),
4338
+ ([[2, 0], [2]], 1),
4339
+ ([[1, 1], [1]], 1),
4340
+ ([[1, 0, 0], [0, 0], [0]], 1),
4341
+ ([[1, 0, 0], [1, 0], [0]], 1),
4342
+ ([[1, 0, 0], [1, 0], [1]], 1),
4343
+ ([[3, 0], [0]], 1)]
4344
+ """
4345
+ if self._sageconstructor_overridden is None:
4346
+ if level is None:
4347
+ g = (x
4348
+ for level in self._data["LevelsWithSizes"]
4349
+ for x in self._data["Code"].elements_on_level(level))
4350
+ else:
4351
+ g = (x for x in self._data["Code"].elements_on_level(level))
4352
+ else:
4353
+ if level is None:
4354
+ g = self._sageconstructor_overridden
4355
+ else:
4356
+ g = (x for x in self._sageconstructor_overridden
4357
+ if self.element_level(x) == level)
4358
+
4359
+ return lazy_list((x, function(x)) for x in g)
4360
+
4361
+ def id(self):
4362
+ r"""
4363
+ Return the FindStat identifier of the collection.
4364
+
4365
+ OUTPUT: the FindStat identifier of the collection as an integer
4366
+
4367
+ EXAMPLES::
4368
+
4369
+ sage: from sage.databases.findstat import FindStatCollection
4370
+ sage: c = FindStatCollection("GelfandTsetlinPatterns") # optional -- internet
4371
+ sage: c.id() # optional -- internet
4372
+ 18
4373
+ """
4374
+ return int(self._id[2:])
4375
+
4376
+ def id_str(self):
4377
+ r"""
4378
+ Return the FindStat identifier of the collection.
4379
+
4380
+ OUTPUT: the FindStat identifier of the collection as a string
4381
+
4382
+ EXAMPLES::
4383
+
4384
+ sage: from sage.databases.findstat import FindStatCollection
4385
+ sage: c = FindStatCollection("GelfandTsetlinPatterns") # optional -- internet
4386
+ sage: c.id_str() # optional -- internet
4387
+ 'Cc0018'
4388
+ """
4389
+ return self._id
4390
+
4391
+ def browse(self):
4392
+ r"""
4393
+ Open the FindStat web page of the collection in a browser.
4394
+
4395
+ EXAMPLES::
4396
+
4397
+ sage: from sage.databases.findstat import FindStatCollection
4398
+ sage: FindStatCollection("Permutations").browse() # optional -- webbrowser
4399
+ """
4400
+ webbrowser.open(FINDSTAT_URL_COLLECTIONS + self._id)
4401
+
4402
+ def to_string(self):
4403
+ r"""
4404
+ Return a function that returns a FindStat representation given an
4405
+ object.
4406
+
4407
+ If the collection is not yet supported, return the identity.
4408
+
4409
+ OUTPUT:
4410
+
4411
+ The function that produces the string representation as
4412
+ needed by the FindStat search webpage.
4413
+
4414
+ EXAMPLES::
4415
+
4416
+ sage: from sage.databases.findstat import FindStatCollection
4417
+ sage: p = Poset((range(3), [[0, 1], [1, 2]])) # optional -- internet
4418
+ sage: c = FindStatCollection("Posets") # optional -- internet
4419
+ sage: c.to_string()(p) # optional -- internet
4420
+ '([(0, 1), (1, 2)], 3)'
4421
+ """
4422
+ if self.is_supported():
4423
+ return self._data["Code"].element_to_string
4424
+ return lambda x: x
4425
+
4426
+ def from_string(self):
4427
+ r"""
4428
+ Return a function that returns the object given a FindStat
4429
+ representation.
4430
+
4431
+ OUTPUT:
4432
+
4433
+ The function that produces the Sage object given its FindStat
4434
+ representation as a string.
4435
+
4436
+ EXAMPLES::
4437
+
4438
+ sage: from sage.databases.findstat import FindStatCollection
4439
+ sage: c = FindStatCollection("Posets") # optional -- internet
4440
+ sage: p = c.from_string()('([(0, 2), (2, 1)], 3)') # optional -- internet
4441
+ sage: p.cover_relations() # optional -- internet
4442
+ [[0, 2], [2, 1]]
4443
+
4444
+ sage: c = FindStatCollection("Binary Words") # optional -- internet
4445
+ sage: w = c.from_string()('010101') # optional -- internet
4446
+ sage: w in c._data["Code"].elements_on_level(6) # optional -- internet
4447
+ True
4448
+ """
4449
+ return self._data["Code"].string_to_element
4450
+
4451
+ def _repr_(self):
4452
+ r"""
4453
+ Return the representation of the FindStat collection.
4454
+
4455
+ OUTPUT: the representation, including the identifier and the name
4456
+
4457
+ EXAMPLES::
4458
+
4459
+ sage: from sage.databases.findstat import FindStatCollection
4460
+ sage: FindStatCollection("Binary trees") # optional -- internet
4461
+ Cc0010: Binary trees
4462
+ """
4463
+ if self._sageconstructor_overridden:
4464
+ return "a subset of %s: %s" % (self.id_str(), self._data["NamePlural"])
4465
+ return "%s: %s" % (self.id_str(), self._data["NamePlural"])
4466
+
4467
+ def name(self, style='singular'):
4468
+ r"""
4469
+ Return the name of the FindStat collection.
4470
+
4471
+ INPUT:
4472
+
4473
+ - ``style`` -- string (default: ``'singular'``); can be
4474
+ ``'singular'``, or ``'plural'``
4475
+
4476
+ OUTPUT: the name of the FindStat collection, in singular or in plural
4477
+
4478
+ EXAMPLES::
4479
+
4480
+ sage: from sage.databases.findstat import FindStatCollection
4481
+ sage: FindStatCollection("Binary trees").name() # optional -- internet
4482
+ 'Binary tree'
4483
+
4484
+ sage: FindStatCollection("Binary trees").name(style='plural') # optional -- internet
4485
+ 'Binary trees'
4486
+ """
4487
+ if style == "singular":
4488
+ return self._data["Name"]
4489
+ if style == "plural":
4490
+ return self._data["NamePlural"]
4491
+ raise ValueError("argument 'style' (=%s) must be 'singular' or 'plural'" % style)
4492
+
4493
+
4494
+ from collections import namedtuple
4495
+ _SupportedFindStatCollection = namedtuple("SupportedFindStatCollection",
4496
+ ["string_to_element",
4497
+ "element_to_string",
4498
+ "elements_on_level", # return all elements on given level
4499
+ "element_level", # return level of a given element
4500
+ "is_element"]) # return whether element is member of this collection
4501
+
4502
+ # this dictionary must be sorted so that subclasses come before
4503
+ # superclasses, eg., "StandardTableaux" before "SemistandardTableaux"
4504
+ _SupportedFindStatCollections = {
4505
+ "Permutations":
4506
+ _SupportedFindStatCollection(lambda x: Permutation(literal_eval(x)),
4507
+ str,
4508
+ Permutations,
4509
+ lambda x: x.size(),
4510
+ lambda x: isinstance(x, Permutation)),
4511
+ "BinaryWords":
4512
+ _SupportedFindStatCollection(lambda x: Word((int(e) for e in str(x)), alphabet=[0,1]),
4513
+ str,
4514
+ lambda x: Words([0,1], length=x),
4515
+ lambda x: x.length(),
4516
+ lambda x: isinstance(x, Word_class)),
4517
+ "AlternatingSignMatrices":
4518
+ _SupportedFindStatCollection(lambda x: AlternatingSignMatrix(literal_eval(x)),
4519
+ lambda x: str(list(map(list, x.to_matrix().rows()))),
4520
+ AlternatingSignMatrices,
4521
+ lambda x: x.to_matrix().nrows(),
4522
+ lambda x: isinstance(x, AlternatingSignMatrix)),
4523
+ "BinaryTrees":
4524
+ _SupportedFindStatCollection(lambda x: BinaryTree(str(x)),
4525
+ str,
4526
+ BinaryTrees,
4527
+ lambda x: x.node_number(),
4528
+ lambda x: isinstance(x, BinaryTree)),
4529
+ "Cores":
4530
+ _SupportedFindStatCollection(lambda x: Core(*literal_eval(x)),
4531
+ lambda X: "( " + X._repr_() + ", " + str(X.k()) + " )",
4532
+ lambda x: Cores(x[1], x[0]),
4533
+ lambda x: (x.length(), x.k()),
4534
+ lambda x: isinstance(x, Core)),
4535
+ "DyckPaths":
4536
+ _SupportedFindStatCollection(lambda x: DyckWord(literal_eval(x)),
4537
+ lambda x: str(list(DyckWord(x))),
4538
+ DyckWords,
4539
+ lambda x: x.semilength(),
4540
+ lambda x: isinstance(x, DyckWord)),
4541
+ "FiniteCartanTypes":
4542
+ _SupportedFindStatCollection(lambda x: CartanType(*literal_eval(str(x))),
4543
+ str,
4544
+ _finite_irreducible_cartan_types_by_rank,
4545
+ lambda x: x.rank(),
4546
+ lambda x: isinstance(x, CartanType_abstract)),
4547
+ "GelfandTsetlinPatterns":
4548
+ _SupportedFindStatCollection(lambda x: GelfandTsetlinPattern(literal_eval(x)),
4549
+ str,
4550
+ lambda x: (P
4551
+ for la in Partitions(x[1], max_length=x[0])
4552
+ for P in GelfandTsetlinPatterns(top_row=la + [0]*(x[0]-len(la)))),
4553
+ lambda x: (len(x[0]), sum(x[0])),
4554
+ lambda x: (x == GelfandTsetlinPatterns
4555
+ or isinstance(x, GelfandTsetlinPattern))),
4556
+ "Graphs":
4557
+ _SupportedFindStatCollection(lambda x: (lambda E, V: Graph([list(range(V)),
4558
+ lambda i,j: (i,j) in E or (j,i) in E],
4559
+ immutable=True))(*literal_eval(x)),
4560
+ lambda X: str((X.edges(labels=False, sort=True), X.num_verts())),
4561
+ lambda x: (g.copy(immutable=True) for g in graphs(x, copy=False)),
4562
+ lambda x: x.num_verts(),
4563
+ lambda x: isinstance(x, Graph)),
4564
+ "IntegerPartitions":
4565
+ _SupportedFindStatCollection(lambda x: Partition(literal_eval(x)),
4566
+ str,
4567
+ Partitions,
4568
+ lambda x: x.size(),
4569
+ lambda x: isinstance(x, Partition)),
4570
+ "IntegerCompositions":
4571
+ _SupportedFindStatCollection(lambda x: Composition(literal_eval(x)),
4572
+ str,
4573
+ Compositions,
4574
+ lambda x: x.size(),
4575
+ lambda x: isinstance(x, Composition)),
4576
+ "OrderedTrees":
4577
+ _SupportedFindStatCollection(lambda x: OrderedTree(literal_eval(x)),
4578
+ str,
4579
+ OrderedTrees,
4580
+ lambda x: x.node_number(),
4581
+ lambda x: isinstance(x, OrderedTree)),
4582
+ "ParkingFunctions":
4583
+ _SupportedFindStatCollection(lambda x: ParkingFunction(literal_eval(x)),
4584
+ str,
4585
+ ParkingFunctions,
4586
+ len,
4587
+ lambda x: isinstance(x, ParkingFunction)),
4588
+ "Lattices":
4589
+ _SupportedFindStatCollection(lambda x: (lambda R, E: LatticePoset((list(range(E)), R)))(*literal_eval(x)),
4590
+ lambda X: str((sorted(X._hasse_diagram.cover_relations()),
4591
+ len(X._hasse_diagram.vertices(sort=False)))),
4592
+ _finite_lattices,
4593
+ lambda x: x.cardinality(),
4594
+ lambda x: isinstance(x, FiniteLatticePoset)),
4595
+ "Posets":
4596
+ _SupportedFindStatCollection(lambda x: (lambda R, E: Poset((list(range(E)), R)))(*literal_eval(x)),
4597
+ lambda X: str((sorted(X._hasse_diagram.cover_relations()),
4598
+ len(X._hasse_diagram.vertices(sort=False)))),
4599
+ Posets,
4600
+ lambda x: x.cardinality(),
4601
+ lambda x: isinstance(x, FinitePoset)),
4602
+ "StandardTableaux":
4603
+ _SupportedFindStatCollection(lambda x: StandardTableau(literal_eval(x)),
4604
+ str,
4605
+ StandardTableaux,
4606
+ lambda x: x.size(),
4607
+ lambda x: isinstance(x, StandardTableau)),
4608
+ "SemistandardTableaux":
4609
+ _SupportedFindStatCollection(lambda x: SemistandardTableau(literal_eval(x)),
4610
+ str,
4611
+ lambda x: (T for T in SemistandardTableaux(size=x[0], max_entry=x[1])
4612
+ if max(T.entries()) == x[1]),
4613
+ lambda x: (x.size(), max(x.entries())),
4614
+ lambda x: isinstance(x, SemistandardTableau)),
4615
+ "PerfectMatchings":
4616
+ _SupportedFindStatCollection(lambda x: PerfectMatching(literal_eval(x)),
4617
+ str,
4618
+ PerfectMatchings,
4619
+ lambda x: x.size(),
4620
+ lambda x: isinstance(x, PerfectMatching)),
4621
+ "SetPartitions":
4622
+ _SupportedFindStatCollection(lambda x: SetPartition(literal_eval(x.replace('{','[').replace('}',']'))),
4623
+ str,
4624
+ SetPartitions,
4625
+ lambda x: x.size(),
4626
+ lambda x: isinstance(x, SetPartition)),
4627
+ "SkewPartitions":
4628
+ _SupportedFindStatCollection(lambda x: SkewPartition(literal_eval(x)),
4629
+ str,
4630
+ SkewPartitions,
4631
+ lambda x: x.size(),
4632
+ lambda x: isinstance(x, SkewPartition)),
4633
+ "SignedPermutations":
4634
+ _SupportedFindStatCollection(lambda x: SignedPermutations(len(literal_eval(x)))(list(literal_eval(x))),
4635
+ str,
4636
+ SignedPermutations,
4637
+ lambda x: len(list(x)),
4638
+ lambda x: isinstance(x, SignedPermutation)),
4639
+ "PlanePartitions":
4640
+ _SupportedFindStatCollection(lambda x: PlanePartition(literal_eval(x)),
4641
+ lambda X: str(list(X)).replace(" ",""),
4642
+ _plane_partitions_by_size,
4643
+ lambda x: sum(sum(la) for la in x),
4644
+ lambda x: isinstance(x, PlanePartition)),
4645
+ "DecoratedPermutations":
4646
+ _SupportedFindStatCollection(lambda x: DecoratedPermutation([v if v > 0 else (i if v == 0 else -i)
4647
+ for i, v in enumerate(literal_eval(x.replace("+","0").replace("-","-1")), 1)]),
4648
+ lambda x: "[" + ",".join((str(v) if abs(v) != i else ("+" if v > 0 else "-")
4649
+ for i, v in enumerate(x, 1))) + "]",
4650
+ DecoratedPermutations,
4651
+ lambda x: x.size(),
4652
+ lambda x: isinstance(x, DecoratedPermutation)),
4653
+ "OrderedSetPartitions":
4654
+ _SupportedFindStatCollection(lambda x: OrderedSetPartition(literal_eval(x.replace('{','[').replace('}',']'))),
4655
+ str,
4656
+ OrderedSetPartitions,
4657
+ lambda x: x.size(),
4658
+ lambda x: isinstance(x, OrderedSetPartition))}
4659
+
4660
+
4661
+ class FindStatCollections(UniqueRepresentation, Parent):
4662
+ r"""
4663
+ The class of FindStat collections.
4664
+
4665
+ The elements of this class are combinatorial collections in
4666
+ FindStat as of January 2020. If a new collection was added to the
4667
+ web service since then, the dictionary ``_SupportedFindStatCollections``
4668
+ in this module has to be updated accordingly.
4669
+
4670
+ EXAMPLES::
4671
+
4672
+ sage: from sage.databases.findstat import FindStatCollections
4673
+ sage: sorted(c for c in FindStatCollections() if c.is_supported()) # optional -- internet
4674
+ [Cc0001: Permutations,
4675
+ Cc0002: Integer partitions,
4676
+ Cc0005: Dyck paths,
4677
+ Cc0006: Integer compositions,
4678
+ Cc0007: Standard tableaux,
4679
+ Cc0009: Set partitions,
4680
+ Cc0010: Binary trees,
4681
+ Cc0012: Perfect matchings,
4682
+ Cc0013: Cores,
4683
+ Cc0014: Posets,
4684
+ Cc0017: Alternating sign matrices,
4685
+ Cc0018: Gelfand-Tsetlin patterns,
4686
+ Cc0019: Semistandard tableaux,
4687
+ Cc0020: Graphs,
4688
+ Cc0021: Ordered trees,
4689
+ Cc0022: Finite Cartan types,
4690
+ Cc0023: Parking functions,
4691
+ Cc0024: Binary words,
4692
+ Cc0025: Plane partitions,
4693
+ Cc0026: Decorated permutations,
4694
+ Cc0027: Signed permutations,
4695
+ Cc0028: Skew partitions,
4696
+ Cc0029: Lattices,
4697
+ Cc0030: Ordered set partitions]
4698
+ """
4699
+ def __init__(self):
4700
+ """
4701
+ Fetch the collections from FindStat.
4702
+
4703
+ TESTS::
4704
+
4705
+ sage: from sage.databases.findstat import FindStatCollections
4706
+ sage: C = FindStatCollections() # optional -- internet
4707
+ sage: TestSuite(C).run() # optional -- internet
4708
+ """
4709
+ fields = "LevelsWithSizes,Name,NamePlural,NameWiki"
4710
+ url = FINDSTAT_API_COLLECTIONS + "?fields=" + fields
4711
+ d = _get_json(url, object_pairs_hook=dict)["included"]["Collections"]
4712
+ for id, data in d.items():
4713
+ data["LevelsWithSizes"] = {literal_eval(level): size
4714
+ for level, size in data["LevelsWithSizes"].items()}
4715
+ if data["NameWiki"] in _SupportedFindStatCollections:
4716
+ data["Code"] = _SupportedFindStatCollections[data["NameWiki"]]
4717
+ else:
4718
+ print("%s provides a new collection:" % FindStat())
4719
+ print(" %s: %s" % (id, data["NamePlural"]))
4720
+ print("To use it with this interface, it has to be added to the dictionary")
4721
+ print(" _SupportedFindStatCollections in src/sage/databases/findstat.py")
4722
+ print("of the SageMath distribution. Please open an issue on github!")
4723
+ # print("Very likely, the following code would work:")
4724
+ # fields = "SageCodeElementToString,SageCodeElementsOnLevel,SageCodeStringToElement"
4725
+ # url = FINDSTAT_API_COLLECTIONS + id + "?fields=" + fields
4726
+ # print(json.load(urlopen(url))["included"]["Collections"][id])
4727
+
4728
+ def position(item):
4729
+ try:
4730
+ return tuple(_SupportedFindStatCollections).index(item[1]["NameWiki"])
4731
+ except ValueError:
4732
+ return len(_SupportedFindStatCollections)
4733
+
4734
+ self._findstat_collections = dict(sorted(d.items(), key=position))
4735
+ Parent.__init__(self, category=Sets())
4736
+
4737
+ def _element_constructor_(self, entry):
4738
+ """
4739
+ Initialize a FindStat collection.
4740
+
4741
+ INPUT:
4742
+
4743
+ See :class:`FindStatCollection`.
4744
+
4745
+ TESTS:
4746
+
4747
+ Create an object and find its collection::
4748
+
4749
+ sage: from sage.databases.findstat import FindStatCollection, FindStatCollections
4750
+ sage: sorted([FindStatCollection(c.first_terms(lambda x: 0)[0][0]) for c in FindStatCollections() if c.is_supported()]) # optional -- internet
4751
+ [Cc0001: Permutations,
4752
+ Cc0002: Integer partitions,
4753
+ Cc0005: Dyck paths,
4754
+ Cc0006: Integer compositions,
4755
+ Cc0007: Standard tableaux,
4756
+ Cc0009: Set partitions,
4757
+ Cc0010: Binary trees,
4758
+ Cc0012: Perfect matchings,
4759
+ Cc0013: Cores,
4760
+ Cc0014: Posets,
4761
+ Cc0017: Alternating sign matrices,
4762
+ Cc0018: Gelfand-Tsetlin patterns,
4763
+ Cc0019: Semistandard tableaux,
4764
+ Cc0020: Graphs,
4765
+ Cc0021: Ordered trees,
4766
+ Cc0022: Finite Cartan types,
4767
+ Cc0023: Parking functions,
4768
+ Cc0024: Binary words,
4769
+ Cc0025: Plane partitions,
4770
+ Cc0026: Decorated permutations,
4771
+ Cc0027: Signed permutations,
4772
+ Cc0028: Skew partitions,
4773
+ Cc0029: Lattices,
4774
+ Cc0030: Ordered set partitions]
4775
+
4776
+ sage: FindStatCollection(Permutation([1,2,3])) # optional -- internet
4777
+ Cc0001: Permutations
4778
+
4779
+ sage: FindStatCollection(Permutations(3)) # optional -- internet
4780
+ a subset of Cc0001: Permutations
4781
+
4782
+ sage: FindStatCollection(PerfectMatching([[1,2]])) # optional -- internet
4783
+ Cc0012: Perfect matchings
4784
+
4785
+ sage: FindStatCollection(PerfectMatchings(4)) # optional -- internet
4786
+ a subset of Cc0012: Perfect matchings
4787
+
4788
+ sage: FindStatCollection(SetPartition([[1,2]])) # optional -- internet
4789
+ Cc0009: Set partitions
4790
+
4791
+ sage: FindStatCollection(CartanType("A3")) # optional -- internet
4792
+ Cc0022: Finite Cartan types
4793
+
4794
+ sage: cc = FindStatCollection(graphs(3)); cc # optional -- internet
4795
+ a subset of Cc0020: Graphs
4796
+ sage: cc.first_terms(lambda x: x.edges(labels=False, sort=True)).list() # optional -- internet
4797
+ [(Graph on 3 vertices, []),
4798
+ (Graph on 3 vertices, [(0, 2)]),
4799
+ (Graph on 3 vertices, [(0, 2), (1, 2)]),
4800
+ (Graph on 3 vertices, [(0, 1), (0, 2), (1, 2)])]
4801
+
4802
+ sage: len(cc.first_terms(lambda x: x.edges(labels=False, sort=False)).list()) # optional -- internet
4803
+ 4
4804
+
4805
+ Check that we can override the automatic detection::
4806
+
4807
+ sage: l = [(T, len(T)) for n in range(1,5) for T in StandardTableaux(n)] # optional -- internet
4808
+ sage: qu = findstat("Semistandardtableaux", l, depth=1) # optional -- internet
4809
+ sage: qu[0] # optional -- internet
4810
+ St000010oMp00077 (quality [100, 100])
4811
+ """
4812
+ if isinstance(entry, self.Element):
4813
+ return entry
4814
+
4815
+ if isinstance(entry, str):
4816
+ # find by name in self._findstat_collections (ignoring case and spaces)
4817
+ def normalize(e):
4818
+ return "".join(e.split()).upper()
4819
+
4820
+ for id, data in self._findstat_collections.items():
4821
+ if normalize(entry) in (normalize(id),
4822
+ normalize(data["NameWiki"]),
4823
+ normalize(data["NamePlural"]),
4824
+ normalize(data["Name"])):
4825
+ return self.element_class(self, id, data, None)
4826
+
4827
+ elif isinstance(entry, (int, Integer)):
4828
+ # find by id in _findstat_collections
4829
+ for id, data in self._findstat_collections.items():
4830
+ if entry == int(id[2:]):
4831
+ return self.element_class(self, id, data, None)
4832
+
4833
+ else:
4834
+ # find collection given an object or a constructor
4835
+
4836
+ # it would be good to first check whether entry is an
4837
+ # element rather than a type - unfortunately, we cannot
4838
+ # test with isinstance(_, SageObject), since this is True
4839
+ # for CartanType.
4840
+
4841
+ # first check whether the class fits:
4842
+ for id, data in self._findstat_collections.items():
4843
+ if ("Code" in data
4844
+ and (data["Code"].is_element(entry)
4845
+ # elements_on_level is rarely equal to entry
4846
+ # (it may be a function), but it is
4847
+ # convenient for some types
4848
+ or data["Code"].elements_on_level == entry)):
4849
+ return self.element_class(self, id, data, None)
4850
+
4851
+ # check whether entry is iterable (it's not a string!)
4852
+ # producing a subset of objects
4853
+
4854
+ # WARNING: SkewPartition is iterable and produces two
4855
+ # elements of Partition
4856
+
4857
+ # WARNING: we have to remember all elements of the
4858
+ # generator, because it is used again, for example in
4859
+ # self.first_terms
4860
+ try:
4861
+ entries = lazy_list(entry)
4862
+ obj = entries[0]
4863
+ except (TypeError, IndexError):
4864
+ pass
4865
+ else:
4866
+ for id, data in self._findstat_collections.items():
4867
+ if "Code" in data and data["Code"].is_element(obj):
4868
+ return self.element_class(self, id, data, entries)
4869
+
4870
+ raise ValueError("could not find FindStat collection for %s" % str(entry))
4871
+
4872
+ def _repr_(self):
4873
+ """
4874
+ Return the representation of the set of FindStat collections.
4875
+
4876
+ EXAMPLES::
4877
+
4878
+ sage: from sage.databases.findstat import FindStatCollections
4879
+ sage: FindStatCollections() # optional -- internet
4880
+ Set of combinatorial collections used by FindStat
4881
+ """
4882
+ return "Set of combinatorial collections used by FindStat"
4883
+
4884
+ def __iter__(self):
4885
+ """
4886
+ Return an iterator over all FindStat collections.
4887
+
4888
+ EXAMPLES::
4889
+
4890
+ sage: from sage.databases.findstat import FindStatCollections
4891
+ sage: sorted(FindStatCollections())[0] # optional -- internet
4892
+ Cc0001: Permutations
4893
+ """
4894
+ for c in self._findstat_collections:
4895
+ yield FindStatCollection(c)
4896
+
4897
+ Element = FindStatCollection