passagemath-combinat 10.6.42__cp314-cp314-musllinux_1_2_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. passagemath_combinat/__init__.py +3 -0
  2. passagemath_combinat-10.6.42.dist-info/METADATA +160 -0
  3. passagemath_combinat-10.6.42.dist-info/RECORD +400 -0
  4. passagemath_combinat-10.6.42.dist-info/WHEEL +5 -0
  5. passagemath_combinat-10.6.42.dist-info/top_level.txt +3 -0
  6. passagemath_combinat.libs/libgmp-0e7fc84e.so.10.5.0 +0 -0
  7. passagemath_combinat.libs/libsymmetrica-81fe8739.so.3.0.0 +0 -0
  8. sage/algebras/affine_nil_temperley_lieb.py +263 -0
  9. sage/algebras/all.py +24 -0
  10. sage/algebras/all__sagemath_combinat.py +35 -0
  11. sage/algebras/askey_wilson.py +935 -0
  12. sage/algebras/associated_graded.py +345 -0
  13. sage/algebras/cellular_basis.py +350 -0
  14. sage/algebras/cluster_algebra.py +2766 -0
  15. sage/algebras/down_up_algebra.py +860 -0
  16. sage/algebras/free_algebra.py +1698 -0
  17. sage/algebras/free_algebra_element.py +345 -0
  18. sage/algebras/free_algebra_quotient.py +405 -0
  19. sage/algebras/free_algebra_quotient_element.py +295 -0
  20. sage/algebras/free_zinbiel_algebra.py +885 -0
  21. sage/algebras/hall_algebra.py +783 -0
  22. sage/algebras/hecke_algebras/all.py +4 -0
  23. sage/algebras/hecke_algebras/ariki_koike_algebra.py +1796 -0
  24. sage/algebras/hecke_algebras/ariki_koike_specht_modules.py +475 -0
  25. sage/algebras/hecke_algebras/cubic_hecke_algebra.py +3520 -0
  26. sage/algebras/hecke_algebras/cubic_hecke_base_ring.py +1473 -0
  27. sage/algebras/hecke_algebras/cubic_hecke_matrix_rep.py +1079 -0
  28. sage/algebras/iwahori_hecke_algebra.py +3095 -0
  29. sage/algebras/jordan_algebra.py +1773 -0
  30. sage/algebras/lie_conformal_algebras/abelian_lie_conformal_algebra.py +113 -0
  31. sage/algebras/lie_conformal_algebras/affine_lie_conformal_algebra.py +156 -0
  32. sage/algebras/lie_conformal_algebras/all.py +18 -0
  33. sage/algebras/lie_conformal_algebras/bosonic_ghosts_lie_conformal_algebra.py +134 -0
  34. sage/algebras/lie_conformal_algebras/examples.py +43 -0
  35. sage/algebras/lie_conformal_algebras/fermionic_ghosts_lie_conformal_algebra.py +131 -0
  36. sage/algebras/lie_conformal_algebras/finitely_freely_generated_lca.py +139 -0
  37. sage/algebras/lie_conformal_algebras/free_bosons_lie_conformal_algebra.py +174 -0
  38. sage/algebras/lie_conformal_algebras/free_fermions_lie_conformal_algebra.py +167 -0
  39. sage/algebras/lie_conformal_algebras/freely_generated_lie_conformal_algebra.py +107 -0
  40. sage/algebras/lie_conformal_algebras/graded_lie_conformal_algebra.py +135 -0
  41. sage/algebras/lie_conformal_algebras/lie_conformal_algebra.py +353 -0
  42. sage/algebras/lie_conformal_algebras/lie_conformal_algebra_element.py +236 -0
  43. sage/algebras/lie_conformal_algebras/lie_conformal_algebra_with_basis.py +78 -0
  44. sage/algebras/lie_conformal_algebras/lie_conformal_algebra_with_structure_coefs.py +328 -0
  45. sage/algebras/lie_conformal_algebras/n2_lie_conformal_algebra.py +117 -0
  46. sage/algebras/lie_conformal_algebras/neveu_schwarz_lie_conformal_algebra.py +86 -0
  47. sage/algebras/lie_conformal_algebras/virasoro_lie_conformal_algebra.py +82 -0
  48. sage/algebras/lie_conformal_algebras/weyl_lie_conformal_algebra.py +205 -0
  49. sage/algebras/nil_coxeter_algebra.py +191 -0
  50. sage/algebras/q_commuting_polynomials.py +673 -0
  51. sage/algebras/q_system.py +608 -0
  52. sage/algebras/quantum_clifford.py +959 -0
  53. sage/algebras/quantum_groups/ace_quantum_onsager.py +693 -0
  54. sage/algebras/quantum_groups/all.py +9 -0
  55. sage/algebras/quantum_groups/fock_space.py +2219 -0
  56. sage/algebras/quantum_groups/q_numbers.py +207 -0
  57. sage/algebras/quantum_groups/quantum_group_gap.py +2695 -0
  58. sage/algebras/quantum_groups/representations.py +591 -0
  59. sage/algebras/quantum_matrix_coordinate_algebra.py +1006 -0
  60. sage/algebras/quantum_oscillator.py +623 -0
  61. sage/algebras/quaternion_algebra.py +20 -0
  62. sage/algebras/quaternion_algebra_element.py +55 -0
  63. sage/algebras/rational_cherednik_algebra.py +525 -0
  64. sage/algebras/schur_algebra.py +670 -0
  65. sage/algebras/shuffle_algebra.py +1011 -0
  66. sage/algebras/splitting_algebra.py +779 -0
  67. sage/algebras/tensor_algebra.py +709 -0
  68. sage/algebras/yangian.py +1082 -0
  69. sage/algebras/yokonuma_hecke_algebra.py +1018 -0
  70. sage/all__sagemath_combinat.py +35 -0
  71. sage/combinat/SJT.py +255 -0
  72. sage/combinat/affine_permutation.py +2405 -0
  73. sage/combinat/algebraic_combinatorics.py +55 -0
  74. sage/combinat/all.py +53 -0
  75. sage/combinat/all__sagemath_combinat.py +195 -0
  76. sage/combinat/alternating_sign_matrix.py +2063 -0
  77. sage/combinat/baxter_permutations.py +346 -0
  78. sage/combinat/bijectionist.py +3220 -0
  79. sage/combinat/binary_recurrence_sequences.py +1180 -0
  80. sage/combinat/blob_algebra.py +685 -0
  81. sage/combinat/catalog_partitions.py +27 -0
  82. sage/combinat/chas/all.py +23 -0
  83. sage/combinat/chas/fsym.py +1180 -0
  84. sage/combinat/chas/wqsym.py +2601 -0
  85. sage/combinat/cluster_complex.py +326 -0
  86. sage/combinat/colored_permutations.py +2039 -0
  87. sage/combinat/colored_permutations_representations.py +964 -0
  88. sage/combinat/composition_signed.py +142 -0
  89. sage/combinat/composition_tableau.py +855 -0
  90. sage/combinat/constellation.py +1729 -0
  91. sage/combinat/core.py +751 -0
  92. sage/combinat/counting.py +12 -0
  93. sage/combinat/crystals/affine.py +742 -0
  94. sage/combinat/crystals/affine_factorization.py +518 -0
  95. sage/combinat/crystals/affinization.py +331 -0
  96. sage/combinat/crystals/alcove_path.py +2013 -0
  97. sage/combinat/crystals/all.py +22 -0
  98. sage/combinat/crystals/bkk_crystals.py +141 -0
  99. sage/combinat/crystals/catalog.py +115 -0
  100. sage/combinat/crystals/catalog_elementary_crystals.py +18 -0
  101. sage/combinat/crystals/catalog_infinity_crystals.py +33 -0
  102. sage/combinat/crystals/catalog_kirillov_reshetikhin.py +18 -0
  103. sage/combinat/crystals/crystals.py +257 -0
  104. sage/combinat/crystals/direct_sum.py +260 -0
  105. sage/combinat/crystals/elementary_crystals.py +1251 -0
  106. sage/combinat/crystals/fast_crystals.py +441 -0
  107. sage/combinat/crystals/fully_commutative_stable_grothendieck.py +1205 -0
  108. sage/combinat/crystals/generalized_young_walls.py +1076 -0
  109. sage/combinat/crystals/highest_weight_crystals.py +436 -0
  110. sage/combinat/crystals/induced_structure.py +695 -0
  111. sage/combinat/crystals/infinity_crystals.py +730 -0
  112. sage/combinat/crystals/kac_modules.py +863 -0
  113. sage/combinat/crystals/kirillov_reshetikhin.py +4196 -0
  114. sage/combinat/crystals/kyoto_path_model.py +497 -0
  115. sage/combinat/crystals/letters.cpython-314-x86_64-linux-musl.so +0 -0
  116. sage/combinat/crystals/letters.pxd +79 -0
  117. sage/combinat/crystals/letters.pyx +3056 -0
  118. sage/combinat/crystals/littelmann_path.py +1518 -0
  119. sage/combinat/crystals/monomial_crystals.py +1262 -0
  120. sage/combinat/crystals/multisegments.py +462 -0
  121. sage/combinat/crystals/mv_polytopes.py +467 -0
  122. sage/combinat/crystals/pbw_crystal.py +511 -0
  123. sage/combinat/crystals/pbw_datum.cpython-314-x86_64-linux-musl.so +0 -0
  124. sage/combinat/crystals/pbw_datum.pxd +4 -0
  125. sage/combinat/crystals/pbw_datum.pyx +487 -0
  126. sage/combinat/crystals/polyhedral_realization.py +372 -0
  127. sage/combinat/crystals/spins.cpython-314-x86_64-linux-musl.so +0 -0
  128. sage/combinat/crystals/spins.pxd +21 -0
  129. sage/combinat/crystals/spins.pyx +756 -0
  130. sage/combinat/crystals/star_crystal.py +290 -0
  131. sage/combinat/crystals/subcrystal.py +464 -0
  132. sage/combinat/crystals/tensor_product.py +1177 -0
  133. sage/combinat/crystals/tensor_product_element.cpython-314-x86_64-linux-musl.so +0 -0
  134. sage/combinat/crystals/tensor_product_element.pxd +35 -0
  135. sage/combinat/crystals/tensor_product_element.pyx +1870 -0
  136. sage/combinat/crystals/virtual_crystal.py +420 -0
  137. sage/combinat/cyclic_sieving_phenomenon.py +204 -0
  138. sage/combinat/debruijn_sequence.cpython-314-x86_64-linux-musl.so +0 -0
  139. sage/combinat/debruijn_sequence.pyx +355 -0
  140. sage/combinat/decorated_permutation.py +270 -0
  141. sage/combinat/degree_sequences.cpython-314-x86_64-linux-musl.so +0 -0
  142. sage/combinat/degree_sequences.pyx +588 -0
  143. sage/combinat/derangements.py +527 -0
  144. sage/combinat/descent_algebra.py +1008 -0
  145. sage/combinat/diagram.py +1551 -0
  146. sage/combinat/diagram_algebras.py +5886 -0
  147. sage/combinat/dyck_word.py +4349 -0
  148. sage/combinat/e_one_star.py +1623 -0
  149. sage/combinat/enumerated_sets.py +123 -0
  150. sage/combinat/expnums.cpython-314-x86_64-linux-musl.so +0 -0
  151. sage/combinat/expnums.pyx +148 -0
  152. sage/combinat/fast_vector_partitions.cpython-314-x86_64-linux-musl.so +0 -0
  153. sage/combinat/fast_vector_partitions.pyx +346 -0
  154. sage/combinat/fqsym.py +1977 -0
  155. sage/combinat/free_dendriform_algebra.py +954 -0
  156. sage/combinat/free_prelie_algebra.py +1141 -0
  157. sage/combinat/fully_commutative_elements.py +1077 -0
  158. sage/combinat/fully_packed_loop.py +1523 -0
  159. sage/combinat/gelfand_tsetlin_patterns.py +1409 -0
  160. sage/combinat/gray_codes.py +311 -0
  161. sage/combinat/grossman_larson_algebras.py +667 -0
  162. sage/combinat/growth.py +4352 -0
  163. sage/combinat/hall_polynomial.py +188 -0
  164. sage/combinat/hillman_grassl.py +866 -0
  165. sage/combinat/integer_matrices.py +329 -0
  166. sage/combinat/integer_vectors_mod_permgroup.py +1238 -0
  167. sage/combinat/k_tableau.py +4564 -0
  168. sage/combinat/kazhdan_lusztig.py +215 -0
  169. sage/combinat/key_polynomial.py +885 -0
  170. sage/combinat/knutson_tao_puzzles.py +2286 -0
  171. sage/combinat/lr_tableau.py +311 -0
  172. sage/combinat/matrices/all.py +24 -0
  173. sage/combinat/matrices/hadamard_matrix.py +3790 -0
  174. sage/combinat/matrices/latin.py +2912 -0
  175. sage/combinat/misc.py +401 -0
  176. sage/combinat/multiset_partition_into_sets_ordered.py +3541 -0
  177. sage/combinat/ncsf_qsym/all.py +21 -0
  178. sage/combinat/ncsf_qsym/combinatorics.py +317 -0
  179. sage/combinat/ncsf_qsym/generic_basis_code.py +1427 -0
  180. sage/combinat/ncsf_qsym/ncsf.py +5637 -0
  181. sage/combinat/ncsf_qsym/qsym.py +4053 -0
  182. sage/combinat/ncsf_qsym/tutorial.py +447 -0
  183. sage/combinat/ncsym/all.py +21 -0
  184. sage/combinat/ncsym/bases.py +855 -0
  185. sage/combinat/ncsym/dual.py +593 -0
  186. sage/combinat/ncsym/ncsym.py +2076 -0
  187. sage/combinat/necklace.py +551 -0
  188. sage/combinat/non_decreasing_parking_function.py +634 -0
  189. sage/combinat/nu_dyck_word.py +1474 -0
  190. sage/combinat/output.py +861 -0
  191. sage/combinat/parallelogram_polyomino.py +4326 -0
  192. sage/combinat/parking_functions.py +1602 -0
  193. sage/combinat/partition_algebra.py +1998 -0
  194. sage/combinat/partition_kleshchev.py +1982 -0
  195. sage/combinat/partition_shifting_algebras.py +584 -0
  196. sage/combinat/partition_tuple.py +3114 -0
  197. sage/combinat/path_tableaux/all.py +13 -0
  198. sage/combinat/path_tableaux/catalog.py +29 -0
  199. sage/combinat/path_tableaux/dyck_path.py +380 -0
  200. sage/combinat/path_tableaux/frieze.py +476 -0
  201. sage/combinat/path_tableaux/path_tableau.py +728 -0
  202. sage/combinat/path_tableaux/semistandard.py +510 -0
  203. sage/combinat/perfect_matching.py +779 -0
  204. sage/combinat/plane_partition.py +3300 -0
  205. sage/combinat/q_bernoulli.cpython-314-x86_64-linux-musl.so +0 -0
  206. sage/combinat/q_bernoulli.pyx +128 -0
  207. sage/combinat/quickref.py +81 -0
  208. sage/combinat/recognizable_series.py +2051 -0
  209. sage/combinat/regular_sequence.py +4316 -0
  210. sage/combinat/regular_sequence_bounded.py +543 -0
  211. sage/combinat/restricted_growth.py +81 -0
  212. sage/combinat/ribbon.py +20 -0
  213. sage/combinat/ribbon_shaped_tableau.py +489 -0
  214. sage/combinat/ribbon_tableau.py +1180 -0
  215. sage/combinat/rigged_configurations/all.py +46 -0
  216. sage/combinat/rigged_configurations/bij_abstract_class.py +548 -0
  217. sage/combinat/rigged_configurations/bij_infinity.py +370 -0
  218. sage/combinat/rigged_configurations/bij_type_A.py +163 -0
  219. sage/combinat/rigged_configurations/bij_type_A2_dual.py +338 -0
  220. sage/combinat/rigged_configurations/bij_type_A2_even.py +218 -0
  221. sage/combinat/rigged_configurations/bij_type_A2_odd.py +199 -0
  222. sage/combinat/rigged_configurations/bij_type_B.py +900 -0
  223. sage/combinat/rigged_configurations/bij_type_C.py +267 -0
  224. sage/combinat/rigged_configurations/bij_type_D.py +771 -0
  225. sage/combinat/rigged_configurations/bij_type_D_tri.py +392 -0
  226. sage/combinat/rigged_configurations/bij_type_D_twisted.py +576 -0
  227. sage/combinat/rigged_configurations/bij_type_E67.py +402 -0
  228. sage/combinat/rigged_configurations/bijection.py +143 -0
  229. sage/combinat/rigged_configurations/kleber_tree.py +1475 -0
  230. sage/combinat/rigged_configurations/kr_tableaux.py +1898 -0
  231. sage/combinat/rigged_configurations/rc_crystal.py +461 -0
  232. sage/combinat/rigged_configurations/rc_infinity.py +540 -0
  233. sage/combinat/rigged_configurations/rigged_configuration_element.py +2403 -0
  234. sage/combinat/rigged_configurations/rigged_configurations.py +1918 -0
  235. sage/combinat/rigged_configurations/rigged_partition.cpython-314-x86_64-linux-musl.so +0 -0
  236. sage/combinat/rigged_configurations/rigged_partition.pxd +15 -0
  237. sage/combinat/rigged_configurations/rigged_partition.pyx +680 -0
  238. sage/combinat/rigged_configurations/tensor_product_kr_tableaux.py +499 -0
  239. sage/combinat/rigged_configurations/tensor_product_kr_tableaux_element.py +428 -0
  240. sage/combinat/rsk.py +3438 -0
  241. sage/combinat/schubert_polynomial.py +508 -0
  242. sage/combinat/set_partition.py +3318 -0
  243. sage/combinat/set_partition_iterator.cpython-314-x86_64-linux-musl.so +0 -0
  244. sage/combinat/set_partition_iterator.pyx +136 -0
  245. sage/combinat/set_partition_ordered.py +1590 -0
  246. sage/combinat/sf/abreu_nigro.py +346 -0
  247. sage/combinat/sf/all.py +52 -0
  248. sage/combinat/sf/character.py +576 -0
  249. sage/combinat/sf/classical.py +319 -0
  250. sage/combinat/sf/dual.py +996 -0
  251. sage/combinat/sf/elementary.py +549 -0
  252. sage/combinat/sf/hall_littlewood.py +1028 -0
  253. sage/combinat/sf/hecke.py +336 -0
  254. sage/combinat/sf/homogeneous.py +464 -0
  255. sage/combinat/sf/jack.py +1428 -0
  256. sage/combinat/sf/k_dual.py +1458 -0
  257. sage/combinat/sf/kfpoly.py +447 -0
  258. sage/combinat/sf/llt.py +789 -0
  259. sage/combinat/sf/macdonald.py +2019 -0
  260. sage/combinat/sf/monomial.py +525 -0
  261. sage/combinat/sf/multiplicative.py +113 -0
  262. sage/combinat/sf/new_kschur.py +1786 -0
  263. sage/combinat/sf/ns_macdonald.py +964 -0
  264. sage/combinat/sf/orthogonal.py +246 -0
  265. sage/combinat/sf/orthotriang.py +355 -0
  266. sage/combinat/sf/powersum.py +963 -0
  267. sage/combinat/sf/schur.py +880 -0
  268. sage/combinat/sf/sf.py +1653 -0
  269. sage/combinat/sf/sfa.py +7053 -0
  270. sage/combinat/sf/symplectic.py +253 -0
  271. sage/combinat/sf/witt.py +721 -0
  272. sage/combinat/shifted_primed_tableau.py +2735 -0
  273. sage/combinat/shuffle.py +830 -0
  274. sage/combinat/sidon_sets.py +146 -0
  275. sage/combinat/similarity_class_type.py +1721 -0
  276. sage/combinat/sine_gordon.py +618 -0
  277. sage/combinat/six_vertex_model.py +784 -0
  278. sage/combinat/skew_partition.py +2053 -0
  279. sage/combinat/skew_tableau.py +2989 -0
  280. sage/combinat/sloane_functions.py +8935 -0
  281. sage/combinat/specht_module.py +1403 -0
  282. sage/combinat/species/all.py +48 -0
  283. sage/combinat/species/characteristic_species.py +321 -0
  284. sage/combinat/species/composition_species.py +273 -0
  285. sage/combinat/species/cycle_species.py +284 -0
  286. sage/combinat/species/empty_species.py +155 -0
  287. sage/combinat/species/functorial_composition_species.py +148 -0
  288. sage/combinat/species/generating_series.py +673 -0
  289. sage/combinat/species/library.py +148 -0
  290. sage/combinat/species/linear_order_species.py +169 -0
  291. sage/combinat/species/misc.py +83 -0
  292. sage/combinat/species/partition_species.py +290 -0
  293. sage/combinat/species/permutation_species.py +268 -0
  294. sage/combinat/species/product_species.py +423 -0
  295. sage/combinat/species/recursive_species.py +476 -0
  296. sage/combinat/species/set_species.py +192 -0
  297. sage/combinat/species/species.py +820 -0
  298. sage/combinat/species/structure.py +539 -0
  299. sage/combinat/species/subset_species.py +243 -0
  300. sage/combinat/species/sum_species.py +225 -0
  301. sage/combinat/subword.py +564 -0
  302. sage/combinat/subword_complex.py +2122 -0
  303. sage/combinat/subword_complex_c.cpython-314-x86_64-linux-musl.so +0 -0
  304. sage/combinat/subword_complex_c.pyx +119 -0
  305. sage/combinat/super_tableau.py +821 -0
  306. sage/combinat/superpartition.py +1154 -0
  307. sage/combinat/symmetric_group_algebra.py +3774 -0
  308. sage/combinat/symmetric_group_representations.py +1830 -0
  309. sage/combinat/t_sequences.py +877 -0
  310. sage/combinat/tableau.py +9506 -0
  311. sage/combinat/tableau_residues.py +860 -0
  312. sage/combinat/tableau_tuple.py +5353 -0
  313. sage/combinat/tiling.py +2432 -0
  314. sage/combinat/triangles_FHM.py +777 -0
  315. sage/combinat/tutorial.py +1857 -0
  316. sage/combinat/vector_partition.py +337 -0
  317. sage/combinat/words/abstract_word.py +1722 -0
  318. sage/combinat/words/all.py +59 -0
  319. sage/combinat/words/alphabet.py +268 -0
  320. sage/combinat/words/finite_word.py +7201 -0
  321. sage/combinat/words/infinite_word.py +113 -0
  322. sage/combinat/words/lyndon_word.py +652 -0
  323. sage/combinat/words/morphic.py +351 -0
  324. sage/combinat/words/morphism.py +3878 -0
  325. sage/combinat/words/paths.py +2932 -0
  326. sage/combinat/words/shuffle_product.py +278 -0
  327. sage/combinat/words/suffix_trees.py +1873 -0
  328. sage/combinat/words/word.py +769 -0
  329. sage/combinat/words/word_char.cpython-314-x86_64-linux-musl.so +0 -0
  330. sage/combinat/words/word_char.pyx +847 -0
  331. sage/combinat/words/word_datatypes.cpython-314-x86_64-linux-musl.so +0 -0
  332. sage/combinat/words/word_datatypes.pxd +4 -0
  333. sage/combinat/words/word_datatypes.pyx +1067 -0
  334. sage/combinat/words/word_generators.py +2026 -0
  335. sage/combinat/words/word_infinite_datatypes.py +1218 -0
  336. sage/combinat/words/word_options.py +99 -0
  337. sage/combinat/words/words.py +2396 -0
  338. sage/data_structures/all__sagemath_combinat.py +1 -0
  339. sage/databases/all__sagemath_combinat.py +13 -0
  340. sage/databases/findstat.py +4897 -0
  341. sage/databases/oeis.py +2058 -0
  342. sage/databases/sloane.py +393 -0
  343. sage/dynamics/all__sagemath_combinat.py +14 -0
  344. sage/dynamics/cellular_automata/all.py +7 -0
  345. sage/dynamics/cellular_automata/catalog.py +34 -0
  346. sage/dynamics/cellular_automata/elementary.py +612 -0
  347. sage/dynamics/cellular_automata/glca.py +477 -0
  348. sage/dynamics/cellular_automata/solitons.py +1463 -0
  349. sage/dynamics/finite_dynamical_system.py +1249 -0
  350. sage/dynamics/finite_dynamical_system_catalog.py +382 -0
  351. sage/games/all.py +7 -0
  352. sage/games/hexad.py +704 -0
  353. sage/games/quantumino.py +591 -0
  354. sage/games/sudoku.py +889 -0
  355. sage/games/sudoku_backtrack.cpython-314-x86_64-linux-musl.so +0 -0
  356. sage/games/sudoku_backtrack.pyx +189 -0
  357. sage/groups/all__sagemath_combinat.py +1 -0
  358. sage/groups/indexed_free_group.py +489 -0
  359. sage/libs/all__sagemath_combinat.py +6 -0
  360. sage/libs/lrcalc/__init__.py +1 -0
  361. sage/libs/lrcalc/lrcalc.py +525 -0
  362. sage/libs/symmetrica/__init__.py +7 -0
  363. sage/libs/symmetrica/all.py +101 -0
  364. sage/libs/symmetrica/kostka.pxi +168 -0
  365. sage/libs/symmetrica/part.pxi +193 -0
  366. sage/libs/symmetrica/plet.pxi +42 -0
  367. sage/libs/symmetrica/sab.pxi +196 -0
  368. sage/libs/symmetrica/sb.pxi +332 -0
  369. sage/libs/symmetrica/sc.pxi +192 -0
  370. sage/libs/symmetrica/schur.pxi +956 -0
  371. sage/libs/symmetrica/symmetrica.cpython-314-x86_64-linux-musl.so +0 -0
  372. sage/libs/symmetrica/symmetrica.pxi +1172 -0
  373. sage/libs/symmetrica/symmetrica.pyx +39 -0
  374. sage/monoids/all.py +13 -0
  375. sage/monoids/automatic_semigroup.py +1054 -0
  376. sage/monoids/free_abelian_monoid.py +315 -0
  377. sage/monoids/free_abelian_monoid_element.cpython-314-x86_64-linux-musl.so +0 -0
  378. sage/monoids/free_abelian_monoid_element.pxd +16 -0
  379. sage/monoids/free_abelian_monoid_element.pyx +397 -0
  380. sage/monoids/free_monoid.py +335 -0
  381. sage/monoids/free_monoid_element.py +431 -0
  382. sage/monoids/hecke_monoid.py +65 -0
  383. sage/monoids/string_monoid.py +817 -0
  384. sage/monoids/string_monoid_element.py +547 -0
  385. sage/monoids/string_ops.py +143 -0
  386. sage/monoids/trace_monoid.py +972 -0
  387. sage/rings/all__sagemath_combinat.py +2 -0
  388. sage/sat/all.py +4 -0
  389. sage/sat/boolean_polynomials.py +405 -0
  390. sage/sat/converters/__init__.py +6 -0
  391. sage/sat/converters/anf2cnf.py +14 -0
  392. sage/sat/converters/polybori.py +611 -0
  393. sage/sat/solvers/__init__.py +5 -0
  394. sage/sat/solvers/cryptominisat.py +287 -0
  395. sage/sat/solvers/dimacs.py +783 -0
  396. sage/sat/solvers/picosat.py +228 -0
  397. sage/sat/solvers/sat_lp.py +156 -0
  398. sage/sat/solvers/satsolver.cpython-314-x86_64-linux-musl.so +0 -0
  399. sage/sat/solvers/satsolver.pxd +3 -0
  400. sage/sat/solvers/satsolver.pyx +405 -0
