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