@@ -0,0 +1,2432 @@
1
+ # sage_setup: distribution = sagemath-combinat
2
+ # sage.doctest: needs sage.combinat sage.modules
3
+ r"""
4
+ Tiling solver
5
+
6
+ Tiling a `n`-dimensional polyomino with n-dimensional polyominoes.
7
+
8
+ This module defines two classes:
9
+
10
+ - :class:`sage.combinat.tiling.Polyomino` class, to represent polyominoes
11
+ in arbitrary dimension. The goal of this class is to return all the
12
+ rotated, reflected and/or translated copies of a polyomino that are
13
+ contained in a certain box.
14
+
15
+ - :class:`sage.combinat.tiling.TilingSolver` class, to solve the problem of
16
+ tiling a `n`-dimensional polyomino with a set of `n`-dimensional
17
+ polyominoes. One can specify if rotations and reflections are allowed or
18
+ not and if pieces can be reused or not. This class convert the tiling
19
+ data into rows of a matrix that are passed to the DLX solver. It also
20
+ allows to compute the number of solutions.
21
+
22
+ This uses dancing links code which is in Sage. Dancing links were
23
+ originally introduced by Donald Knuth in 2000 [Knuth1]_. Knuth used dancing
24
+ links to solve tilings of a region by 2d pentaminoes. Here we extend the
25
+ method to any dimension.
26
+
27
+ In particular, the :mod:`sage.games.quantumino` module is based on
28
+ the Tiling Solver and allows to solve the 3d Quantumino puzzle.
29
+
30
+ AUTHOR:
31
+
32
+ - Sébastien Labbé, June 2011, initial version
33
+ - Sébastien Labbé, July 2015, count solutions up to rotations
34
+ - Sébastien Labbé, April 2017, tiling a polyomino, not only a rectangular box
35
+
36
+ EXAMPLES:
37
+
38
+ 2d Easy Example
39
+ ---------------
40
+
41
+ Here is a 2d example. Let us try to fill the `3 \times 2` rectangle with a
42
+ `1 \times 2` rectangle and a `2 \times 2` square. Obviously, there are two
43
+ solutions::
44
+
45
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
46
+ sage: p = Polyomino([(0,0), (0,1)])
47
+ sage: q = Polyomino([(0,0), (0,1), (1,0), (1,1)])
48
+ sage: T = TilingSolver([p,q], box=[3,2])
49
+ sage: it = T.solve()
50
+ sage: next(it)
51
+ [Polyomino: [(0, 0), (0, 1), (1, 0), (1, 1)], Color: gray,
52
+ Polyomino: [(2, 0), (2, 1)], Color: gray]
53
+ sage: next(it)
54
+ [Polyomino: [(1, 0), (1, 1), (2, 0), (2, 1)], Color: gray,
55
+ Polyomino: [(0, 0), (0, 1)], Color: gray]
56
+ sage: next(it)
57
+ Traceback (most recent call last):
58
+ ...
59
+ StopIteration
60
+ sage: T.number_of_solutions()
61
+ 2
62
+
63
+ Scott's pentamino problem
64
+ -------------------------
65
+
66
+ As mentioned in the introduction of [Knuth1]_, Scott's pentamino problem
67
+ consists in tiling a chessboard leaving the center four squares vacant with
68
+ the 12 distinct pentaminoes.
69
+
70
+ The 12 pentaminoes::
71
+
72
+ sage: from sage.combinat.tiling import Polyomino
73
+ sage: I = Polyomino([(0,0),(1,0),(2,0),(3,0),(4,0)], color='brown')
74
+ sage: N = Polyomino([(1,0),(1,1),(1,2),(0,2),(0,3)], color='yellow')
75
+ sage: L = Polyomino([(0,0),(1,0),(0,1),(0,2),(0,3)], color='magenta')
76
+ sage: U = Polyomino([(0,0),(1,0),(0,1),(0,2),(1,2)], color='violet')
77
+ sage: X = Polyomino([(1,0),(0,1),(1,1),(1,2),(2,1)], color='pink')
78
+ sage: W = Polyomino([(2,0),(2,1),(1,1),(1,2),(0,2)], color='green')
79
+ sage: P = Polyomino([(1,0),(2,0),(0,1),(1,1),(2,1)], color='orange')
80
+ sage: F = Polyomino([(1,0),(1,1),(0,1),(2,1),(2,2)], color='gray')
81
+ sage: Z = Polyomino([(0,0),(1,0),(1,1),(1,2),(2,2)], color='yellow')
82
+ sage: T = Polyomino([(0,0),(0,1),(1,1),(2,1),(0,2)], color='red')
83
+ sage: Y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color='green')
84
+ sage: V = Polyomino([(0,0),(0,1),(0,2),(1,0),(2,0)], color='blue')
85
+
86
+ A `8 \times 8` chessboard leaving the center four squares vacant::
87
+
88
+ sage: import itertools
89
+ sage: s = set(itertools.product(range(8), repeat=2))
90
+ sage: s.difference_update([(3,3), (3,4), (4,3), (4,4)])
91
+ sage: chessboard = Polyomino(s)
92
+ sage: len(chessboard)
93
+ 60
94
+
95
+ This problem is represented by a matrix made of 1568 rows and 72 columns.
96
+ It has 65 different solutions up to isometries::
97
+
98
+ sage: from sage.combinat.tiling import TilingSolver
99
+ sage: T = TilingSolver([I,N,L,U,X,W,P,F,Z,T,Y,V], box=chessboard, reflection=True)
100
+ sage: T
101
+ Tiling solver of 12 pieces into a box of size 60
102
+ Rotation allowed: True
103
+ Reflection allowed: True
104
+ Reusing pieces allowed: False
105
+ sage: len(T.rows()) # long time
106
+ 1568
107
+ sage: T.number_of_solutions() # long time
108
+ 520
109
+ sage: 520 / 8
110
+ 65
111
+
112
+ Showing one solution::
113
+
114
+ sage: solution = next(T.solve()) # long time
115
+ sage: G = sum([piece.show2d() for piece in solution], Graphics()) # long time, needs sage.plot
116
+ sage: G.show(aspect_ratio=1, axes=False) # long time, needs sage.plot
117
+
118
+ 1d Easy Example
119
+ ---------------
120
+
121
+ Here is an easy one dimensional example where we try to tile a stick of
122
+ length 6 with three sticks of length 1, 2 and 3. There are six solutions::
123
+
124
+ sage: p = Polyomino([[0]])
125
+ sage: q = Polyomino([[0],[1]])
126
+ sage: r = Polyomino([[0],[1],[2]])
127
+ sage: T = TilingSolver([p,q,r], box=[6])
128
+ sage: len(T.rows())
129
+ 15
130
+ sage: it = T.solve()
131
+ sage: next(it)
132
+ [Polyomino: [(0)], Color: gray,
133
+ Polyomino: [(1), (2)], Color: gray,
134
+ Polyomino: [(3), (4), (5)], Color: gray]
135
+ sage: next(it)
136
+ [Polyomino: [(0)], Color: gray,
137
+ Polyomino: [(1), (2), (3)], Color: gray,
138
+ Polyomino: [(4), (5)], Color: gray]
139
+ sage: T.number_of_solutions()
140
+ 6
141
+
142
+ 2d Puzzle allowing reflections
143
+ ------------------------------
144
+
145
+ The following is a puzzle owned by Florent Hivert::
146
+
147
+ sage: from sage.combinat.tiling import Polyomino, TilingSolver
148
+ sage: L = []
149
+ sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,1),(1,2),(1,3)], 'yellow'))
150
+ sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,1),(1,2)], "black"))
151
+ sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,1),(1,3)], "gray"))
152
+ sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,3)],"cyan"))
153
+ sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,0),(1,1)],"red"))
154
+ sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,1),(1,2)],"blue"))
155
+ sage: L.append(Polyomino([(0,0),(0,1),(0,2),(0,3),(1,1),(1,3)],"green"))
156
+ sage: L.append(Polyomino([(0,1),(0,2),(0,3),(1,0),(1,1),(1,3)],"magenta"))
157
+ sage: L.append(Polyomino([(0,1),(0,2),(0,3),(1,0),(1,1),(1,2)],"orange"))
158
+ sage: L.append(Polyomino([(0,0),(0,1),(0,2),(1,0),(1,1),(1,2)],"pink"))
159
+
160
+ By default, rotations are allowed and reflections are not. In this case,
161
+ there are no solutions for tiling a `8 \times 8` rectangular box::
162
+
163
+ sage: T = TilingSolver(L, box=(8,8))
164
+ sage: T.number_of_solutions() # long time (2.5s)
165
+ 0
166
+
167
+ If reflections are allowed, there are solutions. Solve the puzzle and show
168
+ one solution::
169
+
170
+ sage: T = TilingSolver(L, box=(8,8), reflection=True)
171
+ sage: solution = next(T.solve()) # long time (7s)
172
+ sage: G = sum([piece.show2d() for piece in solution], Graphics()) # long time (<1s), needs sage.plot
173
+ sage: G.show(aspect_ratio=1, axes=False) # long time (2s), needs sage.plot
174
+
175
+ Compute the number of solutions::
176
+
177
+ sage: T.number_of_solutions() # long time (2.6s)
178
+ 328
179
+
180
+ Create a animation of all the solutions::
181
+
182
+ sage: a = T.animate() # not tested
183
+ sage: a # not tested
184
+ Animation with 328 frames
185
+
186
+ 3d Puzzle
187
+ ---------
188
+
189
+ The same thing done in 3d *without* allowing reflections this time::
190
+
191
+ sage: from sage.combinat.tiling import Polyomino, TilingSolver
192
+ sage: L = []
193
+ sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,2,0),(1,3,0)]))
194
+ sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,2,0)]))
195
+ sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,3,0)]))
196
+ sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,3,0)]))
197
+ sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0)]))
198
+ sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,1,0),(1,2,0)]))
199
+ sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(0,3,0),(1,1,0),(1,3,0)]))
200
+ sage: L.append(Polyomino([(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,3,0)]))
201
+ sage: L.append(Polyomino([(0,1,0),(0,2,0),(0,3,0),(1,0,0),(1,1,0),(1,2,0)]))
202
+ sage: L.append(Polyomino([(0,0,0),(0,1,0),(0,2,0),(1,0,0),(1,1,0),(1,2,0)]))
203
+
204
+ Solve the puzzle and show one solution::
205
+
206
+ sage: T = TilingSolver(L, box=(8,8,1))
207
+ sage: solution = next(T.solve()) # long time (8s)
208
+ sage: G = sum([p.show3d(size=0.85) for p in solution], Graphics()) # long time (<1s), needs sage.plot
209
+ sage: G.show(aspect_ratio=1, viewer='tachyon') # long time (2s), needs sage.plot
210
+
211
+ Let us compute the number of solutions::
212
+
213
+ sage: T.number_of_solutions() # long time (3s)
214
+ 328
215
+
216
+ Donald Knuth example : the Y pentamino
217
+ --------------------------------------
218
+
219
+ Donald Knuth [Knuth1]_ considered the problem of packing 45 Y pentaminoes into a
220
+ `15 \times 15` square::
221
+
222
+ sage: from sage.combinat.tiling import Polyomino, TilingSolver
223
+ sage: y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)])
224
+ sage: T = TilingSolver([y], box=(5,10), reusable=True, reflection=True)
225
+ sage: T.number_of_solutions()
226
+ 10
227
+ sage: solution = next(T.solve())
228
+ sage: G = sum([p.show2d() for p in solution], Graphics()) # needs sage.plot
229
+ sage: G.show(aspect_ratio=1) # long time (2s), needs sage.plot
230
+
231
+ ::
232
+
233
+ sage: T = TilingSolver([y], box=(15,15), reusable=True, reflection=True)
234
+ sage: T.number_of_solutions() # not tested
235
+ 1696
236
+
237
+ Up to the symmetries of the square, there are 212 distinct solutions::
238
+
239
+ sage: 1696 // 8
240
+ 212
241
+
242
+ Animation of Donald Knuth's dancing links
243
+ -----------------------------------------
244
+
245
+ Animation of the solutions::
246
+
247
+ sage: from sage.combinat.tiling import Polyomino, TilingSolver
248
+ sage: Y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color='yellow')
249
+ sage: T = TilingSolver([Y], box=(15,15), reusable=True, reflection=True)
250
+ sage: a = T.animate(stop=40); a # long time, optional - imagemagick, needs sage.plot
251
+ Animation with 40 frames
252
+
253
+ Incremental animation of the solutions (one piece is removed/added at a time)::
254
+
255
+ sage: a = T.animate('incremental', stop=40); a # long time, optional - imagemagick, needs sage.plot
256
+ Animation with 40 frames
257
+ sage: a.show(delay=50, iterations=1) # long time, optional - imagemagick, needs sage.plot
258
+
259
+ 5d Easy Example
260
+ ---------------
261
+
262
+ Here is a 5d example. Let us try to fill the `2 \times 2 \times 2 \times 2
263
+ \times 2` rectangle with reusable `1 \times 1 \times 1 \times 1 \times 1`
264
+ rectangles. Obviously, there is one solution::
265
+
266
+ sage: from sage.combinat.tiling import Polyomino, TilingSolver
267
+ sage: p = Polyomino([(0,0,0,0,0)])
268
+ sage: T = TilingSolver([p], box=(2,2,2,2,2), reusable=True)
269
+ sage: rows = T.rows() # long time (3s)
270
+ sage: rows # long time (fast)
271
+ [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12],
272
+ [13], [14], [15], [16], [17], [18], [19], [20], [21], [22], [23], [24],
273
+ [25], [26], [27], [28], [29], [30], [31]]
274
+ sage: T.number_of_solutions() # long time (fast)
275
+ 1
276
+
277
+ REFERENCES:
278
+
279
+ .. [Knuth1] Knuth, Donald (2000). "Dancing links". :arxiv:`cs/0011047`.
280
+ """
281
+ # ****************************************************************************
282
+ # Copyright (C) 2011-2015 Sébastien Labbé <slabqc@gmail.com>
283
+ #
284
+ # Distributed under the terms of the GNU General Public License (GPL)
285
+ # as published by the Free Software Foundation; either version 2 of
286
+ # the License, or (at your option) any later version.
287
+ # https://www.gnu.org/licenses/
288
+ # ****************************************************************************
289
+
290
+ import itertools
291
+ from sage.structure.sage_object import SageObject
292
+ from sage.modules.free_module_element import vector
293
+ from sage.misc.cachefunc import cached_method, cached_function
294
+
295
+
296
+ #######################################
297
+ # n-cube isometry group transformations
298
+ #######################################
299
+
300
+
301
+ def ncube_isometry_group(n, orientation_preserving=True):
302
+ r"""
303
+ Return the isometry group of the `n`-cube as a list of matrices.
304
+
305
+ INPUT:
306
+
307
+ - ``n`` -- positive integer, dimension of the space
308
+ - ``orientation_preserving`` -- boolean (default: ``True``);
309
+ whether the orientation is preserved
310
+
311
+ OUTPUT: list of matrices
312
+
313
+ EXAMPLES::
314
+
315
+ sage: from sage.combinat.tiling import ncube_isometry_group
316
+ sage: sorted(ncube_isometry_group(2))
317
+ [
318
+ [-1 0] [ 0 -1] [ 0 1] [1 0]
319
+ [ 0 -1], [ 1 0], [-1 0], [0 1]
320
+ ]
321
+ sage: sorted(ncube_isometry_group(2, orientation_preserving=False))
322
+ [
323
+ [-1 0] [-1 0] [ 0 -1] [ 0 -1] [ 0 1] [0 1] [ 1 0] [1 0]
324
+ [ 0 -1], [ 0 1], [-1 0], [ 1 0], [-1 0], [1 0], [ 0 -1], [0 1]
325
+ ]
326
+
327
+ There are 24 orientation preserving isometries of the 3-cube::
328
+
329
+ sage: sorted(ncube_isometry_group(3))
330
+ [
331
+ [-1 0 0] [-1 0 0] [-1 0 0] [-1 0 0] [ 0 -1 0] [ 0 -1 0]
332
+ [ 0 -1 0] [ 0 0 -1] [ 0 0 1] [ 0 1 0] [-1 0 0] [ 0 0 -1]
333
+ [ 0 0 1], [ 0 -1 0], [ 0 1 0], [ 0 0 -1], [ 0 0 -1], [ 1 0 0],
334
+ <BLANKLINE>
335
+ [ 0 -1 0] [ 0 -1 0] [ 0 0 -1] [ 0 0 -1] [ 0 0 -1] [ 0 0 -1]
336
+ [ 0 0 1] [ 1 0 0] [-1 0 0] [ 0 -1 0] [ 0 1 0] [ 1 0 0]
337
+ [-1 0 0], [ 0 0 1], [ 0 1 0], [-1 0 0], [ 1 0 0], [ 0 -1 0],
338
+ <BLANKLINE>
339
+ [ 0 0 1] [ 0 0 1] [ 0 0 1] [0 0 1] [ 0 1 0] [ 0 1 0]
340
+ [-1 0 0] [ 0 -1 0] [ 0 1 0] [1 0 0] [-1 0 0] [ 0 0 -1]
341
+ [ 0 -1 0], [ 1 0 0], [-1 0 0], [0 1 0], [ 0 0 1], [-1 0 0],
342
+ <BLANKLINE>
343
+ [0 1 0] [ 0 1 0] [ 1 0 0] [ 1 0 0] [ 1 0 0] [1 0 0]
344
+ [0 0 1] [ 1 0 0] [ 0 -1 0] [ 0 0 -1] [ 0 0 1] [0 1 0]
345
+ [1 0 0], [ 0 0 -1], [ 0 0 -1], [ 0 1 0], [ 0 -1 0], [0 0 1]
346
+ ]
347
+
348
+ TESTS::
349
+
350
+ sage: ncube_isometry_group(1)
351
+ [[1]]
352
+ sage: ncube_isometry_group(0)
353
+ Traceback (most recent call last):
354
+ ...
355
+ ValueError: ['B', 0] is not a valid Cartan type
356
+ """
357
+ from sage.combinat.root_system.weyl_group import WeylGroup
358
+ L = [w.matrix() for w in WeylGroup(['B', n])]
359
+ if orientation_preserving:
360
+ return [m for m in L if m.det() == 1]
361
+ else:
362
+ return L
363
+
364
+
365
+ @cached_function
366
+ def ncube_isometry_group_cosets(n, orientation_preserving=True):
367
+ r"""
368
+ Return the quotient of the isometry group of the `n`-cube by the
369
+ the isometry group of the rectangular parallelepiped.
370
+
371
+ INPUT:
372
+
373
+ - ``n`` -- positive integer, dimension of the space
374
+ - ``orientation_preserving`` -- boolean (default: ``True``);
375
+ whether the orientation is preserved
376
+
377
+ OUTPUT: list of cosets, each coset being a sorted list of matrices
378
+
379
+ EXAMPLES::
380
+
381
+ sage: from sage.combinat.tiling import ncube_isometry_group_cosets
382
+ sage: sorted(ncube_isometry_group_cosets(2))
383
+ [[
384
+ [-1 0] [1 0]
385
+ [ 0 -1], [0 1]
386
+ ], [
387
+ [ 0 -1] [ 0 1]
388
+ [ 1 0], [-1 0]
389
+ ]]
390
+ sage: sorted(ncube_isometry_group_cosets(2, False))
391
+ [[
392
+ [-1 0] [-1 0] [ 1 0] [1 0]
393
+ [ 0 -1], [ 0 1], [ 0 -1], [0 1]
394
+ ], [
395
+ [ 0 -1] [ 0 -1] [ 0 1] [0 1]
396
+ [-1 0], [ 1 0], [-1 0], [1 0]
397
+ ]]
398
+
399
+ ::
400
+
401
+ sage: sorted(ncube_isometry_group_cosets(3))
402
+ [[
403
+ [-1 0 0] [-1 0 0] [ 1 0 0] [1 0 0]
404
+ [ 0 -1 0] [ 0 1 0] [ 0 -1 0] [0 1 0]
405
+ [ 0 0 1], [ 0 0 -1], [ 0 0 -1], [0 0 1]
406
+ ], [
407
+ [-1 0 0] [-1 0 0] [ 1 0 0] [ 1 0 0]
408
+ [ 0 0 -1] [ 0 0 1] [ 0 0 -1] [ 0 0 1]
409
+ [ 0 -1 0], [ 0 1 0], [ 0 1 0], [ 0 -1 0]
410
+ ], [
411
+ [ 0 -1 0] [ 0 -1 0] [ 0 1 0] [ 0 1 0]
412
+ [-1 0 0] [ 1 0 0] [-1 0 0] [ 1 0 0]
413
+ [ 0 0 -1], [ 0 0 1], [ 0 0 1], [ 0 0 -1]
414
+ ], [
415
+ [ 0 -1 0] [ 0 -1 0] [ 0 1 0] [0 1 0]
416
+ [ 0 0 -1] [ 0 0 1] [ 0 0 -1] [0 0 1]
417
+ [ 1 0 0], [-1 0 0], [-1 0 0], [1 0 0]
418
+ ], [
419
+ [ 0 0 -1] [ 0 0 -1] [ 0 0 1] [0 0 1]
420
+ [-1 0 0] [ 1 0 0] [-1 0 0] [1 0 0]
421
+ [ 0 1 0], [ 0 -1 0], [ 0 -1 0], [0 1 0]
422
+ ], [
423
+ [ 0 0 -1] [ 0 0 -1] [ 0 0 1] [ 0 0 1]
424
+ [ 0 -1 0] [ 0 1 0] [ 0 -1 0] [ 0 1 0]
425
+ [-1 0 0], [ 1 0 0], [ 1 0 0], [-1 0 0]
426
+ ]]
427
+
428
+ TESTS::
429
+
430
+ sage: cosets = ncube_isometry_group_cosets(3, False)
431
+ sage: len(cosets)
432
+ 6
433
+ sage: [len(c) for c in cosets]
434
+ [8, 8, 8, 8, 8, 8]
435
+ sage: type(cosets[0][0])
436
+ <class 'sage.matrix.matrix_rational_dense.Matrix_rational_dense'>
437
+ """
438
+ from sage.misc.misc_c import prod
439
+ from sage.matrix.constructor import diagonal_matrix
440
+ G = ncube_isometry_group(n, orientation_preserving)
441
+
442
+ # Construct the subgroup H of G of diagonal matrices
443
+ it = itertools.product((1, -1), repeat=n)
444
+ if orientation_preserving:
445
+ H = [diagonal_matrix(L) for L in it if prod(L) == 1]
446
+ else:
447
+ H = [diagonal_matrix(L) for L in it]
448
+
449
+ G_todo = set(G)
450
+ # Make sure that H is a subset of G
451
+ for h in H:
452
+ h.set_immutable()
453
+ assert all(h in G_todo for h in H), "H must be a subset of G"
454
+
455
+ # Construct the cosets
456
+ cosets = []
457
+ for g in G:
458
+ if g not in G_todo:
459
+ continue
460
+ left_coset = sorted(h*g for h in H)
461
+ right_coset = sorted(g*h for h in H)
462
+ assert left_coset == right_coset, "H must be a normal subgroup of G"
463
+ for c in left_coset:
464
+ c.set_immutable()
465
+ G_todo.difference_update(left_coset)
466
+ cosets.append(left_coset)
467
+ return cosets
468
+
469
+ ##############################
470
+ # Class Polyomino
471
+ ##############################
472
+
473
+
474
+ class Polyomino(SageObject):
475
+ r"""
476
+ A polyomino in `\ZZ^d`.
477
+
478
+ The polyomino is the union of the unit square (or cube, or n-cube)
479
+ centered at those coordinates. Such an object should be connected, but
480
+ the code does not make this assumption.
481
+
482
+ INPUT:
483
+
484
+ - ``coords`` -- iterable of integer coordinates in `\ZZ^d`
485
+ - ``color`` -- string (default: ``'gray'``); color for display
486
+ - ``dimension`` -- integer (default: ``None``); dimension of the space,
487
+ if ``None``, it is guessed from the ``coords`` if ``coords`` is non
488
+ empty
489
+
490
+ EXAMPLES::
491
+
492
+ sage: from sage.combinat.tiling import Polyomino
493
+ sage: Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
494
+ Polyomino: [(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 1, 1)], Color: blue
495
+ """
496
+
497
+ def __init__(self, coords, color='gray', dimension=None):
498
+ r"""
499
+ Constructor.
500
+
501
+ See :mod:`Polyomino` for full documentation.
502
+
503
+ EXAMPLES::
504
+
505
+ sage: from sage.combinat.tiling import Polyomino
506
+ sage: Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
507
+ Polyomino: [(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 1, 1)], Color: blue
508
+
509
+ ::
510
+
511
+ sage: Polyomino([(0,0), (1,0), (2,0)])
512
+ Polyomino: [(0, 0), (1, 0), (2, 0)], Color: gray
513
+
514
+ TESTS::
515
+
516
+ sage: Polyomino([], dimension=2)
517
+ Polyomino: [], Color: gray
518
+ """
519
+ from sage.modules.free_module import FreeModule
520
+ from sage.rings.integer_ring import ZZ
521
+
522
+ if not isinstance(color, str):
523
+ raise TypeError("color = ({!r}) must be a string".format(color))
524
+ self._color = color
525
+
526
+ if not isinstance(coords, (tuple,list)):
527
+ coords = list(coords)
528
+
529
+ if dimension is None:
530
+ if coords:
531
+ self._dimension = ZZ(len(coords[0]))
532
+ else:
533
+ raise ValueError("dimension(={}) must be provided for"
534
+ " the empty polyomino".format(dimension))
535
+ else:
536
+ self._dimension = dimension
537
+ self._free_module = FreeModule(ZZ, self._dimension)
538
+
539
+ self._blocs = coords
540
+ self._blocs = [self._free_module(bloc) for bloc in self._blocs]
541
+ for b in self._blocs:
542
+ b.set_immutable()
543
+ self._blocs = frozenset(self._blocs)
544
+
545
+ def _repr_(self):
546
+ r"""
547
+ String representation.
548
+
549
+ EXAMPLES::
550
+
551
+ sage: from sage.combinat.tiling import Polyomino
552
+ sage: Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='red')
553
+ Polyomino: [(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 1, 1)], Color: red
554
+ """
555
+ s = "Polyomino: %s, " % sorted(self._blocs)
556
+ s += "Color: %s" % self._color
557
+ return s
558
+
559
+ def color(self, color=None):
560
+ r"""
561
+ Return or change the color of the polyomino.
562
+
563
+ INPUT:
564
+
565
+ - ``color`` -- string, RBG tuple or ``None`` (default: ``None``),
566
+ if ``None``, it returns the current color
567
+
568
+ EXAMPLES::
569
+
570
+ sage: from sage.combinat.tiling import Polyomino
571
+ sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
572
+ sage: p.color()
573
+ 'blue'
574
+ """
575
+ if color is None:
576
+ return self._color
577
+ else:
578
+ self._color = color
579
+
580
+ def frozenset(self):
581
+ r"""
582
+ Return the elements of `\ZZ^d` in the polyomino as a frozenset.
583
+
584
+ EXAMPLES::
585
+
586
+ sage: from sage.combinat.tiling import Polyomino
587
+ sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='red')
588
+ sage: p.frozenset()
589
+ frozenset({(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 1, 1)})
590
+ """
591
+ return self._blocs
592
+
593
+ @cached_method
594
+ def sorted_list(self):
595
+ r"""
596
+ Return the color of the polyomino.
597
+
598
+ EXAMPLES::
599
+
600
+ sage: from sage.combinat.tiling import Polyomino
601
+ sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
602
+ sage: p.sorted_list()
603
+ [(0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 1, 1)]
604
+ """
605
+ return sorted(self.frozenset())
606
+
607
+ def __len__(self):
608
+ r"""
609
+ Return the size of the polyomino, i.e. the number of n-dimensional
610
+ unit cubes.
611
+
612
+ EXAMPLES::
613
+
614
+ sage: from sage.combinat.tiling import Polyomino
615
+ sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
616
+ sage: len(p)
617
+ 4
618
+ """
619
+ return len(self.frozenset())
620
+
621
+ def __iter__(self):
622
+ r"""
623
+ EXAMPLES::
624
+
625
+ sage: from sage.combinat.tiling import Polyomino
626
+ sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
627
+ sage: it = iter(p)
628
+ sage: next(it)
629
+ (0, 0, 0)
630
+ """
631
+ return iter(self.sorted_list())
632
+
633
+ @cached_method
634
+ def bounding_box(self):
635
+ r"""
636
+ EXAMPLES::
637
+
638
+ sage: from sage.combinat.tiling import Polyomino
639
+ sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
640
+ sage: p.bounding_box()
641
+ [[0, 0, 0], [1, 2, 1]]
642
+ """
643
+ return [[min(w) for w in zip(*self)],
644
+ [max(w) for w in zip(*self)]]
645
+
646
+ def __hash__(self):
647
+ r"""
648
+ EXAMPLES::
649
+
650
+ sage: from sage.combinat.tiling import Polyomino
651
+ sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
652
+ sage: hash(p) # random
653
+ 2059134902
654
+ """
655
+ return hash(self.frozenset())
656
+
657
+ def __eq__(self, other):
658
+ r"""
659
+ Return whether ``self`` is equal to ``other``.
660
+
661
+ INPUT:
662
+
663
+ - ``other`` -- a polyomino
664
+
665
+ OUTPUT: boolean
666
+
667
+ EXAMPLES::
668
+
669
+ sage: from sage.combinat.tiling import Polyomino
670
+ sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
671
+ sage: q = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='red')
672
+ sage: p == q
673
+ True
674
+ sage: r = Polyomino([(0,0,0), (0,1,0), (1,1,0)], color='blue')
675
+ sage: p == r
676
+ False
677
+ """
678
+ return isinstance(other, Polyomino) and self.frozenset() == other.frozenset()
679
+
680
+ def __ne__(self, other):
681
+ r"""
682
+ Return whether ``self`` is not equal to ``other``.
683
+
684
+ INPUT:
685
+
686
+ - ``other`` -- a polyomino
687
+
688
+ OUTPUT: boolean
689
+
690
+ EXAMPLES::
691
+
692
+ sage: from sage.combinat.tiling import Polyomino
693
+ sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
694
+ sage: q = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='red')
695
+ sage: p != q
696
+ False
697
+ sage: r = Polyomino([(0,0,0), (0,1,0), (1,1,0)], color='blue')
698
+ sage: p != r
699
+ True
700
+ """
701
+ return not self.frozenset() == other.frozenset()
702
+
703
+ def __le__(self, other):
704
+ r"""
705
+ Return whether ``self`` is inside of ``other``.
706
+
707
+ INPUT:
708
+
709
+ - ``other`` -- a polyomino
710
+
711
+ OUTPUT: boolean
712
+
713
+ EXAMPLES::
714
+
715
+ sage: from sage.combinat.tiling import Polyomino
716
+ sage: p = Polyomino([(0,0)])
717
+ sage: b = Polyomino([(0,0), (0,1), (1,1), (2,1)])
718
+ sage: p <= b
719
+ True
720
+ sage: b <= p
721
+ False
722
+ """
723
+ return isinstance(other, Polyomino) and self.frozenset() <= other.frozenset()
724
+
725
+ def __ge__(self, other):
726
+ r"""
727
+ Return whether ``self`` contains ``other``.
728
+
729
+ INPUT:
730
+
731
+ - ``other`` -- a polyomino
732
+
733
+ OUTPUT: boolean
734
+
735
+ EXAMPLES::
736
+
737
+ sage: from sage.combinat.tiling import Polyomino
738
+ sage: p = Polyomino([(0,0)])
739
+ sage: b = Polyomino([(0,0), (0,1), (1,1), (2,1)])
740
+ sage: p >= b
741
+ False
742
+ sage: b >= p
743
+ True
744
+ """
745
+ return isinstance(other, Polyomino) and self.frozenset() >= other.frozenset()
746
+
747
+ def __lt__(self, other):
748
+ r"""
749
+ Return whether ``self`` is strictly inside of ``other``.
750
+
751
+ INPUT:
752
+
753
+ - ``other`` -- a polyomino
754
+
755
+ OUTPUT: boolean
756
+
757
+ EXAMPLES::
758
+
759
+ sage: from sage.combinat.tiling import Polyomino
760
+ sage: p = Polyomino([(0,0)])
761
+ sage: b = Polyomino([(0,0), (0,1), (1,1), (2,1)])
762
+ sage: p < b
763
+ True
764
+ sage: b < p
765
+ False
766
+ """
767
+ return isinstance(other, Polyomino) and self.frozenset() < other.frozenset()
768
+
769
+ def __gt__(self, other):
770
+ r"""
771
+ Return whether ``self`` strictly contains ``other``.
772
+
773
+ INPUT:
774
+
775
+ - ``other`` -- a polyomino
776
+
777
+ OUTPUT: boolean
778
+
779
+ EXAMPLES::
780
+
781
+ sage: from sage.combinat.tiling import Polyomino
782
+ sage: p = Polyomino([(0,0)])
783
+ sage: b = Polyomino([(0,0), (0,1), (1,1), (2,1)])
784
+ sage: p > b
785
+ False
786
+ sage: b > p
787
+ True
788
+ """
789
+ return isinstance(other, Polyomino) and self.frozenset() > other.frozenset()
790
+
791
+ def intersection(self, other):
792
+ r"""
793
+ Return the intersection of ``self`` and ``other``.
794
+
795
+ INPUT:
796
+
797
+ - ``other`` -- a polyomino
798
+
799
+ OUTPUT: polyomino
800
+
801
+ EXAMPLES::
802
+
803
+ sage: from sage.combinat.tiling import Polyomino
804
+ sage: a = Polyomino([(0,0)])
805
+ sage: b = Polyomino([(0,0), (0,1), (1,1), (2,1)])
806
+ sage: a.intersection(b)
807
+ Polyomino: [(0, 0)], Color: gray
808
+ sage: a.intersection(b+(1,1))
809
+ Polyomino: [], Color: gray
810
+ """
811
+ if not isinstance(other, Polyomino):
812
+ raise TypeError("other(={}) must be a polyomino".format(other))
813
+ return Polyomino(self.frozenset() & other.frozenset(),
814
+ color=self._color, dimension=self._dimension)
815
+
816
+ def __sub__(self, v):
817
+ r"""
818
+ Return a translated copy of ``self`` by the opposite of the
819
+ vector v.
820
+
821
+ INPUT:
822
+
823
+ - ``v`` -- tuple
824
+
825
+ OUTPUT: polyomino
826
+
827
+ EXAMPLES::
828
+
829
+ sage: from sage.combinat.tiling import Polyomino
830
+ sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
831
+ sage: p - (2,2,2)
832
+ Polyomino: [(-2, -2, -2), (-1, -2, -2), (-1, -1, -2), (-1, -1, -1), (-1, 0, -2)], Color: deeppink
833
+ """
834
+ v = self._free_module(v)
835
+ return Polyomino([p-v for p in self], color=self._color)
836
+
837
+ def __add__(self, v):
838
+ r"""
839
+ Return a translated copy of ``self`` by the vector v.
840
+
841
+ INPUT:
842
+
843
+ - ``v`` -- tuple
844
+
845
+ OUTPUT: polyomino
846
+
847
+ EXAMPLES::
848
+
849
+ sage: from sage.combinat.tiling import Polyomino
850
+ sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
851
+ sage: p + (2,2,2)
852
+ Polyomino: [(2, 2, 2), (3, 2, 2), (3, 3, 2), (3, 3, 3), (3, 4, 2)], Color: deeppink
853
+ """
854
+ v = self._free_module(v)
855
+ return Polyomino([p+v for p in self], color=self._color)
856
+
857
+ def __rmul__(self, m):
858
+ r"""
859
+ Return the image of the polyomino under the application of the
860
+ matrix m.
861
+
862
+ INPUT:
863
+
864
+ - ``m`` -- square matrix, matching the dimension of ``self``
865
+
866
+ OUTPUT: polyomino
867
+
868
+ EXAMPLES::
869
+
870
+ sage: from sage.combinat.tiling import Polyomino
871
+ sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
872
+ sage: m = matrix(3, [1,0,0,0,1,0,0,0,1])
873
+ sage: m * p
874
+ Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
875
+ sage: m = matrix(3, [1,0,0,0,0,-1,0,1,0])
876
+ sage: m * p
877
+ Polyomino: [(0, 0, 0), (1, -1, 1), (1, 0, 0), (1, 0, 1), (1, 0, 2)], Color: deeppink
878
+
879
+ TESTS::
880
+
881
+ sage: m = matrix(2, [1,0,0,1])
882
+ sage: m * p
883
+ Traceback (most recent call last):
884
+ ...
885
+ ValueError: Dimension of input matrix must match the dimension of the polyomino
886
+ """
887
+ if not m.nrows() == m.ncols() == self._dimension:
888
+ raise ValueError("Dimension of input matrix must match the "
889
+ "dimension of the polyomino")
890
+ return Polyomino([m * p for p in self], color=self._color)
891
+
892
+ def canonical(self):
893
+ r"""
894
+ Return the translated copy of ``self`` having minimal and nonnegative
895
+ coordinates
896
+
897
+ EXAMPLES::
898
+
899
+ sage: from sage.combinat.tiling import Polyomino
900
+ sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
901
+ sage: p
902
+ Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
903
+ sage: p.canonical()
904
+ Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
905
+
906
+ TESTS::
907
+
908
+ sage: p
909
+ Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
910
+ sage: p + (3,4,5)
911
+ Polyomino: [(3, 4, 5), (4, 4, 5), (4, 5, 5), (4, 5, 6), (4, 6, 5)], Color: deeppink
912
+ sage: (p + (3,4,5)).canonical()
913
+ Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
914
+ """
915
+ minxyz, _ = self.bounding_box()
916
+ return self - minxyz
917
+
918
+ def canonical_isometric_copies(self, orientation_preserving=True,
919
+ mod_box_isometries=False):
920
+ r"""
921
+ Return the list of image of ``self`` under isometries of the `n`-cube
922
+ where the coordinates are all nonnegative and minimal.
923
+
924
+ INPUT:
925
+
926
+ - ``orientation_preserving`` -- boolean (default: ``True``);
927
+ if ``True``, the group of isometries of the `n`-cube is restricted
928
+ to those that preserve the orientation, i.e. of determinant 1.
929
+
930
+ - ``mod_box_isometries`` -- boolean (default: ``False``); whether to
931
+ quotient the group of isometries of the `n`-cube by the
932
+ subgroup of isometries of the `a_1\times a_2\cdots \times a_n`
933
+ rectangular box where are the `a_i` are assumed to be distinct.
934
+
935
+ OUTPUT: set of Polyomino
936
+
937
+ EXAMPLES::
938
+
939
+ sage: from sage.combinat.tiling import Polyomino
940
+ sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
941
+ sage: s = p.canonical_isometric_copies()
942
+ sage: len(s)
943
+ 12
944
+
945
+ With the non orientation-preserving::
946
+
947
+ sage: s = p.canonical_isometric_copies(orientation_preserving=False)
948
+ sage: len(s)
949
+ 24
950
+
951
+ Modulo rotation by angle 180 degrees::
952
+
953
+ sage: s = p.canonical_isometric_copies(mod_box_isometries=True)
954
+ sage: len(s)
955
+ 3
956
+
957
+ TESTS::
958
+
959
+ sage: from sage.games.quantumino import pentaminos
960
+ sage: [len(p.canonical_isometric_copies((5,8,2), mod_box_isometries=False)) for p in pentaminos]
961
+ [24, 24, 24, 24, 24, 24, 12, 12, 24, 24, 24, 24, 12, 12, 24, 24, 12]
962
+ sage: [len(p.canonical_isometric_copies((5,8,2), mod_box_isometries=True)) for p in pentaminos]
963
+ [6, 6, 6, 6, 6, 6, 3, 3, 6, 6, 6, 6, 3, 3, 6, 6, 3]
964
+ """
965
+ if mod_box_isometries:
966
+ L = ncube_isometry_group_cosets(self._dimension, orientation_preserving)
967
+ P_cosets = {frozenset((m * self).canonical() for m in coset)
968
+ for coset in L}
969
+ P_cosets_representents = [min(s, key=lambda a: a.sorted_list()) for s in P_cosets]
970
+ return sorted(P_cosets_representents, key=lambda a: a.sorted_list())
971
+ else:
972
+ L = ncube_isometry_group(self._dimension, orientation_preserving)
973
+ P_images = {(m * self).canonical() for m in L}
974
+ return sorted(P_images, key=lambda a: a.sorted_list())
975
+
976
+ def translated_copies(self, box):
977
+ r"""
978
+ Return an iterator over the translated images of ``self`` inside a
979
+ polyomino.
980
+
981
+ INPUT:
982
+
983
+ - ``box`` -- polyomino or tuple of integers (size of a box)
984
+
985
+ OUTPUT: iterator of 3d polyominoes
986
+
987
+ EXAMPLES::
988
+
989
+ sage: from sage.combinat.tiling import Polyomino
990
+ sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
991
+ sage: for t in p.translated_copies(box=(5,8,2)): t
992
+ Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink
993
+ Polyomino: [(0, 1, 0), (1, 1, 0), (1, 2, 0), (1, 2, 1), (1, 3, 0)], Color: deeppink
994
+ Polyomino: [(0, 2, 0), (1, 2, 0), (1, 3, 0), (1, 3, 1), (1, 4, 0)], Color: deeppink
995
+ Polyomino: [(0, 3, 0), (1, 3, 0), (1, 4, 0), (1, 4, 1), (1, 5, 0)], Color: deeppink
996
+ Polyomino: [(0, 4, 0), (1, 4, 0), (1, 5, 0), (1, 5, 1), (1, 6, 0)], Color: deeppink
997
+ Polyomino: [(0, 5, 0), (1, 5, 0), (1, 6, 0), (1, 6, 1), (1, 7, 0)], Color: deeppink
998
+ Polyomino: [(1, 0, 0), (2, 0, 0), (2, 1, 0), (2, 1, 1), (2, 2, 0)], Color: deeppink
999
+ Polyomino: [(1, 1, 0), (2, 1, 0), (2, 2, 0), (2, 2, 1), (2, 3, 0)], Color: deeppink
1000
+ Polyomino: [(1, 2, 0), (2, 2, 0), (2, 3, 0), (2, 3, 1), (2, 4, 0)], Color: deeppink
1001
+ Polyomino: [(1, 3, 0), (2, 3, 0), (2, 4, 0), (2, 4, 1), (2, 5, 0)], Color: deeppink
1002
+ Polyomino: [(1, 4, 0), (2, 4, 0), (2, 5, 0), (2, 5, 1), (2, 6, 0)], Color: deeppink
1003
+ Polyomino: [(1, 5, 0), (2, 5, 0), (2, 6, 0), (2, 6, 1), (2, 7, 0)], Color: deeppink
1004
+ Polyomino: [(2, 0, 0), (3, 0, 0), (3, 1, 0), (3, 1, 1), (3, 2, 0)], Color: deeppink
1005
+ Polyomino: [(2, 1, 0), (3, 1, 0), (3, 2, 0), (3, 2, 1), (3, 3, 0)], Color: deeppink
1006
+ Polyomino: [(2, 2, 0), (3, 2, 0), (3, 3, 0), (3, 3, 1), (3, 4, 0)], Color: deeppink
1007
+ Polyomino: [(2, 3, 0), (3, 3, 0), (3, 4, 0), (3, 4, 1), (3, 5, 0)], Color: deeppink
1008
+ Polyomino: [(2, 4, 0), (3, 4, 0), (3, 5, 0), (3, 5, 1), (3, 6, 0)], Color: deeppink
1009
+ Polyomino: [(2, 5, 0), (3, 5, 0), (3, 6, 0), (3, 6, 1), (3, 7, 0)], Color: deeppink
1010
+ Polyomino: [(3, 0, 0), (4, 0, 0), (4, 1, 0), (4, 1, 1), (4, 2, 0)], Color: deeppink
1011
+ Polyomino: [(3, 1, 0), (4, 1, 0), (4, 2, 0), (4, 2, 1), (4, 3, 0)], Color: deeppink
1012
+ Polyomino: [(3, 2, 0), (4, 2, 0), (4, 3, 0), (4, 3, 1), (4, 4, 0)], Color: deeppink
1013
+ Polyomino: [(3, 3, 0), (4, 3, 0), (4, 4, 0), (4, 4, 1), (4, 5, 0)], Color: deeppink
1014
+ Polyomino: [(3, 4, 0), (4, 4, 0), (4, 5, 0), (4, 5, 1), (4, 6, 0)], Color: deeppink
1015
+ Polyomino: [(3, 5, 0), (4, 5, 0), (4, 6, 0), (4, 6, 1), (4, 7, 0)], Color: deeppink
1016
+
1017
+ This method is independent of the translation of the polyomino::
1018
+
1019
+ sage: q = Polyomino([(0,0,0), (1,0,0)])
1020
+ sage: list(q.translated_copies((2,2,1)))
1021
+ [Polyomino: [(0, 0, 0), (1, 0, 0)], Color: gray,
1022
+ Polyomino: [(0, 1, 0), (1, 1, 0)], Color: gray]
1023
+ sage: q = Polyomino([(34,7,-9), (35,7,-9)])
1024
+ sage: list(q.translated_copies((2,2,1)))
1025
+ [Polyomino: [(0, 0, 0), (1, 0, 0)], Color: gray,
1026
+ Polyomino: [(0, 1, 0), (1, 1, 0)], Color: gray]
1027
+
1028
+ Inside smaller boxes::
1029
+
1030
+ sage: list(p.translated_copies(box=(2,2,3)))
1031
+ []
1032
+ sage: list(p.translated_copies(box=(2,3,2)))
1033
+ [Polyomino: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 0)], Color: deeppink]
1034
+ sage: list(p.translated_copies(box=(3,2,2)))
1035
+ []
1036
+ sage: list(p.translated_copies(box=(1,1,1)))
1037
+ []
1038
+
1039
+ Using a Polyomino as input::
1040
+
1041
+ sage: b = Polyomino([(0,0), (0,1), (0,2), (1,0), (1,1), (1,2)])
1042
+ sage: p = Polyomino([(0,0)])
1043
+ sage: list(p.translated_copies(b))
1044
+ [Polyomino: [(0, 0)], Color: gray,
1045
+ Polyomino: [(0, 1)], Color: gray,
1046
+ Polyomino: [(0, 2)], Color: gray,
1047
+ Polyomino: [(1, 0)], Color: gray,
1048
+ Polyomino: [(1, 1)], Color: gray,
1049
+ Polyomino: [(1, 2)], Color: gray]
1050
+
1051
+ ::
1052
+
1053
+ sage: p = Polyomino([(0,0), (1,0), (0,1)])
1054
+ sage: b = Polyomino([(0,0), (1,0), (2,0), (0,1), (1,1), (0,2)])
1055
+ sage: list(p.translated_copies(b))
1056
+ [Polyomino: [(0, 0), (0, 1), (1, 0)], Color: gray,
1057
+ Polyomino: [(0, 1), (0, 2), (1, 1)], Color: gray,
1058
+ Polyomino: [(1, 0), (1, 1), (2, 0)], Color: gray]
1059
+ """
1060
+ if not isinstance(box, Polyomino):
1061
+ ranges = [range(a) for a in box]
1062
+ box = Polyomino(itertools.product(*ranges))
1063
+ if not box._dimension == self._dimension:
1064
+ raise ValueError("Dimension of input box must match the "
1065
+ "dimension of the polyomino")
1066
+ minxyz, maxxyz = self.bounding_box()
1067
+ minxyz, maxxyz = vector(minxyz), vector(maxxyz)
1068
+ size = maxxyz - minxyz
1069
+ boxminxyz, boxmaxxyz = box.bounding_box()
1070
+ ranges = [range(a, b-c+1) for (a,b,c) in zip(boxminxyz,
1071
+ boxmaxxyz,
1072
+ size)]
1073
+ cano = self.canonical()
1074
+ for v in itertools.product(*ranges):
1075
+ translated = cano + v
1076
+ if translated <= box:
1077
+ yield translated
1078
+
1079
+ def translated_copies_intersection(self, box):
1080
+ r"""
1081
+ Return the set of non empty intersections of translated images of
1082
+ ``self`` with a polyomino.
1083
+
1084
+ INPUT:
1085
+
1086
+ - ``box`` -- polyomino or tuple of integers (size of a box)
1087
+
1088
+ OUTPUT: set of 3d polyominoes
1089
+
1090
+ EXAMPLES::
1091
+
1092
+ sage: from sage.combinat.tiling import Polyomino
1093
+ sage: p = Polyomino([(0,0),(1,0)], color='deeppink')
1094
+ sage: sorted(sorted(a.frozenset())
1095
+ ....: for a in p.translated_copies_intersection(box=(2,3)))
1096
+ [[(0, 0)],
1097
+ [(0, 0), (1, 0)],
1098
+ [(0, 1)],
1099
+ [(0, 1), (1, 1)],
1100
+ [(0, 2)],
1101
+ [(0, 2), (1, 2)],
1102
+ [(1, 0)],
1103
+ [(1, 1)],
1104
+ [(1, 2)]]
1105
+
1106
+ Using a Polyomino as input::
1107
+
1108
+ sage: b = Polyomino([(0,0), (0,1), (0,2), (1,0), (2,0)])
1109
+ sage: p = Polyomino([(0,0), (1,0)])
1110
+ sage: sorted(sorted(a.frozenset())
1111
+ ....: for a in p.translated_copies_intersection(b))
1112
+ [[(0, 0)], [(0, 0), (1, 0)], [(0, 1)], [(0, 2)], [(1, 0), (2, 0)], [(2, 0)]]
1113
+ """
1114
+ if not isinstance(box, Polyomino):
1115
+ ranges = [range(a) for a in box]
1116
+ box = Polyomino(itertools.product(*ranges))
1117
+ if not box._dimension == self._dimension:
1118
+ raise ValueError("Dimension of input box must match the "
1119
+ "dimension of the polyomino")
1120
+ minxyz, maxxyz = self.bounding_box()
1121
+ minxyz, maxxyz = vector(minxyz), vector(maxxyz)
1122
+ size = maxxyz - minxyz
1123
+ boxminxyz, boxmaxxyz = box.bounding_box()
1124
+ ranges = [range(a-c, b+1) for (a,b,c) in zip(boxminxyz,
1125
+ boxmaxxyz,
1126
+ size)]
1127
+ S = set()
1128
+ cano = self.canonical()
1129
+ for v in itertools.product(*ranges):
1130
+ translated = cano + v
1131
+ intersected = translated.intersection(box)
1132
+ if intersected:
1133
+ S.add(intersected)
1134
+ return S
1135
+
1136
+ def isometric_copies(self, box, orientation_preserving=True,
1137
+ mod_box_isometries=False):
1138
+ r"""
1139
+ Return the translated and isometric images of ``self`` that lies in the box.
1140
+
1141
+ INPUT:
1142
+
1143
+ - ``box`` -- polyomino or tuple of integers (size of a box)
1144
+
1145
+ - ``orientation_preserving`` -- boolean (default: ``True``);
1146
+ if ``True``, the group of isometries of the `n`-cube is restricted
1147
+ to those that preserve the orientation, i.e. of determinant 1.
1148
+
1149
+ - ``mod_box_isometries`` -- boolean (default: ``False``); whether to
1150
+ quotient the group of isometries of the `n`-cube by the
1151
+ subgroup of isometries of the `a_1\times a_2\cdots \times a_n`
1152
+ rectangular box where are the `a_i` are assumed to be distinct.
1153
+
1154
+ EXAMPLES::
1155
+
1156
+ sage: from sage.combinat.tiling import Polyomino
1157
+ sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
1158
+ sage: L = list(p.isometric_copies(box=(5,8,2)))
1159
+ sage: len(L)
1160
+ 360
1161
+
1162
+ ::
1163
+
1164
+ sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,2,0),(1,2,1)], color='orange')
1165
+ sage: L = list(p.isometric_copies(box=(5,8,2)))
1166
+ sage: len(L)
1167
+ 180
1168
+ sage: L = list(p.isometric_copies((5,8,2), False))
1169
+ sage: len(L)
1170
+ 360
1171
+ sage: L = list(p.isometric_copies((5,8,2), mod_box_isometries=True))
1172
+ sage: len(L)
1173
+ 45
1174
+
1175
+ ::
1176
+
1177
+ sage: p = Polyomino([(0,0), (1,0), (0,1)])
1178
+ sage: b = Polyomino([(0,0), (1,0), (2,0), (0,1), (1,1), (0,2)])
1179
+ sage: sorted(p.isometric_copies(b), key=lambda p: p.sorted_list())
1180
+ [Polyomino: [(0, 0), (0, 1), (1, 0)], Color: gray,
1181
+ Polyomino: [(0, 0), (0, 1), (1, 1)], Color: gray,
1182
+ Polyomino: [(0, 0), (1, 0), (1, 1)], Color: gray,
1183
+ Polyomino: [(0, 1), (0, 2), (1, 1)], Color: gray,
1184
+ Polyomino: [(0, 1), (1, 0), (1, 1)], Color: gray,
1185
+ Polyomino: [(1, 0), (1, 1), (2, 0)], Color: gray]
1186
+ """
1187
+ if not isinstance(box, Polyomino):
1188
+ ranges = [range(a) for a in box]
1189
+ box = Polyomino(itertools.product(*ranges))
1190
+ if not box._dimension == self._dimension:
1191
+ raise ValueError("Dimension of input box must match the "
1192
+ "dimension of the polyomino")
1193
+ box_min_coords, box_max_coords = box.bounding_box()
1194
+ if mod_box_isometries and len({b - a for a, b in zip(box_min_coords,
1195
+ box_max_coords)}) < box._dimension:
1196
+ raise NotImplementedError("The code below assumes that the"
1197
+ " sizes of the box (={}) are all distinct when"
1198
+ " argument `mod_box_isometries` is True.".format(box))
1199
+ all_distinct_cano = self.canonical_isometric_copies(orientation_preserving,
1200
+ mod_box_isometries)
1201
+ for cano in all_distinct_cano:
1202
+ yield from cano.translated_copies(box=box)
1203
+
1204
+ def isometric_copies_intersection(self, box, orientation_preserving=True):
1205
+ r"""
1206
+ Return the set of non empty intersections of isometric images of
1207
+ ``self`` with a polyomino.
1208
+
1209
+ INPUT:
1210
+
1211
+ - ``box`` -- polyomino or tuple of integers (size of a box)
1212
+
1213
+ - ``orientation_preserving`` -- boolean (default: ``True``);
1214
+ if ``True``, the group of isometries of the `n`-cube is restricted
1215
+ to those that preserve the orientation, i.e. of determinant 1.
1216
+
1217
+ EXAMPLES::
1218
+
1219
+ sage: from sage.combinat.tiling import Polyomino
1220
+ sage: p = Polyomino([(0,0),(1,0)], color='deeppink')
1221
+ sage: sorted(sorted(a.frozenset()) for a in p.isometric_copies_intersection(box=(2,3)))
1222
+ [[(0, 0)],
1223
+ [(0, 0), (0, 1)],
1224
+ [(0, 0), (1, 0)],
1225
+ [(0, 1)],
1226
+ [(0, 1), (0, 2)],
1227
+ [(0, 1), (1, 1)],
1228
+ [(0, 2)],
1229
+ [(0, 2), (1, 2)],
1230
+ [(1, 0)],
1231
+ [(1, 0), (1, 1)],
1232
+ [(1, 1)],
1233
+ [(1, 1), (1, 2)],
1234
+ [(1, 2)]]
1235
+ """
1236
+ all_distinct_cano = self.canonical_isometric_copies(orientation_preserving,
1237
+ mod_box_isometries=False)
1238
+ return {t for cano in all_distinct_cano
1239
+ for t in cano.translated_copies_intersection(box=box)}
1240
+
1241
+ def neighbor_edges(self):
1242
+ r"""
1243
+ Return an iterator over the pairs of neighbor coordinates inside of
1244
+ the polyomino.
1245
+
1246
+ Two points `P` and `Q` in the polyomino are neighbor if `P - Q` has
1247
+ one coordinate equal to `+1` or `-1` and zero everywhere else.
1248
+
1249
+ EXAMPLES::
1250
+
1251
+ sage: from sage.combinat.tiling import Polyomino
1252
+ sage: p = Polyomino([(0,0,0),(0,0,1)])
1253
+ sage: [sorted(edge) for edge in p.neighbor_edges()]
1254
+ [[(0, 0, 0), (0, 0, 1)]]
1255
+
1256
+ In 3d::
1257
+
1258
+ sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
1259
+ sage: L = sorted(sorted(edge) for edge in p.neighbor_edges())
1260
+ sage: for a in L: a
1261
+ [(0, 0, 0), (1, 0, 0)]
1262
+ [(1, 0, 0), (1, 1, 0)]
1263
+ [(1, 1, 0), (1, 1, 1)]
1264
+ [(1, 1, 0), (1, 2, 0)]
1265
+
1266
+ In 2d::
1267
+
1268
+ sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)])
1269
+ sage: L = sorted(sorted(edge) for edge in p.neighbor_edges())
1270
+ sage: for a in L: a
1271
+ [(0, 0), (1, 0)]
1272
+ [(1, 0), (1, 1)]
1273
+ [(1, 1), (1, 2)]
1274
+ """
1275
+ for P, Q in itertools.combinations(self, 2):
1276
+ s = sorted(map(abs, Q-P))
1277
+ firsts = s[:-1]
1278
+ last = s[-1]
1279
+ if last == 1 and all(f == 0 for f in firsts):
1280
+ yield P, Q
1281
+
1282
+ def center(self):
1283
+ r"""
1284
+ Return the center of the polyomino.
1285
+
1286
+ EXAMPLES::
1287
+
1288
+ sage: from sage.combinat.tiling import Polyomino
1289
+ sage: p = Polyomino([(0,0,0),(0,0,1)])
1290
+ sage: p.center()
1291
+ (0, 0, 1/2)
1292
+
1293
+ In 3d::
1294
+
1295
+ sage: p = Polyomino([(0,0,0),(1,0,0),(1,1,0),(1,1,1),(1,2,0)], color='deeppink')
1296
+ sage: p.center()
1297
+ (4/5, 4/5, 1/5)
1298
+
1299
+ In 2d::
1300
+
1301
+ sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)])
1302
+ sage: p.center()
1303
+ (3/4, 3/4)
1304
+ """
1305
+ return sum(self) / len(self)
1306
+
1307
+ def boundary(self):
1308
+ r"""
1309
+ Return the boundary of a 2d polyomino.
1310
+
1311
+ INPUT:
1312
+
1313
+ - ``self`` -- a 2d polyomino
1314
+
1315
+ OUTPUT:
1316
+
1317
+ - list of edges (an edge is a pair of adjacent 2d coordinates)
1318
+
1319
+ EXAMPLES::
1320
+
1321
+ sage: from sage.combinat.tiling import Polyomino
1322
+ sage: p = Polyomino([(0,0), (1,0), (0,1), (1,1)])
1323
+ sage: sorted(p.boundary())
1324
+ [((-0.5, -0.5), (-0.5, 0.5)), ((-0.5, -0.5), (0.5, -0.5)),
1325
+ ((-0.5, 0.5), (-0.5, 1.5)), ((-0.5, 1.5), (0.5, 1.5)),
1326
+ ((0.5, -0.5), (1.5, -0.5)), ((0.5, 1.5), (1.5, 1.5)),
1327
+ ((1.5, -0.5), (1.5, 0.5)), ((1.5, 0.5), (1.5, 1.5))]
1328
+ sage: len(_)
1329
+ 8
1330
+ sage: p = Polyomino([(5,5)])
1331
+ sage: sorted(p.boundary())
1332
+ [((4.5, 4.5), (4.5, 5.5)), ((4.5, 4.5), (5.5, 4.5)),
1333
+ ((4.5, 5.5), (5.5, 5.5)), ((5.5, 4.5), (5.5, 5.5))]
1334
+ """
1335
+ if self._dimension != 2:
1336
+ raise NotImplementedError("The method boundary is currently "
1337
+ "implemented "
1338
+ "only for dimension 2")
1339
+ from collections import defaultdict
1340
+ horizontal = defaultdict(int)
1341
+ vertical = defaultdict(int)
1342
+ for a in self:
1343
+ x, y = a = tuple(a)
1344
+ horizontal[a] += 1
1345
+ vertical[a] += 1
1346
+ horizontal[(x, y+1)] -= 1
1347
+ vertical[(x+1, y)] -= 1
1348
+ edges = []
1349
+ h = 0.5
1350
+ for (x, y), coeff in horizontal.items():
1351
+ if coeff:
1352
+ edges.append(((x-h, y-h), (x+h, y-h)))
1353
+ for (x, y), coeff in vertical.items():
1354
+ if coeff:
1355
+ edges.append(((x-h, y-h), (x-h, y+h)))
1356
+ return edges
1357
+
1358
+ def show3d(self, size=1):
1359
+ r"""
1360
+ Return a 3d Graphic object representing the polyomino.
1361
+
1362
+ INPUT:
1363
+
1364
+ - ``self`` -- a polyomino of dimension 3
1365
+ - ``size`` -- number (default: ``1``); the size of each
1366
+ ``1 \times 1 \times 1`` cube. This does a homothety with respect
1367
+ to the center of the polyomino.
1368
+
1369
+ EXAMPLES::
1370
+
1371
+ sage: from sage.combinat.tiling import Polyomino
1372
+ sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue')
1373
+ sage: p.show3d() # long time (2s) # needs sage.plot
1374
+ Graphics3d Object
1375
+ """
1376
+ assert self._dimension == 3, "Dimension of the polyomino must be 3."
1377
+ from sage.plot.graphics import Graphics
1378
+ from sage.plot.plot3d.platonic import cube
1379
+ G = Graphics()
1380
+ for p in self:
1381
+ G += cube(p, color=self._color)
1382
+ center = self.center()
1383
+ G = G.translate(-center)
1384
+ G = G.scale(size)
1385
+ G = G.translate(center)
1386
+ return G
1387
+
1388
+ def show2d(self, size=0.7, color='black', thickness=1):
1389
+ r"""
1390
+ Return a 2d Graphic object representing the polyomino.
1391
+
1392
+ INPUT:
1393
+
1394
+ - ``self`` -- a polyomino of dimension 2
1395
+ - ``size`` -- number (default: ``0.7``); the size of each square
1396
+ - ``color`` -- color (default: ``'black'``); color of the boundary line
1397
+ - ``thickness`` -- number (default: ``1``); how thick the boundary line is
1398
+
1399
+ EXAMPLES::
1400
+
1401
+ sage: from sage.combinat.tiling import Polyomino
1402
+ sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)], color='deeppink')
1403
+ sage: p.show2d() # long time (0.5s) # needs sage.plot
1404
+ Graphics object consisting of 17 graphics primitives
1405
+ """
1406
+ assert self._dimension == 2, "Dimension of the polyomino must be 2."
1407
+ from sage.plot.graphics import Graphics
1408
+ from sage.plot.circle import circle
1409
+ from sage.plot.line import line
1410
+ from sage.plot.polygon import polygon
1411
+ h = size / 2.0
1412
+ G = Graphics()
1413
+ for a, b in self:
1414
+ G += circle((a, b), h, fill=True, color=self._color)
1415
+ k = h / 2.0
1416
+ for P, Q in self.neighbor_edges():
1417
+ a, b = (P + Q) / 2.0
1418
+ G += polygon([(a-k, b-k), (a+k, b-k), (a+k, b+k), (a-k, b+k),
1419
+ (a-k, b-k)], color=self._color)
1420
+ for edge in self.boundary():
1421
+ G += line(edge, color=color, thickness=thickness)
1422
+ return G
1423
+
1424
+ def self_surrounding(self, radius, remove_incomplete_copies=True,
1425
+ ncpus=None):
1426
+ r"""
1427
+ Return a list of isometric copies of ``self`` surrounding it with an
1428
+ annulus of given radius.
1429
+
1430
+ INPUT:
1431
+
1432
+ - ``self`` -- a polyomino of dimension 2
1433
+ - ``radius`` -- integer
1434
+ - ``remove_incomplete_copies`` -- boolean (default: ``True``); whether
1435
+ to keep only complete copies of ``self`` in the output
1436
+ - ``ncpus`` -- integer (default: ``None``); maximal number of
1437
+ subprocesses to use at the same time. If ``None``, it detects the
1438
+ number of effective CPUs in the system using
1439
+ :func:`sage.parallel.ncpus.ncpus()`.
1440
+ If ``ncpus=1``, the first solution is searched serially.
1441
+
1442
+ OUTPUT: list of polyominoes
1443
+
1444
+ EXAMPLES::
1445
+
1446
+ sage: from sage.combinat.tiling import Polyomino
1447
+ sage: H = Polyomino([(-1, 1), (-1, 4), (-1, 7), (0, 0), (0, 1), (0, 2),
1448
+ ....: (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (1, 1), (1, 2),
1449
+ ....: (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (2, 0), (2, 2),
1450
+ ....: (2, 3), (2, 5), (2, 6), (2, 8)])
1451
+ sage: solution = H.self_surrounding(8) # needs sage.plot
1452
+ sage: G = sum([p.show2d() for p in solution], Graphics()) # needs sage.plot
1453
+
1454
+ ::
1455
+
1456
+ sage: solution = H.self_surrounding(8, remove_incomplete_copies=False) # needs sage.plot
1457
+ sage: G = sum([p.show2d() for p in solution], Graphics()) # needs sage.plot
1458
+ """
1459
+ # Define the box to tile
1460
+ minxyz, maxxyz = self.bounding_box()
1461
+ minxyz, maxxyz = vector(minxyz), vector(maxxyz)
1462
+ v = vector([radius for _ in range(self._dimension)])
1463
+ ranges = [range(a,b) for a,b in zip(minxyz-v, maxxyz+v)]
1464
+ box = Polyomino(itertools.product(*ranges))
1465
+
1466
+ # Get the rows for this problem
1467
+ T = TilingSolver([self], box=box, reusable=True,
1468
+ reflection=True, rotation=True, outside=True)
1469
+ rows = T.rows()
1470
+
1471
+ # Add one row to force the placement of the central tile
1472
+ coord_to_int = T.coord_to_int_dict()
1473
+ new_row = [coord_to_int[coord] for coord in self]
1474
+ new_row.append(len(coord_to_int)) # to force this row in the solution
1475
+ forced_row_number = len(rows)
1476
+ rows.append(new_row)
1477
+
1478
+ # Construct the dancing links solver
1479
+ from sage.combinat.matrices.dancing_links import dlx_solver
1480
+ d = dlx_solver(rows)
1481
+
1482
+ # Solve
1483
+ solution = d.one_solution(ncpus=ncpus)
1484
+ if solution is None:
1485
+ raise ValueError('No solution was found with radius={}, '
1486
+ 'this tile can not be surrounded by itself'.format(radius))
1487
+
1488
+ # Recover the polyominoes
1489
+ assert forced_row_number in solution
1490
+ solution.remove(forced_row_number)
1491
+ polyominoes = [T.row_to_polyomino(v) for v in solution]
1492
+ if remove_incomplete_copies:
1493
+ polyominoes = [p for p in polyominoes if len(p) == len(self)]
1494
+
1495
+ # Recolor randomly the polyominoes
1496
+ from sage.plot.colors import Color
1497
+ from random import random
1498
+ for p in polyominoes:
1499
+ random_color = Color(tuple(random() for _ in range(3)))
1500
+ p.color(random_color)
1501
+
1502
+ return polyominoes
1503
+
1504
+
1505
+ #######################
1506
+ # General tiling solver
1507
+ #######################
1508
+ class TilingSolver(SageObject):
1509
+ r"""
1510
+ Tiling solver.
1511
+
1512
+ Solve the problem of tiling a polyomino with a certain number
1513
+ of polyominoes.
1514
+
1515
+ INPUT:
1516
+
1517
+ - ``pieces`` -- iterable of Polyominoes
1518
+ - ``box`` -- polyomino or tuple of integers (size of a box)
1519
+ - ``rotation`` -- boolean (default: ``True``); whether to allow
1520
+ rotations
1521
+ - ``reflection`` -- boolean (default: ``False``); whether to allow
1522
+ reflections
1523
+ - ``reusable`` -- boolean (default: ``False``); whether to allow
1524
+ the pieces to be reused
1525
+ - ``outside`` -- boolean (default: ``False``); whether to allow
1526
+ pieces to partially go outside of the box (all non-empty intersection
1527
+ of the pieces with the box are considered)
1528
+
1529
+ EXAMPLES:
1530
+
1531
+ By default, rotations are allowed and reflections are not allowed::
1532
+
1533
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
1534
+ sage: p = Polyomino([(0,0,0)])
1535
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
1536
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
1537
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
1538
+ sage: T
1539
+ Tiling solver of 3 pieces into a box of size 6
1540
+ Rotation allowed: True
1541
+ Reflection allowed: False
1542
+ Reusing pieces allowed: False
1543
+
1544
+ Solutions are given by an iterator::
1545
+
1546
+ sage: it = T.solve()
1547
+ sage: for p in next(it): p
1548
+ Polyomino: [(0, 0, 0)], Color: gray
1549
+ Polyomino: [(0, 0, 1), (0, 0, 2)], Color: gray
1550
+ Polyomino: [(0, 0, 3), (0, 0, 4), (0, 0, 5)], Color: gray
1551
+
1552
+ Another solution::
1553
+
1554
+ sage: for p in next(it): p
1555
+ Polyomino: [(0, 0, 0)], Color: gray
1556
+ Polyomino: [(0, 0, 1), (0, 0, 2), (0, 0, 3)], Color: gray
1557
+ Polyomino: [(0, 0, 4), (0, 0, 5)], Color: gray
1558
+
1559
+ Tiling of a polyomino by polyominoes::
1560
+
1561
+ sage: b = Polyomino([(0,0), (1,0), (1,1), (2,1), (1,2), (2,2), (0,3), (1,3)])
1562
+ sage: p = Polyomino([(0,0), (1,0)])
1563
+ sage: T = TilingSolver([p], box=b, reusable=True)
1564
+ sage: T.number_of_solutions()
1565
+ 2
1566
+
1567
+ TESTS::
1568
+
1569
+ sage: T = TilingSolver([p,q,r], box=(1,1,6), rotation=False, reflection=True)
1570
+ Traceback (most recent call last):
1571
+ ...
1572
+ NotImplementedError: When reflection is allowed and rotation is not allowed
1573
+ """
1574
+
1575
+ def __init__(self, pieces, box, rotation=True,
1576
+ reflection=False, reusable=False, outside=False):
1577
+ r"""
1578
+ Constructor.
1579
+
1580
+ EXAMPLES::
1581
+
1582
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
1583
+ sage: p = Polyomino([(0,0,0)])
1584
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
1585
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
1586
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
1587
+ sage: T
1588
+ Tiling solver of 3 pieces into a box of size 6
1589
+ Rotation allowed: True
1590
+ Reflection allowed: False
1591
+ Reusing pieces allowed: False
1592
+ """
1593
+ self._pieces = pieces
1594
+ self._free_module = self._pieces[0]._free_module
1595
+ if isinstance(box, Polyomino):
1596
+ self._box = box
1597
+ else:
1598
+ ranges = [range(a) for a in box]
1599
+ self._box = Polyomino(itertools.product(*ranges))
1600
+ self._rotation = rotation
1601
+ self._reflection = reflection
1602
+ if not self._rotation and self._reflection:
1603
+ raise NotImplementedError("When reflection is allowed and "
1604
+ "rotation is not allowed")
1605
+ self._reusable = reusable
1606
+ self._outside = outside
1607
+
1608
+ def _repr_(self):
1609
+ r"""
1610
+ String representation.
1611
+
1612
+ EXAMPLES::
1613
+
1614
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
1615
+ sage: p = Polyomino([(0,0,0)])
1616
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
1617
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
1618
+ sage: TilingSolver([p,q,r], box=(1,1,6))
1619
+ Tiling solver of 3 pieces into a box of size 6
1620
+ Rotation allowed: True
1621
+ Reflection allowed: False
1622
+ Reusing pieces allowed: False
1623
+ """
1624
+ s = "Tiling solver of %s pieces " % len(self._pieces)
1625
+ s += "into a box of size %s\n" % len(self._box)
1626
+ s += "Rotation allowed: %s\n" % self._rotation
1627
+ s += "Reflection allowed: %s\n" % self._reflection
1628
+ s += "Reusing pieces allowed: %s" % self._reusable
1629
+ return s
1630
+
1631
+ def is_suitable(self):
1632
+ r"""
1633
+ Return whether the volume of the box is equal to sum of the volume
1634
+ of the polyominoes and the number of rows sent to the DLX solver is
1635
+ larger than zero.
1636
+
1637
+ If these conditions are not verified, then the problem is not suitable
1638
+ in the sense that there are no solution.
1639
+
1640
+ EXAMPLES::
1641
+
1642
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
1643
+ sage: p = Polyomino([(0,0,0)])
1644
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
1645
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
1646
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
1647
+ sage: T.is_suitable()
1648
+ True
1649
+ sage: T = TilingSolver([p,q,r], box=(1,1,7))
1650
+ sage: T.is_suitable()
1651
+ False
1652
+ """
1653
+ if self._reusable:
1654
+ return len(self.rows()) != 0
1655
+ else:
1656
+ return (sum(len(p) for p in self.pieces()) == len(self._box)
1657
+ and len(self.rows()) != 0)
1658
+
1659
+ def pieces(self):
1660
+ r"""
1661
+ Return the list of pieces.
1662
+
1663
+ OUTPUT: list of 3d polyominoes
1664
+
1665
+ EXAMPLES::
1666
+
1667
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
1668
+ sage: p = Polyomino([(0,0,0)])
1669
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
1670
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
1671
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
1672
+ sage: for p in T._pieces: p
1673
+ Polyomino: [(0, 0, 0)], Color: gray
1674
+ Polyomino: [(0, 0, 0), (0, 0, 1)], Color: gray
1675
+ Polyomino: [(0, 0, 0), (0, 0, 1), (0, 0, 2)], Color: gray
1676
+ """
1677
+ return self._pieces
1678
+
1679
+ def space(self):
1680
+ r"""
1681
+ Return an iterator over all the nonnegative integer coordinates
1682
+ contained in the space to tile.
1683
+
1684
+ EXAMPLES::
1685
+
1686
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
1687
+ sage: p = Polyomino([(0,0,0)])
1688
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
1689
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
1690
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
1691
+ sage: list(T.space())
1692
+ [(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 0, 4), (0, 0, 5)]
1693
+ """
1694
+ return iter(self._box)
1695
+
1696
+ @cached_method
1697
+ def coord_to_int_dict(self):
1698
+ r"""
1699
+ Return a dictionary mapping coordinates to integers.
1700
+
1701
+ OUTPUT: dictionary
1702
+
1703
+ EXAMPLES::
1704
+
1705
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
1706
+ sage: p = Polyomino([(0,0,0)])
1707
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
1708
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
1709
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
1710
+ sage: A = T.coord_to_int_dict()
1711
+ sage: sorted(A.items())
1712
+ [((0, 0, 0), 3), ((0, 0, 1), 4), ((0, 0, 2), 5),
1713
+ ((0, 0, 3), 6), ((0, 0, 4), 7), ((0, 0, 5), 8)]
1714
+
1715
+ Reusable pieces::
1716
+
1717
+ sage: p = Polyomino([(0,0), (0,1)])
1718
+ sage: q = Polyomino([(0,0), (0,1), (1,0), (1,1)])
1719
+ sage: T = TilingSolver([p,q], box=[3,2], reusable=True)
1720
+ sage: B = T.coord_to_int_dict()
1721
+ sage: sorted(B.items())
1722
+ [((0, 0), 0), ((0, 1), 1), ((1, 0), 2), ((1, 1), 3),
1723
+ ((2, 0), 4), ((2, 1), 5)]
1724
+ """
1725
+ if self._reusable:
1726
+ return {c: i for i, c in enumerate(self.space())}
1727
+
1728
+ number_of_pieces = len(self._pieces)
1729
+ return {c: i + number_of_pieces for i, c in enumerate(self.space())}
1730
+
1731
+ @cached_method
1732
+ def int_to_coord_dict(self):
1733
+ r"""
1734
+ Return a dictionary mapping integers to coordinates.
1735
+
1736
+ EXAMPLES::
1737
+
1738
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
1739
+ sage: p = Polyomino([(0,0,0)])
1740
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
1741
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
1742
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
1743
+ sage: B = T.int_to_coord_dict()
1744
+ sage: sorted(B.items())
1745
+ [(3, (0, 0, 0)), (4, (0, 0, 1)), (5, (0, 0, 2)),
1746
+ (6, (0, 0, 3)), (7, (0, 0, 4)), (8, (0, 0, 5))]
1747
+
1748
+ Reusable pieces::
1749
+
1750
+ sage: from sage.combinat.tiling import Polyomino, TilingSolver
1751
+ sage: p = Polyomino([(0,0), (0,1)])
1752
+ sage: q = Polyomino([(0,0), (0,1), (1,0), (1,1)])
1753
+ sage: T = TilingSolver([p,q], box=[3,2], reusable=True)
1754
+ sage: B = T.int_to_coord_dict()
1755
+ sage: sorted(B.items())
1756
+ [(0, (0, 0)), (1, (0, 1)), (2, (1, 0)),
1757
+ (3, (1, 1)), (4, (2, 0)), (5, (2, 1))]
1758
+
1759
+ TESTS:
1760
+
1761
+ The methods ``int_to_coord_dict`` and ``coord_to_int_dict`` returns
1762
+ dictionary that are inverse of each other::
1763
+
1764
+ sage: A = T.coord_to_int_dict()
1765
+ sage: B = T.int_to_coord_dict()
1766
+ sage: all(A[B[i]] == i for i in B)
1767
+ True
1768
+ sage: all(B[A[i]] == i for i in A)
1769
+ True
1770
+ """
1771
+ if self._reusable:
1772
+ return dict(enumerate(self.space()))
1773
+ return dict(enumerate(self.space(), start=len(self._pieces)))
1774
+
1775
+ @cached_method
1776
+ def rows_for_piece(self, i, mod_box_isometries=False):
1777
+ r"""
1778
+ Return the rows for the `i`-th piece.
1779
+
1780
+ INPUT:
1781
+
1782
+ - ``i`` -- integer; the `i`-th piece
1783
+
1784
+ - ``mod_box_isometries`` -- boolean (default: ``False``); whether to
1785
+ consider only rows for positions up to the action of the
1786
+ quotient the group of isometries of the `n`-cube by the
1787
+ subgroup of isometries of the `a_1\times a_2\cdots \times a_n`
1788
+ rectangular box where are the `a_i` are assumed to be distinct.
1789
+
1790
+ EXAMPLES::
1791
+
1792
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
1793
+ sage: p = Polyomino([(0,0,0)])
1794
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
1795
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
1796
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
1797
+ sage: T.rows_for_piece(0)
1798
+ [[0, 3], [0, 4], [0, 5], [0, 6], [0, 7], [0, 8]]
1799
+ sage: T.rows_for_piece(1)
1800
+ [[1, 3, 4], [1, 4, 5], [1, 5, 6], [1, 6, 7], [1, 7, 8]]
1801
+ sage: T.rows_for_piece(2)
1802
+ [[2, 3, 4, 5], [2, 4, 5, 6], [2, 5, 6, 7], [2, 6, 7, 8]]
1803
+
1804
+ Less rows when using ``mod_box_isometries=True``::
1805
+
1806
+ sage: a = Polyomino([(0,0,0), (0,0,1), (1,0,0)])
1807
+ sage: b = Polyomino([(0,0,0), (1,0,0), (0,1,0)])
1808
+ sage: T = TilingSolver([a,b], box=(2,1,3))
1809
+ sage: T.rows_for_piece(0)
1810
+ [[0, 2, 3, 5],
1811
+ [0, 3, 4, 6],
1812
+ [0, 2, 3, 6],
1813
+ [0, 3, 4, 7],
1814
+ [0, 2, 5, 6],
1815
+ [0, 3, 6, 7],
1816
+ [0, 3, 5, 6],
1817
+ [0, 4, 6, 7]]
1818
+ sage: T.rows_for_piece(0, mod_box_isometries=True)
1819
+ [[0, 2, 3, 5], [0, 3, 4, 6]]
1820
+ sage: T.rows_for_piece(1, mod_box_isometries=True)
1821
+ [[1, 2, 3, 5], [1, 3, 4, 6]]
1822
+ """
1823
+ p = self._pieces[i]
1824
+ if self._rotation:
1825
+ if self._reflection:
1826
+ orientation_preserving = False
1827
+ else:
1828
+ orientation_preserving = True
1829
+ if self._outside:
1830
+ it = p.isometric_copies_intersection(self._box,
1831
+ orientation_preserving=orientation_preserving)
1832
+ else:
1833
+ it = p.isometric_copies(self._box,
1834
+ orientation_preserving=orientation_preserving,
1835
+ mod_box_isometries=mod_box_isometries)
1836
+ else:
1837
+ if self._reflection:
1838
+ raise NotImplementedError("Reflection allowed, Rotation not "
1839
+ "allowed is not implemented")
1840
+ else:
1841
+ if self._outside:
1842
+ it = p.translated_copies_intersection(self._box)
1843
+ else:
1844
+ it = p.translated_copies(self._box)
1845
+ coord_to_int = self.coord_to_int_dict()
1846
+ rows = []
1847
+ for q in it:
1848
+ L = [] if self._reusable else [i]
1849
+ L.extend(coord_to_int[coord] for coord in q)
1850
+ rows.append(L)
1851
+ return rows
1852
+
1853
+ @cached_method
1854
+ def rows(self):
1855
+ r"""
1856
+ Creation of the rows.
1857
+
1858
+ EXAMPLES::
1859
+
1860
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
1861
+ sage: p = Polyomino([(0,0,0)])
1862
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
1863
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
1864
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
1865
+ sage: rows = T.rows()
1866
+ sage: for row in rows: row
1867
+ [0, 3]
1868
+ [0, 4]
1869
+ [0, 5]
1870
+ [0, 6]
1871
+ [0, 7]
1872
+ [0, 8]
1873
+ [1, 3, 4]
1874
+ [1, 4, 5]
1875
+ [1, 5, 6]
1876
+ [1, 6, 7]
1877
+ [1, 7, 8]
1878
+ [2, 3, 4, 5]
1879
+ [2, 4, 5, 6]
1880
+ [2, 5, 6, 7]
1881
+ [2, 6, 7, 8]
1882
+ """
1883
+ rows = []
1884
+ for i in range(len(self._pieces)):
1885
+ rows.extend(self.rows_for_piece(i))
1886
+ return rows
1887
+
1888
+ def _rows_mod_box_isometries(self, i):
1889
+ r"""
1890
+ Return a list of rows representing the solutions up to isometries of
1891
+ the box.
1892
+
1893
+ The positions of the `i`-th pieces are chosen up to isometries of
1894
+ the box. In dimension 3, there are four times less rows for that
1895
+ piece.
1896
+
1897
+ It is currently implemented only when the pieces are not reusable.
1898
+
1899
+ INPUT:
1900
+
1901
+ - ``i`` -- integer; the `i`-th piece to consider, that piece must not
1902
+ be isometric to itself by a isometry that preserve the box
1903
+
1904
+ EXAMPLES::
1905
+
1906
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
1907
+ sage: p = Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (2,0,1)],
1908
+ ....: color='red')
1909
+ sage: T = TilingSolver([p], box=(3,4,2))
1910
+ sage: T._rows_mod_box_isometries(0)
1911
+ [[0, 1, 3, 4, 11, 13],
1912
+ [0, 3, 5, 6, 13, 15],
1913
+ [0, 9, 11, 12, 19, 21],
1914
+ [0, 11, 13, 14, 21, 23],
1915
+ [0, 1, 9, 10, 11, 18],
1916
+ [0, 3, 11, 12, 13, 20],
1917
+ [0, 5, 13, 14, 15, 22],
1918
+ [0, 2, 3, 4, 5, 11],
1919
+ [0, 4, 5, 6, 7, 13],
1920
+ [0, 10, 11, 12, 13, 19],
1921
+ [0, 12, 13, 14, 15, 21],
1922
+ [0, 2, 9, 10, 12, 20],
1923
+ [0, 4, 11, 12, 14, 22],
1924
+ [0, 6, 13, 14, 16, 24]]
1925
+
1926
+ We test that there are four times less rows for that polyomino::
1927
+
1928
+ sage: len(T.rows()) == 4 * len(T._rows_mod_box_isometries(0))
1929
+ True
1930
+
1931
+ Now, a real use case. A solution of the game Quantumino is a tiling
1932
+ of a `5 \times 8 \times 2` box. Since a `5 \times 8 \times 2` box
1933
+ has four orientation preserving isometries, each solution up to
1934
+ rotation is counted four times by this dancing links solver::
1935
+
1936
+ sage: from sage.games.quantumino import QuantuminoSolver
1937
+ sage: from sage.combinat.matrices.dancing_links import dlx_solver
1938
+ sage: q = QuantuminoSolver(0)
1939
+ sage: T = q.tiling_solver()
1940
+ sage: dlx_solver(T.rows()) # long time (10s)
1941
+ Dancing links solver for 96 columns and 5484 rows
1942
+
1943
+ It is possible to avoid to compute 4 times each solution up to
1944
+ rotations. This is done by choosing a piece (here the 0th) and
1945
+ considering 4 times less positions for that piece. To be precise,
1946
+ 90 positions instead of 360, therefore the dancing links solver
1947
+ below has 270 less rows::
1948
+
1949
+ sage: dlx_solver(T._rows_mod_box_isometries(0)) # long time (10s)
1950
+ Dancing links solver for 96 columns and 5214 rows
1951
+ """
1952
+ assert not self._reusable, ("this code assumes the pieces are not reusable")
1953
+ len_pieces = len(self._pieces)
1954
+ if not 0 <= i < len_pieces:
1955
+ raise ValueError("i(={}) must be 0 <= i < {}".format(i,len_pieces))
1956
+ rows = []
1957
+ for j in range(len_pieces):
1958
+ if j == i:
1959
+ rows.extend(self.rows_for_piece(j, mod_box_isometries=True))
1960
+ else:
1961
+ rows.extend(self.rows_for_piece(j))
1962
+ return rows
1963
+
1964
+ def nrows_per_piece(self):
1965
+ r"""
1966
+ Return the number of rows necessary by each piece.
1967
+
1968
+ OUTPUT: list
1969
+
1970
+ EXAMPLES::
1971
+
1972
+ sage: from sage.games.quantumino import QuantuminoSolver
1973
+ sage: q = QuantuminoSolver(0)
1974
+ sage: T = q.tiling_solver()
1975
+ sage: T.nrows_per_piece() # long time (10s)
1976
+ [360, 360, 360, 360, 360, 180, 180, 672, 672, 360, 360, 180, 180, 360, 360, 180]
1977
+ """
1978
+ return [len(self.rows_for_piece(i)) for i in range(len(self._pieces))]
1979
+
1980
+ def starting_rows(self):
1981
+ r"""
1982
+ Return the starting rows for each piece.
1983
+
1984
+ EXAMPLES::
1985
+
1986
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
1987
+ sage: p = Polyomino([(0,0,0)])
1988
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
1989
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
1990
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
1991
+ sage: T.starting_rows()
1992
+ [0, 6, 11, 15]
1993
+ """
1994
+ s = 0
1995
+ S = [s]
1996
+ for a in self.nrows_per_piece():
1997
+ s += a
1998
+ S.append(s)
1999
+ return S
2000
+
2001
+ def row_to_polyomino(self, row_number):
2002
+ r"""
2003
+ Return a polyomino associated to a row.
2004
+
2005
+ INPUT:
2006
+
2007
+ - ``row_number`` -- integer; the `i`-th row
2008
+
2009
+ OUTPUT: polyomino
2010
+
2011
+ EXAMPLES::
2012
+
2013
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
2014
+ sage: a = Polyomino([(0,0,0), (0,0,1), (1,0,0)], color='blue')
2015
+ sage: b = Polyomino([(0,0,0), (1,0,0), (0,1,0)], color='red')
2016
+ sage: T = TilingSolver([a,b], box=(2,1,3))
2017
+ sage: len(T.rows())
2018
+ 16
2019
+
2020
+ ::
2021
+
2022
+ sage: T.row_to_polyomino(7)
2023
+ Polyomino: [(0, 0, 2), (1, 0, 1), (1, 0, 2)], Color: blue
2024
+
2025
+ ::
2026
+
2027
+ sage: T.row_to_polyomino(13)
2028
+ Polyomino: [(0, 0, 1), (1, 0, 1), (1, 0, 2)], Color: red
2029
+
2030
+ TESTS:
2031
+
2032
+ We check that issue :issue:`32252` is fixed and that colors of
2033
+ polyominoes are properly recovered::
2034
+
2035
+ sage: v = Polyomino([(0, 0), (0, 1)], color='blue')
2036
+ sage: h = Polyomino([(0, 0), (1, 0)], color='red')
2037
+ sage: T = TilingSolver(pieces=[v, h], box=(2, 2),
2038
+ ....: rotation=False, reflection=False, reusable=True)
2039
+ sage: for i in range(4): print(i,T.row_to_polyomino(i))
2040
+ 0 Polyomino: [(0, 0), (0, 1)], Color: blue
2041
+ 1 Polyomino: [(1, 0), (1, 1)], Color: blue
2042
+ 2 Polyomino: [(0, 0), (1, 0)], Color: red
2043
+ 3 Polyomino: [(0, 1), (1, 1)], Color: red
2044
+ """
2045
+ rows = self.rows()
2046
+ row = rows[row_number]
2047
+ if self._reusable:
2048
+ if row_number < 0:
2049
+ row_number += len(rows)
2050
+ from bisect import bisect
2051
+ no = bisect(self.starting_rows(), row_number) - 1
2052
+ indices = row
2053
+ else:
2054
+ no = row[0]
2055
+ indices = row[1:]
2056
+ int_to_coord = self.int_to_coord_dict()
2057
+ coords = [int_to_coord[i] for i in indices]
2058
+ color = self._pieces[no].color()
2059
+ return Polyomino(coords, color=color)
2060
+
2061
+ def dlx_solver(self):
2062
+ r"""
2063
+ Return the sage DLX solver of that tiling problem.
2064
+
2065
+ OUTPUT: dLX Solver
2066
+
2067
+ EXAMPLES::
2068
+
2069
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
2070
+ sage: p = Polyomino([(0,0,0)])
2071
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
2072
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
2073
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
2074
+ sage: T.dlx_solver()
2075
+ Dancing links solver for 9 columns and 15 rows
2076
+ """
2077
+ from sage.combinat.matrices.dancing_links import dlx_solver
2078
+ return dlx_solver(self.rows())
2079
+
2080
+ def _dlx_solutions_iterator(self):
2081
+ r"""
2082
+ Return an iterator over the row indices of the solutions.
2083
+
2084
+ OUTPUT: iterator
2085
+
2086
+ EXAMPLES::
2087
+
2088
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
2089
+ sage: p = Polyomino([(0,0,0)])
2090
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
2091
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
2092
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
2093
+ sage: list(T._dlx_solutions_iterator())
2094
+ [[0, 7, 14], [0, 12, 10], [6, 13, 5], [6, 14, 2], [11, 9, 5], [11, 10, 3]]
2095
+ """
2096
+ if len(self.rows()) == 0:
2097
+ return
2098
+
2099
+ x = self.dlx_solver()
2100
+ while x.search() == 1:
2101
+ yield x.get_solution()
2102
+
2103
+ def _dlx_common_prefix_solutions_iterator(self):
2104
+ r"""
2105
+ Return an iterator over the row indices of solutions and of partial
2106
+ solutions, i.e. the common prefix of two consecutive solutions.
2107
+
2108
+ The purpose is to illustrate the backtracking and construct an
2109
+ animation of the evolution of solutions.
2110
+
2111
+ OUTPUT: iterator
2112
+
2113
+ EXAMPLES::
2114
+
2115
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
2116
+ sage: p = Polyomino([(0,0,0)])
2117
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
2118
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
2119
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
2120
+ sage: list(T._dlx_solutions_iterator())
2121
+ [[0, 7, 14], [0, 12, 10], [6, 13, 5], [6, 14, 2], [11, 9, 5], [11, 10, 3]]
2122
+ sage: list(T._dlx_common_prefix_solutions_iterator())
2123
+ [[0, 7, 14], [0], [0, 12, 10], [], [6, 13, 5], [6], [6, 14, 2], [], [11, 9, 5], [11], [11, 10, 3]]
2124
+
2125
+ ::
2126
+
2127
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
2128
+ sage: y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color='yellow')
2129
+ sage: T = TilingSolver([y], box=(5,10), reusable=True, reflection=True)
2130
+ sage: for a in T._dlx_common_prefix_solutions_iterator(): a
2131
+ [0, 83, 114, 43, 158, 5, 128, 183, 168, 25]
2132
+ [0, 83, 114, 43, 158]
2133
+ [0, 83, 114, 43, 158, 33, 128, 183, 104, 25]
2134
+ [0, 83, 114]
2135
+ [0, 83, 114, 100, 52, 183, 128, 33, 95, 47]
2136
+ [0, 83]
2137
+ [0, 83, 178, 15, 158, 5, 128, 183, 168, 25]
2138
+ [0, 83, 178, 15, 158]
2139
+ [0, 83, 178, 15, 158, 33, 128, 183, 104, 25]
2140
+ []
2141
+ [56, 1, 113, 15, 159, 34, 155, 182, 168, 24]
2142
+ [56, 1, 113]
2143
+ [56, 1, 113, 164, 51, 118, 155, 34, 96, 47]
2144
+ [56, 1, 113, 164, 51]
2145
+ [56, 1, 113, 164, 51, 182, 155, 34, 96, 19]
2146
+ [56]
2147
+ [56, 29, 113, 100, 51, 118, 155, 34, 96, 47]
2148
+ [56, 29, 113, 100, 51]
2149
+ [56, 29, 113, 100, 51, 182, 155, 34, 96, 19]
2150
+ """
2151
+ it = self._dlx_solutions_iterator()
2152
+ B = next(it)
2153
+ while True:
2154
+ yield B
2155
+ try:
2156
+ A, B = B, next(it)
2157
+ except StopIteration:
2158
+ return
2159
+ common_prefix = []
2160
+ for a, b in zip(A, B):
2161
+ if a == b:
2162
+ common_prefix.append(a)
2163
+ else:
2164
+ break
2165
+ yield common_prefix
2166
+
2167
+ def _dlx_incremental_solutions_iterator(self):
2168
+ r"""
2169
+ Return an iterator over the row indices of the incremental
2170
+ solutions.
2171
+
2172
+ Between two incremental solution, either one piece is added or one
2173
+ piece is removed.
2174
+
2175
+ The purpose is to illustrate the backtracking and construct an
2176
+ animation of the evolution of solutions.
2177
+
2178
+ OUTPUT: iterator
2179
+
2180
+ EXAMPLES::
2181
+
2182
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
2183
+ sage: p = Polyomino([(0,0,0)])
2184
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
2185
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
2186
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
2187
+ sage: list(T._dlx_solutions_iterator())
2188
+ [[0, 7, 14], [0, 12, 10], [6, 13, 5], [6, 14, 2], [11, 9, 5], [11, 10, 3]]
2189
+ sage: list(T._dlx_incremental_solutions_iterator())
2190
+ [[0, 7, 14], [0, 7], [0], [0, 12], [0, 12, 10], [0, 12], [0], [], [6], [6, 13], [6, 13, 5], [6, 13], [6], [6, 14], [6, 14, 2], [6, 14], [6], [], [11], [11, 9], [11, 9, 5], [11, 9], [11], [11, 10], [11, 10, 3]]
2191
+
2192
+ ::
2193
+
2194
+ sage: y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color='yellow')
2195
+ sage: T = TilingSolver([y], box=(5,10), reusable=True, reflection=True)
2196
+ sage: for a in T._dlx_solutions_iterator(): a
2197
+ [0, 83, 114, 43, 158, 5, 128, 183, 168, 25]
2198
+ [0, 83, 114, 43, 158, 33, 128, 183, 104, 25]
2199
+ [0, 83, 114, 100, 52, 183, 128, 33, 95, 47]
2200
+ [0, 83, 178, 15, 158, 5, 128, 183, 168, 25]
2201
+ [0, 83, 178, 15, 158, 33, 128, 183, 104, 25]
2202
+ [56, 1, 113, 15, 159, 34, 155, 182, 168, 24]
2203
+ [56, 1, 113, 164, 51, 118, 155, 34, 96, 47]
2204
+ [56, 1, 113, 164, 51, 182, 155, 34, 96, 19]
2205
+ [56, 29, 113, 100, 51, 118, 155, 34, 96, 47]
2206
+ [56, 29, 113, 100, 51, 182, 155, 34, 96, 19]
2207
+ sage: len(list(T._dlx_incremental_solutions_iterator()))
2208
+ 123
2209
+ """
2210
+ it = self._dlx_solutions_iterator()
2211
+ B = next(it)
2212
+ while True:
2213
+ yield B
2214
+ try:
2215
+ A, B = B, next(it)
2216
+ except StopIteration:
2217
+ return
2218
+ common_prefix = 0
2219
+ for a, b in zip(A, B):
2220
+ if a == b:
2221
+ common_prefix += 1
2222
+ else:
2223
+ break
2224
+ for i in range(1, len(A)-common_prefix):
2225
+ yield A[:-i]
2226
+ for j in range(common_prefix, len(B)):
2227
+ yield B[:j]
2228
+
2229
+ def solve(self, partial=None):
2230
+ r"""
2231
+ Return an iterator of list of polyominoes that are an exact cover
2232
+ of the box.
2233
+
2234
+ INPUT:
2235
+
2236
+ - ``partial`` -- string (default: ``None``); whether to
2237
+ include partial (incomplete) solutions. It can be one of the
2238
+ following:
2239
+
2240
+ - ``None`` -- include only complete solution
2241
+ - ``'common_prefix'`` -- common prefix between two consecutive solutions
2242
+ - ``'incremental'`` -- one piece change at a time
2243
+
2244
+ OUTPUT: iterator of list of polyominoes
2245
+
2246
+ EXAMPLES::
2247
+
2248
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
2249
+ sage: p = Polyomino([(0,0,0)])
2250
+ sage: q = Polyomino([(0,0,0), (0,0,1)])
2251
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)])
2252
+ sage: T = TilingSolver([p,q,r], box=(1,1,6))
2253
+ sage: it = T.solve()
2254
+ sage: for p in next(it): p
2255
+ Polyomino: [(0, 0, 0)], Color: gray
2256
+ Polyomino: [(0, 0, 1), (0, 0, 2)], Color: gray
2257
+ Polyomino: [(0, 0, 3), (0, 0, 4), (0, 0, 5)], Color: gray
2258
+ sage: for p in next(it): p
2259
+ Polyomino: [(0, 0, 0)], Color: gray
2260
+ Polyomino: [(0, 0, 1), (0, 0, 2), (0, 0, 3)], Color: gray
2261
+ Polyomino: [(0, 0, 4), (0, 0, 5)], Color: gray
2262
+ sage: for p in next(it): p
2263
+ Polyomino: [(0, 0, 0), (0, 0, 1)], Color: gray
2264
+ Polyomino: [(0, 0, 2), (0, 0, 3), (0, 0, 4)], Color: gray
2265
+ Polyomino: [(0, 0, 5)], Color: gray
2266
+
2267
+ Including the partial solutions::
2268
+
2269
+ sage: it = T.solve(partial='common_prefix')
2270
+ sage: for p in next(it): p
2271
+ Polyomino: [(0, 0, 0)], Color: gray
2272
+ Polyomino: [(0, 0, 1), (0, 0, 2)], Color: gray
2273
+ Polyomino: [(0, 0, 3), (0, 0, 4), (0, 0, 5)], Color: gray
2274
+ sage: for p in next(it): p
2275
+ Polyomino: [(0, 0, 0)], Color: gray
2276
+ sage: for p in next(it): p
2277
+ Polyomino: [(0, 0, 0)], Color: gray
2278
+ Polyomino: [(0, 0, 1), (0, 0, 2), (0, 0, 3)], Color: gray
2279
+ Polyomino: [(0, 0, 4), (0, 0, 5)], Color: gray
2280
+ sage: for p in next(it): p
2281
+ sage: for p in next(it): p
2282
+ Polyomino: [(0, 0, 0), (0, 0, 1)], Color: gray
2283
+ Polyomino: [(0, 0, 2), (0, 0, 3), (0, 0, 4)], Color: gray
2284
+ Polyomino: [(0, 0, 5)], Color: gray
2285
+
2286
+ Colors are preserved when the polyomino can be reused::
2287
+
2288
+ sage: p = Polyomino([(0,0,0)], color='yellow')
2289
+ sage: q = Polyomino([(0,0,0), (0,0,1)], color='yellow')
2290
+ sage: r = Polyomino([(0,0,0), (0,0,1), (0,0,2)], color='yellow')
2291
+ sage: T = TilingSolver([p,q,r], box=(1,1,6), reusable=True)
2292
+ sage: it = T.solve()
2293
+ sage: for p in next(it): p
2294
+ Polyomino: [(0, 0, 0)], Color: yellow
2295
+ Polyomino: [(0, 0, 1)], Color: yellow
2296
+ Polyomino: [(0, 0, 2)], Color: yellow
2297
+ Polyomino: [(0, 0, 3)], Color: yellow
2298
+ Polyomino: [(0, 0, 4)], Color: yellow
2299
+ Polyomino: [(0, 0, 5)], Color: yellow
2300
+
2301
+ TESTS::
2302
+
2303
+ sage: T = TilingSolver([p,q,r], box=(1,1,7))
2304
+ sage: next(T.solve())
2305
+ Traceback (most recent call last):
2306
+ ...
2307
+ StopIteration
2308
+ """
2309
+ if not self.is_suitable():
2310
+ return
2311
+ if partial is None:
2312
+ it = self._dlx_solutions_iterator()
2313
+ elif partial == 'common_prefix':
2314
+ it = self._dlx_common_prefix_solutions_iterator()
2315
+ elif partial == 'incremental':
2316
+ it = self._dlx_incremental_solutions_iterator()
2317
+ else:
2318
+ raise ValueError("Unknown value for partial (=%s)" % partial)
2319
+ for solution in it:
2320
+ yield [self.row_to_polyomino(v) for v in solution]
2321
+
2322
+ def number_of_solutions(self):
2323
+ r"""
2324
+ Return the number of distinct solutions.
2325
+
2326
+ OUTPUT: integer
2327
+
2328
+ EXAMPLES::
2329
+
2330
+ sage: from sage.combinat.tiling import TilingSolver, Polyomino
2331
+ sage: p = Polyomino([(0,0)])
2332
+ sage: q = Polyomino([(0,0), (0,1)])
2333
+ sage: r = Polyomino([(0,0), (0,1), (0,2)])
2334
+ sage: T = TilingSolver([p,q,r], box=(1,6))
2335
+ sage: T.number_of_solutions()
2336
+ 6
2337
+
2338
+ ::
2339
+
2340
+ sage: T = TilingSolver([p,q,r], box=(1,7))
2341
+ sage: T.number_of_solutions()
2342
+ 0
2343
+ """
2344
+ if not self.is_suitable():
2345
+ return 0
2346
+ x = self.dlx_solver()
2347
+ N = 0
2348
+ while x.search() == 1:
2349
+ N += 1
2350
+ return N
2351
+
2352
+ def animate(self, partial=None, stop=None, size=0.75, axes=False):
2353
+ r"""
2354
+ Return an animation of evolving solutions.
2355
+
2356
+ INPUT:
2357
+
2358
+ - ``partial`` -- string (default: ``None``); whether to
2359
+ include partial (incomplete) solutions. It can be one of the
2360
+ following:
2361
+
2362
+ - ``None`` -- include only complete solutions
2363
+ - ``'common_prefix'`` -- common prefix between two consecutive solutions
2364
+ - ``'incremental'`` -- one piece change at a time
2365
+
2366
+ - ``stop`` -- integer (default: ``None``); number of frames
2367
+
2368
+ - ``size`` -- number (default: ``0.75``); the size of each
2369
+ ``1 \times 1`` square. This does a homothety with respect
2370
+ to the center of each polyomino.
2371
+
2372
+ - ``axes`` -- boolean (default: ``False``); whether the x and
2373
+ y axes are shown
2374
+
2375
+ EXAMPLES::
2376
+
2377
+ sage: from sage.combinat.tiling import Polyomino, TilingSolver
2378
+ sage: y = Polyomino([(0,0),(1,0),(2,0),(3,0),(2,1)], color='cyan')
2379
+ sage: T = TilingSolver([y], box=(5,10), reusable=True, reflection=True)
2380
+ sage: a = T.animate() # needs sage.plot
2381
+ sage: a # long time, optional - imagemagick, needs sage.plot
2382
+ Animation with 10 frames
2383
+
2384
+ Include partial solutions (common prefix between two consecutive
2385
+ solutions)::
2386
+
2387
+ sage: a = T.animate('common_prefix') # needs sage.plot
2388
+ sage: a # long time, optional - imagemagick, needs sage.plot
2389
+ Animation with 19 frames
2390
+
2391
+ Incremental solutions (one piece removed or added at a time)::
2392
+
2393
+ sage: a = T.animate('incremental') # long time (2s) # needs sage.plot
2394
+ sage: a # long time (2s), optional - imagemagick, needs sage.plot
2395
+ Animation with 123 frames
2396
+
2397
+ ::
2398
+
2399
+ sage: a.show() # long time, optional - imagemagick, needs sage.plot
2400
+
2401
+ The ``show`` function takes arguments to specify the delay between
2402
+ frames (measured in hundredths of a second, default value 20) and
2403
+ the number of iterations (default: 0, which means to iterate
2404
+ forever). To iterate 4 times with half a second between each frame::
2405
+
2406
+ sage: a.show(delay=50, iterations=4) # long time, optional - imagemagick, needs sage.plot
2407
+
2408
+ Limit the number of frames::
2409
+
2410
+ sage: a = T.animate('incremental', stop=13); a # not tested # needs sage.plot
2411
+ Animation with 13 frames
2412
+ """
2413
+ dimension = self._box._dimension
2414
+ if dimension == 2:
2415
+ from sage.plot.graphics import Graphics
2416
+ from sage.plot.animate import Animation
2417
+ it = self.solve(partial=partial)
2418
+ it = itertools.islice(it, stop)
2419
+ L = [sum([piece.show2d(size)
2420
+ for piece in solution], Graphics()) for solution in it]
2421
+ (xmin,ymin), (xmax,ymax) = self._box.bounding_box()
2422
+ xmax = xmax+0.5
2423
+ ymax = ymax+0.5
2424
+ a = Animation(L, xmin=xmin-0.5, ymin=ymin-0.5,
2425
+ xmax=xmax, ymax=ymax, aspect_ratio=1, axes=axes)
2426
+ return a
2427
+ elif dimension == 3:
2428
+ raise NotImplementedError("3d Animation must be implemented "
2429
+ "in Jmol first")
2430
+ else:
2431
+ raise NotImplementedError("Dimension must be 2 or 3 in order "
2432
+ "to make an animation")