passagemath-combinat 10.6.42__cp314-cp314t-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_combinat/__init__.py +3 -0
- passagemath_combinat-10.6.42.dist-info/DELVEWHEEL +2 -0
- passagemath_combinat-10.6.42.dist-info/METADATA +160 -0
- passagemath_combinat-10.6.42.dist-info/RECORD +401 -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-10-3a5f019e2510aeaad918cab2b57a689d.dll +0 -0
- passagemath_combinat.libs/libsymmetrica-3-7dcf900932804d0df5fd0919b4668720.dll +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 +44 -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.cp314t-win_amd64.pyd +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.cp314t-win_amd64.pyd +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.cp314t-win_amd64.pyd +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.cp314t-win_amd64.pyd +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.cp314t-win_amd64.pyd +0 -0
- sage/combinat/debruijn_sequence.pyx +355 -0
- sage/combinat/decorated_permutation.py +270 -0
- sage/combinat/degree_sequences.cp314t-win_amd64.pyd +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.cp314t-win_amd64.pyd +0 -0
- sage/combinat/expnums.pyx +148 -0
- sage/combinat/fast_vector_partitions.cp314t-win_amd64.pyd +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.cp314t-win_amd64.pyd +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.cp314t-win_amd64.pyd +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.cp314t-win_amd64.pyd +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.cp314t-win_amd64.pyd +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.cp314t-win_amd64.pyd +0 -0
- sage/combinat/words/word_char.pyx +847 -0
- sage/combinat/words/word_datatypes.cp314t-win_amd64.pyd +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.cp314t-win_amd64.pyd +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.cp314t-win_amd64.pyd +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.cp314t-win_amd64.pyd +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.cp314t-win_amd64.pyd +0 -0
- sage/sat/solvers/satsolver.pxd +3 -0
- sage/sat/solvers/satsolver.pyx +405 -0
|
@@ -0,0 +1,2912 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-combinat
|
|
2
|
+
# sage.doctest: needs sage.combinat sage.groups sage.modules
|
|
3
|
+
r"""
|
|
4
|
+
Latin squares
|
|
5
|
+
|
|
6
|
+
A *latin square* of order `n` is an `n \times n` array such that
|
|
7
|
+
each symbol `s \in \{ 0, 1, \dots, n-1\}` appears precisely once in each
|
|
8
|
+
row, and precisely once in each column. A *partial latin square* of
|
|
9
|
+
order `n` is an `n \times n` array such that
|
|
10
|
+
each symbol `s \in \{ 0, 1, \dots, n-1\}` appears at most once in each
|
|
11
|
+
row, and at most once in each column. Empty cells are denoted by `-1`.
|
|
12
|
+
A latin square `L` is a
|
|
13
|
+
*completion* of a partial latin square `P` if `P \subseteq L`. If
|
|
14
|
+
`P` completes to just `L` then `P` *has unique completion*.
|
|
15
|
+
|
|
16
|
+
A *latin bitrade* `(T_1,\, T_2)` is a pair of partial
|
|
17
|
+
latin squares such that:
|
|
18
|
+
|
|
19
|
+
#. `\{ (i,\,j) \mid (i,\,j,\,k) \in T_1 \text{ for some symbol }k \}
|
|
20
|
+
= \{ (i,\,j) \mid (i,\,j,\,k') \in T_2 \text{ for some symbol }k' \};`
|
|
21
|
+
|
|
22
|
+
#. for each `(i,\,j,\,k) \in T_1` and `(i,\,j,\,k') \in T_2`,
|
|
23
|
+
`k \neq k'`;
|
|
24
|
+
|
|
25
|
+
#. the symbols appearing in row `i` of `T_1` are the same as those of
|
|
26
|
+
row `i` of `T_2`; the symbols appearing in column `j` of `T_1` are
|
|
27
|
+
the same as those of column `j` of `T_2`.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
Intuitively speaking, a bitrade gives the difference between two latin
|
|
31
|
+
squares, so if `(T_1,\, T_2)` is a bitrade
|
|
32
|
+
for the pair of latin squares `(L_1,\, L_2)`, then
|
|
33
|
+
`L1 = (L2 \setminus T_1) \cup T_2`
|
|
34
|
+
and
|
|
35
|
+
`L2 = (L1 \setminus T_2) \cup T_1`.
|
|
36
|
+
|
|
37
|
+
This file contains
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
#. LatinSquare class definition;
|
|
41
|
+
|
|
42
|
+
#. some named latin squares (back circulant, forward circulant, abelian
|
|
43
|
+
`2`-group);
|
|
44
|
+
|
|
45
|
+
#. methods :meth:`is_partial_latin_square` and :meth:`is_latin_square` to test
|
|
46
|
+
if a :class:`LatinSquare` object satisfies the definition of a latin square
|
|
47
|
+
or partial latin square, respectively;
|
|
48
|
+
|
|
49
|
+
#. tests for completion and unique completion (these use the C++
|
|
50
|
+
implementation of Knuth's dancing links algorithm to solve the
|
|
51
|
+
problem as a instance of `0-1` matrix exact cover);
|
|
52
|
+
|
|
53
|
+
#. functions for calculating the `\tau_i` representation of a bitrade
|
|
54
|
+
and the genus of the associated hypermap embedding;
|
|
55
|
+
|
|
56
|
+
#. Markov chain of Jacobson and Matthews (1996) for generating latin
|
|
57
|
+
squares uniformly at random (provides a generator interface);
|
|
58
|
+
|
|
59
|
+
#. a few examples of `\tau_i` representations of bitrades constructed
|
|
60
|
+
from the action of a group on itself by right multiplication,
|
|
61
|
+
functions for converting to a pair of :class:`LatinSquare` objects.
|
|
62
|
+
|
|
63
|
+
EXAMPLES::
|
|
64
|
+
|
|
65
|
+
sage: from sage.combinat.matrices.latin import *
|
|
66
|
+
sage: B = back_circulant(5)
|
|
67
|
+
sage: B
|
|
68
|
+
[0 1 2 3 4]
|
|
69
|
+
[1 2 3 4 0]
|
|
70
|
+
[2 3 4 0 1]
|
|
71
|
+
[3 4 0 1 2]
|
|
72
|
+
[4 0 1 2 3]
|
|
73
|
+
sage: B.is_latin_square()
|
|
74
|
+
True
|
|
75
|
+
sage: B[0, 1] = 0
|
|
76
|
+
sage: B.is_latin_square()
|
|
77
|
+
False
|
|
78
|
+
|
|
79
|
+
sage: (a, b, c, G) = alternating_group_bitrade_generators(1)
|
|
80
|
+
sage: (T1, T2) = bitrade_from_group(a, b, c, G)
|
|
81
|
+
sage: T1
|
|
82
|
+
[ 0 -1 3 1]
|
|
83
|
+
[-1 1 0 2]
|
|
84
|
+
[ 1 3 2 -1]
|
|
85
|
+
[ 2 0 -1 3]
|
|
86
|
+
sage: T2
|
|
87
|
+
[ 1 -1 0 3]
|
|
88
|
+
[-1 0 2 1]
|
|
89
|
+
[ 2 1 3 -1]
|
|
90
|
+
[ 0 3 -1 2]
|
|
91
|
+
sage: T1.nr_filled_cells()
|
|
92
|
+
12
|
|
93
|
+
sage: genus(T1, T2)
|
|
94
|
+
1
|
|
95
|
+
|
|
96
|
+
.. TODO::
|
|
97
|
+
|
|
98
|
+
#. Latin squares with symbols from a ring instead of the integers
|
|
99
|
+
`\{ 0, 1, \dots, n-1 \}`.
|
|
100
|
+
|
|
101
|
+
#. Isotopism testing of latin squares and bitrades via graph
|
|
102
|
+
isomorphism (nauty?).
|
|
103
|
+
|
|
104
|
+
#. Combinatorial constructions for bitrades.
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
AUTHORS:
|
|
108
|
+
|
|
109
|
+
- Carlo Hamalainen (2008-03-23): initial version
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
TESTS::
|
|
113
|
+
|
|
114
|
+
sage: L = elementary_abelian_2group(3)
|
|
115
|
+
sage: L == loads(dumps(L))
|
|
116
|
+
True
|
|
117
|
+
"""
|
|
118
|
+
# ****************************************************************************
|
|
119
|
+
# Copyright (C) 2008 Carlo Hamalainen <carlo.hamalainen@gmail.com>,
|
|
120
|
+
#
|
|
121
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
|
122
|
+
#
|
|
123
|
+
# This code is distributed in the hope that it will be useful,
|
|
124
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
125
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
126
|
+
# General Public License for more details.
|
|
127
|
+
#
|
|
128
|
+
# The full text of the GPL is available at:
|
|
129
|
+
#
|
|
130
|
+
# https://www.gnu.org/licenses/
|
|
131
|
+
# ****************************************************************************
|
|
132
|
+
|
|
133
|
+
from sage.matrix.constructor import matrix
|
|
134
|
+
from sage.rings.integer_ring import ZZ
|
|
135
|
+
from sage.rings.integer import Integer
|
|
136
|
+
from sage.matrix.matrix_integer_dense import Matrix_integer_dense
|
|
137
|
+
from sage.groups.perm_gps.permgroup_element import PermutationGroupElement
|
|
138
|
+
from sage.groups.perm_gps.constructor import PermutationGroupElement as PermutationConstructor
|
|
139
|
+
from sage.libs.gap.libgap import libgap
|
|
140
|
+
from sage.libs.gap.element import GapElement
|
|
141
|
+
from sage.combinat.permutation import Permutation
|
|
142
|
+
from sage.groups.perm_gps.permgroup import PermutationGroup
|
|
143
|
+
from sage.arith.misc import is_prime
|
|
144
|
+
from sage.rings.finite_rings.finite_field_constructor import FiniteField
|
|
145
|
+
from sage.misc.flatten import flatten
|
|
146
|
+
|
|
147
|
+
from .dlxcpp import DLXCPP
|
|
148
|
+
from functools import reduce
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class LatinSquare:
|
|
152
|
+
def __init__(self, *args):
|
|
153
|
+
"""
|
|
154
|
+
Latin squares.
|
|
155
|
+
|
|
156
|
+
This class implements a latin square of order n with rows and
|
|
157
|
+
columns indexed by the set 0, 1, ..., n-1 and symbols from the same
|
|
158
|
+
set. The underlying latin square is a matrix(ZZ, n, n). If L is a
|
|
159
|
+
latin square, then the cell at row r, column c is empty if and only
|
|
160
|
+
if L[r, c] < 0. In this way we allow partial latin squares and can
|
|
161
|
+
speak of completions to latin squares, etc.
|
|
162
|
+
|
|
163
|
+
There are two ways to declare a latin square:
|
|
164
|
+
|
|
165
|
+
Empty latin square of order n::
|
|
166
|
+
|
|
167
|
+
sage: n = 3
|
|
168
|
+
sage: L = LatinSquare(n)
|
|
169
|
+
sage: L
|
|
170
|
+
[-1 -1 -1]
|
|
171
|
+
[-1 -1 -1]
|
|
172
|
+
[-1 -1 -1]
|
|
173
|
+
|
|
174
|
+
Latin square from a matrix::
|
|
175
|
+
|
|
176
|
+
sage: M = matrix(ZZ, [[0, 1], [2, 3]])
|
|
177
|
+
sage: LatinSquare(M)
|
|
178
|
+
[0 1]
|
|
179
|
+
[2 3]
|
|
180
|
+
"""
|
|
181
|
+
if len(args) == 1 and isinstance(args[0], (Integer, int)):
|
|
182
|
+
self.square = matrix(ZZ, args[0], args[0])
|
|
183
|
+
self.clear_cells()
|
|
184
|
+
elif len(args) == 2 and all(isinstance(a, (Integer, int))
|
|
185
|
+
for a in args):
|
|
186
|
+
self.square = matrix(ZZ, args[0], args[1])
|
|
187
|
+
self.clear_cells()
|
|
188
|
+
elif len(args) == 1 and isinstance(args[0], Matrix_integer_dense):
|
|
189
|
+
self.square = args[0]
|
|
190
|
+
else:
|
|
191
|
+
raise TypeError("bad input for latin square")
|
|
192
|
+
|
|
193
|
+
def dumps(self):
|
|
194
|
+
"""
|
|
195
|
+
Since the latin square class does not hold any other private
|
|
196
|
+
variables we just call dumps on self.square:
|
|
197
|
+
|
|
198
|
+
EXAMPLES::
|
|
199
|
+
|
|
200
|
+
sage: from sage.combinat.matrices.latin import *
|
|
201
|
+
sage: back_circulant(2) == loads(dumps(back_circulant(2)))
|
|
202
|
+
True
|
|
203
|
+
"""
|
|
204
|
+
from sage.misc.persist import dumps
|
|
205
|
+
return dumps(self.square)
|
|
206
|
+
|
|
207
|
+
def __str__(self):
|
|
208
|
+
"""
|
|
209
|
+
The string representation of a latin square is the same as the
|
|
210
|
+
underlying matrix.
|
|
211
|
+
|
|
212
|
+
EXAMPLES::
|
|
213
|
+
|
|
214
|
+
sage: print(LatinSquare(matrix(ZZ, [[0, 1], [2, 3]])).__str__())
|
|
215
|
+
[0 1]
|
|
216
|
+
[2 3]
|
|
217
|
+
"""
|
|
218
|
+
return str(self.square)
|
|
219
|
+
|
|
220
|
+
def __repr__(self):
|
|
221
|
+
"""
|
|
222
|
+
The representation of a latin square is the same as the underlying
|
|
223
|
+
matrix.
|
|
224
|
+
|
|
225
|
+
EXAMPLES::
|
|
226
|
+
|
|
227
|
+
sage: print(LatinSquare(matrix(ZZ, [[0, 1], [2, 3]])).__repr__())
|
|
228
|
+
[0 1]
|
|
229
|
+
[2 3]
|
|
230
|
+
"""
|
|
231
|
+
return repr(self.square)
|
|
232
|
+
|
|
233
|
+
def __getitem__(self, rc):
|
|
234
|
+
"""
|
|
235
|
+
If L is a LatinSquare then this method allows us to evaluate L[r,
|
|
236
|
+
c].
|
|
237
|
+
|
|
238
|
+
EXAMPLES::
|
|
239
|
+
|
|
240
|
+
sage: from sage.combinat.matrices.latin import *
|
|
241
|
+
sage: B = back_circulant(3)
|
|
242
|
+
sage: B[1, 1]
|
|
243
|
+
2
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
r = rc[0]
|
|
247
|
+
c = rc[1]
|
|
248
|
+
|
|
249
|
+
return self.square[r, c]
|
|
250
|
+
|
|
251
|
+
def __setitem__(self, rc, val):
|
|
252
|
+
"""
|
|
253
|
+
If L is a LatinSquare then this method allows us to set L[r, c].
|
|
254
|
+
|
|
255
|
+
EXAMPLES::
|
|
256
|
+
|
|
257
|
+
sage: from sage.combinat.matrices.latin import *
|
|
258
|
+
sage: B = back_circulant(3)
|
|
259
|
+
sage: B[1, 1] = 10
|
|
260
|
+
sage: B[1, 1]
|
|
261
|
+
10
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
r = rc[0]
|
|
265
|
+
c = rc[1]
|
|
266
|
+
|
|
267
|
+
self.square[r, c] = val
|
|
268
|
+
|
|
269
|
+
def set_immutable(self):
|
|
270
|
+
"""
|
|
271
|
+
A latin square is immutable if the underlying matrix is immutable.
|
|
272
|
+
|
|
273
|
+
EXAMPLES::
|
|
274
|
+
|
|
275
|
+
sage: L = LatinSquare(matrix(ZZ, [[0, 1], [2, 3]]))
|
|
276
|
+
sage: L.set_immutable()
|
|
277
|
+
sage: {L : 0} # this would fail without set_immutable()
|
|
278
|
+
{[0 1]
|
|
279
|
+
[2 3]: 0}
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
self.square.set_immutable()
|
|
283
|
+
|
|
284
|
+
def __hash__(self):
|
|
285
|
+
"""
|
|
286
|
+
The hash of a latin square is precisely the hash of the underlying
|
|
287
|
+
matrix.
|
|
288
|
+
|
|
289
|
+
EXAMPLES::
|
|
290
|
+
|
|
291
|
+
sage: L = LatinSquare(matrix(ZZ, [[0, 1], [2, 3]]))
|
|
292
|
+
sage: L.set_immutable()
|
|
293
|
+
sage: L.__hash__()
|
|
294
|
+
1677951251422179082 # 64-bit
|
|
295
|
+
-479138038 # 32-bit
|
|
296
|
+
"""
|
|
297
|
+
return hash(self.square)
|
|
298
|
+
|
|
299
|
+
def __eq__(self, Q):
|
|
300
|
+
"""
|
|
301
|
+
Two latin squares are equal if the underlying matrices are equal.
|
|
302
|
+
|
|
303
|
+
EXAMPLES::
|
|
304
|
+
|
|
305
|
+
sage: A = LatinSquare(matrix(ZZ, [[0, 1], [2, 3]]))
|
|
306
|
+
sage: B = LatinSquare(matrix(ZZ, [[0, 4], [2, 3]]))
|
|
307
|
+
sage: A == B
|
|
308
|
+
False
|
|
309
|
+
sage: B[0, 1] = 1
|
|
310
|
+
sage: A == B
|
|
311
|
+
True
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
return self.square == Q.square
|
|
315
|
+
|
|
316
|
+
def __copy__(self):
|
|
317
|
+
"""
|
|
318
|
+
To copy a latin square we must copy the underlying matrix.
|
|
319
|
+
|
|
320
|
+
EXAMPLES::
|
|
321
|
+
|
|
322
|
+
sage: A = LatinSquare(matrix(ZZ, [[0, 1], [2, 3]]))
|
|
323
|
+
sage: B = copy(A)
|
|
324
|
+
sage: B
|
|
325
|
+
[0 1]
|
|
326
|
+
[2 3]
|
|
327
|
+
"""
|
|
328
|
+
C = LatinSquare(self.square.nrows(), self.square.ncols())
|
|
329
|
+
from copy import copy
|
|
330
|
+
C.square = copy(self.square)
|
|
331
|
+
return C
|
|
332
|
+
|
|
333
|
+
def clear_cells(self):
|
|
334
|
+
"""
|
|
335
|
+
Mark every cell in ``self`` as being empty.
|
|
336
|
+
|
|
337
|
+
EXAMPLES::
|
|
338
|
+
|
|
339
|
+
sage: A = LatinSquare(matrix(ZZ, [[0, 1], [2, 3]]))
|
|
340
|
+
sage: A.clear_cells()
|
|
341
|
+
sage: A
|
|
342
|
+
[-1 -1]
|
|
343
|
+
[-1 -1]
|
|
344
|
+
"""
|
|
345
|
+
for r in range(self.square.nrows()):
|
|
346
|
+
for c in range(self.square.ncols()):
|
|
347
|
+
self.square[r, c] = -1
|
|
348
|
+
|
|
349
|
+
def nrows(self):
|
|
350
|
+
"""
|
|
351
|
+
Number of rows in the latin square.
|
|
352
|
+
|
|
353
|
+
EXAMPLES::
|
|
354
|
+
|
|
355
|
+
sage: LatinSquare(3).nrows()
|
|
356
|
+
3
|
|
357
|
+
"""
|
|
358
|
+
return self.square.nrows()
|
|
359
|
+
|
|
360
|
+
def ncols(self):
|
|
361
|
+
"""
|
|
362
|
+
Number of columns in the latin square.
|
|
363
|
+
|
|
364
|
+
EXAMPLES::
|
|
365
|
+
|
|
366
|
+
sage: LatinSquare(3).ncols()
|
|
367
|
+
3
|
|
368
|
+
"""
|
|
369
|
+
return self.square.ncols()
|
|
370
|
+
|
|
371
|
+
def row(self, x):
|
|
372
|
+
"""
|
|
373
|
+
Return row x of the latin square.
|
|
374
|
+
|
|
375
|
+
EXAMPLES::
|
|
376
|
+
|
|
377
|
+
sage: from sage.combinat.matrices.latin import *
|
|
378
|
+
sage: back_circulant(3).row(0)
|
|
379
|
+
(0, 1, 2)
|
|
380
|
+
"""
|
|
381
|
+
return self.square.row(x)
|
|
382
|
+
|
|
383
|
+
def column(self, x):
|
|
384
|
+
"""
|
|
385
|
+
Return column x of the latin square.
|
|
386
|
+
|
|
387
|
+
EXAMPLES::
|
|
388
|
+
|
|
389
|
+
sage: from sage.combinat.matrices.latin import *
|
|
390
|
+
sage: back_circulant(3).column(0)
|
|
391
|
+
(0, 1, 2)
|
|
392
|
+
"""
|
|
393
|
+
return self.square.column(x)
|
|
394
|
+
|
|
395
|
+
def list(self):
|
|
396
|
+
"""
|
|
397
|
+
Convert the latin square into a list, in a row-wise manner.
|
|
398
|
+
|
|
399
|
+
EXAMPLES::
|
|
400
|
+
|
|
401
|
+
sage: from sage.combinat.matrices.latin import *
|
|
402
|
+
sage: back_circulant(3).list()
|
|
403
|
+
[0, 1, 2, 1, 2, 0, 2, 0, 1]
|
|
404
|
+
"""
|
|
405
|
+
return self.square.list()
|
|
406
|
+
|
|
407
|
+
def nr_filled_cells(self):
|
|
408
|
+
"""
|
|
409
|
+
Return the number of filled cells (i.e. cells with a positive
|
|
410
|
+
value) in the partial latin square ``self``.
|
|
411
|
+
|
|
412
|
+
EXAMPLES::
|
|
413
|
+
|
|
414
|
+
sage: from sage.combinat.matrices.latin import *
|
|
415
|
+
sage: LatinSquare(matrix([[0, -1], [-1, 0]])).nr_filled_cells()
|
|
416
|
+
2
|
|
417
|
+
"""
|
|
418
|
+
s = 0
|
|
419
|
+
for r in range(self.nrows()):
|
|
420
|
+
for c in range(self.ncols()):
|
|
421
|
+
if self[r, c] >= 0:
|
|
422
|
+
s += 1
|
|
423
|
+
return s
|
|
424
|
+
|
|
425
|
+
def actual_row_col_sym_sizes(self):
|
|
426
|
+
"""
|
|
427
|
+
Bitrades sometimes end up in partial latin squares with unused
|
|
428
|
+
rows, columns, or symbols. This function works out the actual
|
|
429
|
+
number of used rows, columns, and symbols.
|
|
430
|
+
|
|
431
|
+
.. warning::
|
|
432
|
+
|
|
433
|
+
We assume that the unused rows/columns occur in the lower
|
|
434
|
+
right of self, and that the used symbols are in the range
|
|
435
|
+
{0, 1, ..., m} (no holes in that list).
|
|
436
|
+
|
|
437
|
+
EXAMPLES::
|
|
438
|
+
|
|
439
|
+
sage: from sage.combinat.matrices.latin import *
|
|
440
|
+
sage: B = back_circulant(3)
|
|
441
|
+
sage: B[0,2] = B[1,2] = B[2,2] = -1
|
|
442
|
+
sage: B[0,0] = B[2,1] = -1
|
|
443
|
+
sage: B
|
|
444
|
+
[-1 1 -1]
|
|
445
|
+
[ 1 2 -1]
|
|
446
|
+
[ 2 -1 -1]
|
|
447
|
+
sage: B.actual_row_col_sym_sizes()
|
|
448
|
+
(3, 2, 2)
|
|
449
|
+
"""
|
|
450
|
+
row_max = self.nrows()
|
|
451
|
+
col_max = self.ncols()
|
|
452
|
+
sym_max = self.nr_distinct_symbols()
|
|
453
|
+
|
|
454
|
+
while self.is_empty_row(row_max-1):
|
|
455
|
+
row_max -= 1
|
|
456
|
+
while self.is_empty_column(col_max-1):
|
|
457
|
+
col_max -= 1
|
|
458
|
+
|
|
459
|
+
return row_max, col_max, sym_max
|
|
460
|
+
|
|
461
|
+
def is_empty_column(self, c):
|
|
462
|
+
"""
|
|
463
|
+
Check if column c of the partial latin square ``self`` is empty.
|
|
464
|
+
|
|
465
|
+
EXAMPLES::
|
|
466
|
+
|
|
467
|
+
sage: from sage.combinat.matrices.latin import *
|
|
468
|
+
sage: L = back_circulant(4)
|
|
469
|
+
sage: L.is_empty_column(0)
|
|
470
|
+
False
|
|
471
|
+
sage: L[0,0] = L[1,0] = L[2,0] = L[3,0] = -1
|
|
472
|
+
sage: L.is_empty_column(0)
|
|
473
|
+
True
|
|
474
|
+
"""
|
|
475
|
+
return list(set(self.column(c))) == [-1]
|
|
476
|
+
|
|
477
|
+
def is_empty_row(self, r):
|
|
478
|
+
"""
|
|
479
|
+
Check if row r of the partial latin square ``self`` is empty.
|
|
480
|
+
|
|
481
|
+
EXAMPLES::
|
|
482
|
+
|
|
483
|
+
sage: from sage.combinat.matrices.latin import *
|
|
484
|
+
sage: L = back_circulant(4)
|
|
485
|
+
sage: L.is_empty_row(0)
|
|
486
|
+
False
|
|
487
|
+
sage: L[0,0] = L[0,1] = L[0,2] = L[0,3] = -1
|
|
488
|
+
sage: L.is_empty_row(0)
|
|
489
|
+
True
|
|
490
|
+
"""
|
|
491
|
+
return list(set(self.row(r))) == [-1]
|
|
492
|
+
|
|
493
|
+
def nr_distinct_symbols(self):
|
|
494
|
+
"""
|
|
495
|
+
Return the number of distinct symbols in the partial latin square
|
|
496
|
+
``self``.
|
|
497
|
+
|
|
498
|
+
EXAMPLES::
|
|
499
|
+
|
|
500
|
+
sage: from sage.combinat.matrices.latin import *
|
|
501
|
+
sage: back_circulant(5).nr_distinct_symbols()
|
|
502
|
+
5
|
|
503
|
+
sage: L = LatinSquare(10)
|
|
504
|
+
sage: L.nr_distinct_symbols()
|
|
505
|
+
0
|
|
506
|
+
sage: L[0, 0] = 0
|
|
507
|
+
sage: L[0, 1] = 1
|
|
508
|
+
sage: L.nr_distinct_symbols()
|
|
509
|
+
2
|
|
510
|
+
"""
|
|
511
|
+
symbols = set(flatten([list(x) for x in list(self.square)]))
|
|
512
|
+
return sum(1 for x in symbols if x >= 0)
|
|
513
|
+
|
|
514
|
+
def apply_isotopism(self, row_perm, col_perm, sym_perm):
|
|
515
|
+
"""
|
|
516
|
+
An isotopism is a permutation of the rows, columns, and symbols of
|
|
517
|
+
a partial latin square ``self``. Use isotopism() to convert a tuple
|
|
518
|
+
(indexed from 0) to a Permutation object.
|
|
519
|
+
|
|
520
|
+
EXAMPLES::
|
|
521
|
+
|
|
522
|
+
sage: from sage.combinat.matrices.latin import *
|
|
523
|
+
sage: B = back_circulant(5)
|
|
524
|
+
sage: B
|
|
525
|
+
[0 1 2 3 4]
|
|
526
|
+
[1 2 3 4 0]
|
|
527
|
+
[2 3 4 0 1]
|
|
528
|
+
[3 4 0 1 2]
|
|
529
|
+
[4 0 1 2 3]
|
|
530
|
+
sage: alpha = isotopism((0,1,2,3,4))
|
|
531
|
+
sage: beta = isotopism((1,0,2,3,4))
|
|
532
|
+
sage: gamma = isotopism((2,1,0,3,4))
|
|
533
|
+
sage: B.apply_isotopism(alpha, beta, gamma)
|
|
534
|
+
[3 4 2 0 1]
|
|
535
|
+
[0 2 3 1 4]
|
|
536
|
+
[1 3 0 4 2]
|
|
537
|
+
[4 0 1 2 3]
|
|
538
|
+
[2 1 4 3 0]
|
|
539
|
+
"""
|
|
540
|
+
Q = LatinSquare(self.nrows(), self.ncols())
|
|
541
|
+
|
|
542
|
+
for r in range(self.nrows()):
|
|
543
|
+
for c in range(self.ncols()):
|
|
544
|
+
try:
|
|
545
|
+
if self[r, c] < 0:
|
|
546
|
+
s2 = -1
|
|
547
|
+
else:
|
|
548
|
+
s2 = sym_perm[self[r, c]] - 1
|
|
549
|
+
except IndexError:
|
|
550
|
+
s2 = self[r, c] # we must be leaving the symbol fixed?
|
|
551
|
+
|
|
552
|
+
Q[row_perm[r]-1, col_perm[c]-1] = s2
|
|
553
|
+
|
|
554
|
+
return Q
|
|
555
|
+
|
|
556
|
+
def filled_cells_map(self):
|
|
557
|
+
"""
|
|
558
|
+
Number the filled cells of ``self`` with integers from {1, 2, 3, ...}.
|
|
559
|
+
|
|
560
|
+
INPUT:
|
|
561
|
+
|
|
562
|
+
- ``self`` -- partial latin square ``self`` (empty cells
|
|
563
|
+
have negative values)
|
|
564
|
+
|
|
565
|
+
OUTPUT:
|
|
566
|
+
|
|
567
|
+
A dictionary ``cells_map`` where ``cells_map[(i,j)] = m`` means that
|
|
568
|
+
``(i,j)`` is the ``m``-th filled cell in ``P``,
|
|
569
|
+
while ``cells_map[m] = (i,j)``.
|
|
570
|
+
|
|
571
|
+
EXAMPLES::
|
|
572
|
+
|
|
573
|
+
sage: from sage.combinat.matrices.latin import *
|
|
574
|
+
sage: (a, b, c, G) = alternating_group_bitrade_generators(1)
|
|
575
|
+
sage: (T1, T2) = bitrade_from_group(a, b, c, G)
|
|
576
|
+
sage: D = T1.filled_cells_map()
|
|
577
|
+
sage: {i: v for i,v in D.items() if i in ZZ}
|
|
578
|
+
{1: (0, 0),
|
|
579
|
+
2: (0, 2),
|
|
580
|
+
3: (0, 3),
|
|
581
|
+
4: (1, 1),
|
|
582
|
+
5: (1, 2),
|
|
583
|
+
6: (1, 3),
|
|
584
|
+
7: (2, 0),
|
|
585
|
+
8: (2, 1),
|
|
586
|
+
9: (2, 2),
|
|
587
|
+
10: (3, 0),
|
|
588
|
+
11: (3, 1),
|
|
589
|
+
12: (3, 3)}
|
|
590
|
+
sage: {i: v for i,v in D.items() if i not in ZZ}
|
|
591
|
+
{(0, 0): 1,
|
|
592
|
+
(0, 2): 2,
|
|
593
|
+
(0, 3): 3,
|
|
594
|
+
(1, 1): 4,
|
|
595
|
+
(1, 2): 5,
|
|
596
|
+
(1, 3): 6,
|
|
597
|
+
(2, 0): 7,
|
|
598
|
+
(2, 1): 8,
|
|
599
|
+
(2, 2): 9,
|
|
600
|
+
(3, 0): 10,
|
|
601
|
+
(3, 1): 11,
|
|
602
|
+
(3, 3): 12}
|
|
603
|
+
"""
|
|
604
|
+
cells_map = {}
|
|
605
|
+
k = 1
|
|
606
|
+
|
|
607
|
+
for r in range(self.nrows()):
|
|
608
|
+
for c in range(self.ncols()):
|
|
609
|
+
e = self[r, c]
|
|
610
|
+
|
|
611
|
+
if e < 0:
|
|
612
|
+
continue
|
|
613
|
+
|
|
614
|
+
cells_map[(r, c)] = k
|
|
615
|
+
cells_map[k] = (r, c)
|
|
616
|
+
|
|
617
|
+
k += 1
|
|
618
|
+
|
|
619
|
+
return cells_map
|
|
620
|
+
|
|
621
|
+
def top_left_empty_cell(self):
|
|
622
|
+
"""
|
|
623
|
+
Return the least ``[r, c]`` such that ``self[r, c]`` is an empty cell.
|
|
624
|
+
If all cells are filled then we return ``None``.
|
|
625
|
+
|
|
626
|
+
INPUT:
|
|
627
|
+
|
|
628
|
+
- ``self`` -- LatinSquare
|
|
629
|
+
|
|
630
|
+
EXAMPLES::
|
|
631
|
+
|
|
632
|
+
sage: from sage.combinat.matrices.latin import *
|
|
633
|
+
sage: B = back_circulant(5)
|
|
634
|
+
sage: B[3, 4] = -1
|
|
635
|
+
sage: B.top_left_empty_cell()
|
|
636
|
+
[3, 4]
|
|
637
|
+
"""
|
|
638
|
+
for r in range(self.nrows()):
|
|
639
|
+
for c in range(self.ncols()):
|
|
640
|
+
if self[r, c] < 0:
|
|
641
|
+
return [r, c]
|
|
642
|
+
|
|
643
|
+
return None
|
|
644
|
+
|
|
645
|
+
def is_partial_latin_square(self):
|
|
646
|
+
"""
|
|
647
|
+
``self`` is a partial latin square if it is an n by n matrix, and each
|
|
648
|
+
symbol in [0, 1, ..., n-1] appears at most once in each row, and at
|
|
649
|
+
most once in each column.
|
|
650
|
+
|
|
651
|
+
EXAMPLES::
|
|
652
|
+
|
|
653
|
+
sage: from sage.combinat.matrices.latin import *
|
|
654
|
+
sage: LatinSquare(4).is_partial_latin_square()
|
|
655
|
+
True
|
|
656
|
+
sage: back_circulant(3).gcs().is_partial_latin_square()
|
|
657
|
+
True
|
|
658
|
+
sage: back_circulant(6).is_partial_latin_square()
|
|
659
|
+
True
|
|
660
|
+
"""
|
|
661
|
+
|
|
662
|
+
assert self.nrows() == self.ncols()
|
|
663
|
+
|
|
664
|
+
n = self.nrows()
|
|
665
|
+
|
|
666
|
+
for r in range(n):
|
|
667
|
+
vals_in_row = {}
|
|
668
|
+
|
|
669
|
+
for c in range(n):
|
|
670
|
+
e = self[r, c]
|
|
671
|
+
|
|
672
|
+
if e < 0:
|
|
673
|
+
continue
|
|
674
|
+
|
|
675
|
+
# Entry out of range 0, 1, ..., n-1:
|
|
676
|
+
if e >= n:
|
|
677
|
+
return False
|
|
678
|
+
|
|
679
|
+
# Entry has already appeared in this row:
|
|
680
|
+
if e in vals_in_row:
|
|
681
|
+
return False
|
|
682
|
+
|
|
683
|
+
vals_in_row[e] = True
|
|
684
|
+
|
|
685
|
+
for c in range(n):
|
|
686
|
+
vals_in_col = {}
|
|
687
|
+
|
|
688
|
+
for r in range(n):
|
|
689
|
+
e = self[r, c]
|
|
690
|
+
|
|
691
|
+
if e < 0:
|
|
692
|
+
continue
|
|
693
|
+
|
|
694
|
+
# Entry out of range 0, 1, ..., n-1:
|
|
695
|
+
if e >= n:
|
|
696
|
+
return False
|
|
697
|
+
|
|
698
|
+
# Entry has already appeared in this column:
|
|
699
|
+
if e in vals_in_col:
|
|
700
|
+
return False
|
|
701
|
+
|
|
702
|
+
vals_in_col[e] = True
|
|
703
|
+
|
|
704
|
+
return True
|
|
705
|
+
|
|
706
|
+
def is_latin_square(self):
|
|
707
|
+
"""
|
|
708
|
+
``self`` is a latin square if it is an n by n matrix, and each symbol
|
|
709
|
+
in [0, 1, ..., n-1] appears exactly once in each row, and exactly
|
|
710
|
+
once in each column.
|
|
711
|
+
|
|
712
|
+
EXAMPLES::
|
|
713
|
+
|
|
714
|
+
sage: from sage.combinat.matrices.latin import *
|
|
715
|
+
sage: elementary_abelian_2group(4).is_latin_square()
|
|
716
|
+
True
|
|
717
|
+
|
|
718
|
+
::
|
|
719
|
+
|
|
720
|
+
sage: forward_circulant(7).is_latin_square()
|
|
721
|
+
True
|
|
722
|
+
"""
|
|
723
|
+
# We do not allow latin rectangles:
|
|
724
|
+
if self.nrows() != self.ncols():
|
|
725
|
+
return False
|
|
726
|
+
|
|
727
|
+
# Every cell must be filled:
|
|
728
|
+
if any(x < 0 for x in self.list()):
|
|
729
|
+
return False
|
|
730
|
+
|
|
731
|
+
# By necessity self must be a partial latin square:
|
|
732
|
+
if not self.is_partial_latin_square():
|
|
733
|
+
return False
|
|
734
|
+
|
|
735
|
+
return True
|
|
736
|
+
|
|
737
|
+
def permissable_values(self, r, c):
|
|
738
|
+
"""
|
|
739
|
+
Find all values that do not appear in row r and column c of the
|
|
740
|
+
latin square ``self``. If ``self[r, c]`` is filled then we return the
|
|
741
|
+
empty list.
|
|
742
|
+
|
|
743
|
+
INPUT:
|
|
744
|
+
|
|
745
|
+
- ``self`` -- LatinSquare
|
|
746
|
+
|
|
747
|
+
- ``r`` -- integer; row of the latin square
|
|
748
|
+
|
|
749
|
+
- ``c`` -- integer; column of the latin square
|
|
750
|
+
|
|
751
|
+
EXAMPLES::
|
|
752
|
+
|
|
753
|
+
sage: from sage.combinat.matrices.latin import *
|
|
754
|
+
sage: L = back_circulant(5)
|
|
755
|
+
sage: L[0, 0] = -1
|
|
756
|
+
sage: L.permissable_values(0, 0)
|
|
757
|
+
[0]
|
|
758
|
+
"""
|
|
759
|
+
|
|
760
|
+
if self[r, c] >= 0:
|
|
761
|
+
return []
|
|
762
|
+
|
|
763
|
+
assert self.nrows() == self.ncols()
|
|
764
|
+
|
|
765
|
+
n = self.nrows()
|
|
766
|
+
|
|
767
|
+
vals = {}
|
|
768
|
+
for e in range(n):
|
|
769
|
+
vals[e] = True
|
|
770
|
+
|
|
771
|
+
for i in range(n):
|
|
772
|
+
if self[i, c] >= 0:
|
|
773
|
+
del vals[self[i, c]]
|
|
774
|
+
|
|
775
|
+
for j in range(n):
|
|
776
|
+
if self[r, j] >= 0:
|
|
777
|
+
try:
|
|
778
|
+
del vals[self[r, j]]
|
|
779
|
+
except KeyError:
|
|
780
|
+
# We may have already removed a symbol
|
|
781
|
+
# in the previous for-loop.
|
|
782
|
+
pass
|
|
783
|
+
|
|
784
|
+
return list(vals)
|
|
785
|
+
|
|
786
|
+
def random_empty_cell(self):
|
|
787
|
+
"""
|
|
788
|
+
Find an empty cell of self, uniformly at random.
|
|
789
|
+
|
|
790
|
+
INPUT:
|
|
791
|
+
|
|
792
|
+
- ``self`` -- LatinSquare
|
|
793
|
+
|
|
794
|
+
OUTPUT:
|
|
795
|
+
|
|
796
|
+
- ``[r, c]`` -- cell such that ``self[r, c]`` is empty, or returns
|
|
797
|
+
``None`` if ``self`` is a (full) latin square
|
|
798
|
+
|
|
799
|
+
EXAMPLES::
|
|
800
|
+
|
|
801
|
+
sage: from sage.combinat.matrices.latin import *
|
|
802
|
+
sage: P = back_circulant(2)
|
|
803
|
+
sage: P[1,1] = -1
|
|
804
|
+
sage: P.random_empty_cell()
|
|
805
|
+
[1, 1]
|
|
806
|
+
"""
|
|
807
|
+
|
|
808
|
+
cells = {}
|
|
809
|
+
|
|
810
|
+
for r in range(self.nrows()):
|
|
811
|
+
for c in range(self.ncols()):
|
|
812
|
+
if self[r, c] < 0:
|
|
813
|
+
cells[(r, c)] = True
|
|
814
|
+
|
|
815
|
+
cells = list(cells)
|
|
816
|
+
|
|
817
|
+
if not cells:
|
|
818
|
+
return None
|
|
819
|
+
|
|
820
|
+
rc = cells[ZZ.random_element(len(cells))]
|
|
821
|
+
|
|
822
|
+
return [rc[0], rc[1]]
|
|
823
|
+
|
|
824
|
+
def is_uniquely_completable(self):
|
|
825
|
+
"""
|
|
826
|
+
Return ``True`` if the partial latin square ``self`` has exactly one
|
|
827
|
+
completion to a latin square. This is just a wrapper for the
|
|
828
|
+
current best-known algorithm, Dancing Links by Knuth. See
|
|
829
|
+
dancing_links.spyx
|
|
830
|
+
|
|
831
|
+
EXAMPLES::
|
|
832
|
+
|
|
833
|
+
sage: from sage.combinat.matrices.latin import *
|
|
834
|
+
sage: back_circulant(4).gcs().is_uniquely_completable()
|
|
835
|
+
True
|
|
836
|
+
|
|
837
|
+
::
|
|
838
|
+
|
|
839
|
+
sage: G = elementary_abelian_2group(3).gcs()
|
|
840
|
+
sage: G.is_uniquely_completable()
|
|
841
|
+
True
|
|
842
|
+
|
|
843
|
+
::
|
|
844
|
+
|
|
845
|
+
sage: G[0, 0] = -1
|
|
846
|
+
sage: G.is_uniquely_completable()
|
|
847
|
+
False
|
|
848
|
+
"""
|
|
849
|
+
|
|
850
|
+
return self.dlxcpp_has_unique_completion()
|
|
851
|
+
|
|
852
|
+
def is_completable(self):
|
|
853
|
+
"""
|
|
854
|
+
Return ``True`` if the partial latin square can be completed to a
|
|
855
|
+
latin square.
|
|
856
|
+
|
|
857
|
+
EXAMPLES:
|
|
858
|
+
|
|
859
|
+
The following partial latin square has no completion because there
|
|
860
|
+
is nowhere that we can place the symbol 0 in the third row::
|
|
861
|
+
|
|
862
|
+
sage: B = LatinSquare(3)
|
|
863
|
+
|
|
864
|
+
::
|
|
865
|
+
|
|
866
|
+
sage: B[0, 0] = 0
|
|
867
|
+
sage: B[1, 1] = 0
|
|
868
|
+
sage: B[2, 2] = 1
|
|
869
|
+
|
|
870
|
+
::
|
|
871
|
+
|
|
872
|
+
sage: B
|
|
873
|
+
[ 0 -1 -1]
|
|
874
|
+
[-1 0 -1]
|
|
875
|
+
[-1 -1 1]
|
|
876
|
+
|
|
877
|
+
::
|
|
878
|
+
|
|
879
|
+
sage: B.is_completable()
|
|
880
|
+
False
|
|
881
|
+
|
|
882
|
+
::
|
|
883
|
+
|
|
884
|
+
sage: B[2, 2] = 0
|
|
885
|
+
sage: B.is_completable()
|
|
886
|
+
True
|
|
887
|
+
"""
|
|
888
|
+
return bool(dlxcpp_find_completions(self, nr_to_find=1))
|
|
889
|
+
|
|
890
|
+
def gcs(self):
|
|
891
|
+
"""
|
|
892
|
+
A greedy critical set of a latin square ``self`` is found by
|
|
893
|
+
successively removing elements in a row-wise (bottom-up) manner,
|
|
894
|
+
checking for unique completion at each step.
|
|
895
|
+
|
|
896
|
+
EXAMPLES::
|
|
897
|
+
|
|
898
|
+
sage: from sage.combinat.matrices.latin import *
|
|
899
|
+
sage: A = elementary_abelian_2group(3)
|
|
900
|
+
sage: G = A.gcs()
|
|
901
|
+
sage: A
|
|
902
|
+
[0 1 2 3 4 5 6 7]
|
|
903
|
+
[1 0 3 2 5 4 7 6]
|
|
904
|
+
[2 3 0 1 6 7 4 5]
|
|
905
|
+
[3 2 1 0 7 6 5 4]
|
|
906
|
+
[4 5 6 7 0 1 2 3]
|
|
907
|
+
[5 4 7 6 1 0 3 2]
|
|
908
|
+
[6 7 4 5 2 3 0 1]
|
|
909
|
+
[7 6 5 4 3 2 1 0]
|
|
910
|
+
sage: G
|
|
911
|
+
[ 0 1 2 3 4 5 6 -1]
|
|
912
|
+
[ 1 0 3 2 5 4 -1 -1]
|
|
913
|
+
[ 2 3 0 1 6 -1 4 -1]
|
|
914
|
+
[ 3 2 1 0 -1 -1 -1 -1]
|
|
915
|
+
[ 4 5 6 -1 0 1 2 -1]
|
|
916
|
+
[ 5 4 -1 -1 1 0 -1 -1]
|
|
917
|
+
[ 6 -1 4 -1 2 -1 0 -1]
|
|
918
|
+
[-1 -1 -1 -1 -1 -1 -1 -1]
|
|
919
|
+
"""
|
|
920
|
+
|
|
921
|
+
n = self.nrows()
|
|
922
|
+
|
|
923
|
+
from copy import copy
|
|
924
|
+
G = copy(self)
|
|
925
|
+
|
|
926
|
+
for r in range(n-1, -1, -1):
|
|
927
|
+
for c in range(n-1, -1, -1):
|
|
928
|
+
e = G[r, c]
|
|
929
|
+
G[r, c] = -1
|
|
930
|
+
|
|
931
|
+
if not G.dlxcpp_has_unique_completion():
|
|
932
|
+
G[r, c] = e
|
|
933
|
+
|
|
934
|
+
return G
|
|
935
|
+
|
|
936
|
+
def dlxcpp_has_unique_completion(self):
|
|
937
|
+
"""
|
|
938
|
+
Check if the partial latin square ``self`` of order n can be embedded
|
|
939
|
+
in precisely one latin square of order n.
|
|
940
|
+
|
|
941
|
+
EXAMPLES::
|
|
942
|
+
|
|
943
|
+
sage: from sage.combinat.matrices.latin import *
|
|
944
|
+
sage: back_circulant(2).dlxcpp_has_unique_completion()
|
|
945
|
+
True
|
|
946
|
+
sage: P = LatinSquare(2)
|
|
947
|
+
sage: P.dlxcpp_has_unique_completion()
|
|
948
|
+
False
|
|
949
|
+
sage: P[0, 0] = 0
|
|
950
|
+
sage: P.dlxcpp_has_unique_completion()
|
|
951
|
+
True
|
|
952
|
+
"""
|
|
953
|
+
return len(dlxcpp_find_completions(self, nr_to_find=2)) == 1
|
|
954
|
+
|
|
955
|
+
def vals_in_row(self, r):
|
|
956
|
+
"""
|
|
957
|
+
Return a dictionary with key e if and only if row r of ``self`` has
|
|
958
|
+
the symbol e.
|
|
959
|
+
|
|
960
|
+
EXAMPLES::
|
|
961
|
+
|
|
962
|
+
sage: from sage.combinat.matrices.latin import *
|
|
963
|
+
sage: B = back_circulant(3)
|
|
964
|
+
sage: B[0, 0] = -1
|
|
965
|
+
sage: back_circulant(3).vals_in_row(0)
|
|
966
|
+
{0: True, 1: True, 2: True}
|
|
967
|
+
"""
|
|
968
|
+
|
|
969
|
+
n = self.ncols()
|
|
970
|
+
vals_in_row = {}
|
|
971
|
+
|
|
972
|
+
for c in range(n):
|
|
973
|
+
e = self[r, c]
|
|
974
|
+
if e >= 0:
|
|
975
|
+
vals_in_row[e] = True
|
|
976
|
+
|
|
977
|
+
return vals_in_row
|
|
978
|
+
|
|
979
|
+
def vals_in_col(self, c):
|
|
980
|
+
"""
|
|
981
|
+
Return a dictionary with key e if and only if column c of ``self`` has
|
|
982
|
+
the symbol e.
|
|
983
|
+
|
|
984
|
+
EXAMPLES::
|
|
985
|
+
|
|
986
|
+
sage: from sage.combinat.matrices.latin import *
|
|
987
|
+
sage: B = back_circulant(3)
|
|
988
|
+
sage: B[0, 0] = -1
|
|
989
|
+
sage: back_circulant(3).vals_in_col(0)
|
|
990
|
+
{0: True, 1: True, 2: True}
|
|
991
|
+
"""
|
|
992
|
+
n = self.nrows()
|
|
993
|
+
vals_in_col = {}
|
|
994
|
+
|
|
995
|
+
for r in range(n):
|
|
996
|
+
e = self[r, c]
|
|
997
|
+
if e >= 0:
|
|
998
|
+
vals_in_col[e] = True
|
|
999
|
+
|
|
1000
|
+
return vals_in_col
|
|
1001
|
+
|
|
1002
|
+
def latex(self):
|
|
1003
|
+
r"""
|
|
1004
|
+
Return LaTeX code for the latin square.
|
|
1005
|
+
|
|
1006
|
+
EXAMPLES::
|
|
1007
|
+
|
|
1008
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1009
|
+
sage: print(back_circulant(3).latex())
|
|
1010
|
+
\begin{array}{|c|c|c|}\hline 0 & 1 & 2\\\hline 1 & 2 & 0\\\hline 2 & 0 & 1\\\hline\end{array}
|
|
1011
|
+
"""
|
|
1012
|
+
|
|
1013
|
+
a = ""
|
|
1014
|
+
a += r"\begin{array}{" + self.ncols()*"|c" + "|}"
|
|
1015
|
+
for r in range(self.nrows()):
|
|
1016
|
+
a += r"\hline "
|
|
1017
|
+
for c in range(self.ncols()):
|
|
1018
|
+
s = self[r, c]
|
|
1019
|
+
if s < 0:
|
|
1020
|
+
a += "~"
|
|
1021
|
+
else:
|
|
1022
|
+
a += str(s)
|
|
1023
|
+
|
|
1024
|
+
if c < self.ncols()-1:
|
|
1025
|
+
a += " & "
|
|
1026
|
+
else:
|
|
1027
|
+
a += "\\\\"
|
|
1028
|
+
a += r"\hline"
|
|
1029
|
+
a += r"\end{array}"
|
|
1030
|
+
return a
|
|
1031
|
+
|
|
1032
|
+
def disjoint_mate_dlxcpp_rows_and_map(self, allow_subtrade):
|
|
1033
|
+
"""
|
|
1034
|
+
Internal function for find_disjoint_mates.
|
|
1035
|
+
|
|
1036
|
+
EXAMPLES::
|
|
1037
|
+
|
|
1038
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1039
|
+
sage: B = back_circulant(4)
|
|
1040
|
+
sage: B.disjoint_mate_dlxcpp_rows_and_map(allow_subtrade = True)
|
|
1041
|
+
([[0, 16, 32],
|
|
1042
|
+
[1, 17, 32],
|
|
1043
|
+
[2, 18, 32],
|
|
1044
|
+
[3, 19, 32],
|
|
1045
|
+
[4, 16, 33],
|
|
1046
|
+
[5, 17, 33],
|
|
1047
|
+
[6, 18, 33],
|
|
1048
|
+
[7, 19, 33],
|
|
1049
|
+
[8, 16, 34],
|
|
1050
|
+
[9, 17, 34],
|
|
1051
|
+
[10, 18, 34],
|
|
1052
|
+
[11, 19, 34],
|
|
1053
|
+
[12, 16, 35],
|
|
1054
|
+
[13, 17, 35],
|
|
1055
|
+
[14, 18, 35],
|
|
1056
|
+
[15, 19, 35],
|
|
1057
|
+
[0, 20, 36],
|
|
1058
|
+
[1, 21, 36],
|
|
1059
|
+
[2, 22, 36],
|
|
1060
|
+
[3, 23, 36],
|
|
1061
|
+
[4, 20, 37],
|
|
1062
|
+
[5, 21, 37],
|
|
1063
|
+
[6, 22, 37],
|
|
1064
|
+
[7, 23, 37],
|
|
1065
|
+
[8, 20, 38],
|
|
1066
|
+
[9, 21, 38],
|
|
1067
|
+
[10, 22, 38],
|
|
1068
|
+
[11, 23, 38],
|
|
1069
|
+
[12, 20, 39],
|
|
1070
|
+
[13, 21, 39],
|
|
1071
|
+
[14, 22, 39],
|
|
1072
|
+
[15, 23, 39],
|
|
1073
|
+
[0, 24, 40],
|
|
1074
|
+
[1, 25, 40],
|
|
1075
|
+
[2, 26, 40],
|
|
1076
|
+
[3, 27, 40],
|
|
1077
|
+
[4, 24, 41],
|
|
1078
|
+
[5, 25, 41],
|
|
1079
|
+
[6, 26, 41],
|
|
1080
|
+
[7, 27, 41],
|
|
1081
|
+
[8, 24, 42],
|
|
1082
|
+
[9, 25, 42],
|
|
1083
|
+
[10, 26, 42],
|
|
1084
|
+
[11, 27, 42],
|
|
1085
|
+
[12, 24, 43],
|
|
1086
|
+
[13, 25, 43],
|
|
1087
|
+
[14, 26, 43],
|
|
1088
|
+
[15, 27, 43],
|
|
1089
|
+
[0, 28, 44],
|
|
1090
|
+
[1, 29, 44],
|
|
1091
|
+
[2, 30, 44],
|
|
1092
|
+
[3, 31, 44],
|
|
1093
|
+
[4, 28, 45],
|
|
1094
|
+
[5, 29, 45],
|
|
1095
|
+
[6, 30, 45],
|
|
1096
|
+
[7, 31, 45],
|
|
1097
|
+
[8, 28, 46],
|
|
1098
|
+
[9, 29, 46],
|
|
1099
|
+
[10, 30, 46],
|
|
1100
|
+
[11, 31, 46],
|
|
1101
|
+
[12, 28, 47],
|
|
1102
|
+
[13, 29, 47],
|
|
1103
|
+
[14, 30, 47],
|
|
1104
|
+
[15, 31, 47]],
|
|
1105
|
+
{(0, 16, 32): (0, 0, 0),
|
|
1106
|
+
(0, 20, 36): (1, 0, 0),
|
|
1107
|
+
(0, 24, 40): (2, 0, 0),
|
|
1108
|
+
(0, 28, 44): (3, 0, 0),
|
|
1109
|
+
(1, 17, 32): (0, 0, 1),
|
|
1110
|
+
(1, 21, 36): (1, 0, 1),
|
|
1111
|
+
(1, 25, 40): (2, 0, 1),
|
|
1112
|
+
(1, 29, 44): (3, 0, 1),
|
|
1113
|
+
(2, 18, 32): (0, 0, 2),
|
|
1114
|
+
(2, 22, 36): (1, 0, 2),
|
|
1115
|
+
(2, 26, 40): (2, 0, 2),
|
|
1116
|
+
(2, 30, 44): (3, 0, 2),
|
|
1117
|
+
(3, 19, 32): (0, 0, 3),
|
|
1118
|
+
(3, 23, 36): (1, 0, 3),
|
|
1119
|
+
(3, 27, 40): (2, 0, 3),
|
|
1120
|
+
(3, 31, 44): (3, 0, 3),
|
|
1121
|
+
(4, 16, 33): (0, 1, 0),
|
|
1122
|
+
(4, 20, 37): (1, 1, 0),
|
|
1123
|
+
(4, 24, 41): (2, 1, 0),
|
|
1124
|
+
(4, 28, 45): (3, 1, 0),
|
|
1125
|
+
(5, 17, 33): (0, 1, 1),
|
|
1126
|
+
(5, 21, 37): (1, 1, 1),
|
|
1127
|
+
(5, 25, 41): (2, 1, 1),
|
|
1128
|
+
(5, 29, 45): (3, 1, 1),
|
|
1129
|
+
(6, 18, 33): (0, 1, 2),
|
|
1130
|
+
(6, 22, 37): (1, 1, 2),
|
|
1131
|
+
(6, 26, 41): (2, 1, 2),
|
|
1132
|
+
(6, 30, 45): (3, 1, 2),
|
|
1133
|
+
(7, 19, 33): (0, 1, 3),
|
|
1134
|
+
(7, 23, 37): (1, 1, 3),
|
|
1135
|
+
(7, 27, 41): (2, 1, 3),
|
|
1136
|
+
(7, 31, 45): (3, 1, 3),
|
|
1137
|
+
(8, 16, 34): (0, 2, 0),
|
|
1138
|
+
(8, 20, 38): (1, 2, 0),
|
|
1139
|
+
(8, 24, 42): (2, 2, 0),
|
|
1140
|
+
(8, 28, 46): (3, 2, 0),
|
|
1141
|
+
(9, 17, 34): (0, 2, 1),
|
|
1142
|
+
(9, 21, 38): (1, 2, 1),
|
|
1143
|
+
(9, 25, 42): (2, 2, 1),
|
|
1144
|
+
(9, 29, 46): (3, 2, 1),
|
|
1145
|
+
(10, 18, 34): (0, 2, 2),
|
|
1146
|
+
(10, 22, 38): (1, 2, 2),
|
|
1147
|
+
(10, 26, 42): (2, 2, 2),
|
|
1148
|
+
(10, 30, 46): (3, 2, 2),
|
|
1149
|
+
(11, 19, 34): (0, 2, 3),
|
|
1150
|
+
(11, 23, 38): (1, 2, 3),
|
|
1151
|
+
(11, 27, 42): (2, 2, 3),
|
|
1152
|
+
(11, 31, 46): (3, 2, 3),
|
|
1153
|
+
(12, 16, 35): (0, 3, 0),
|
|
1154
|
+
(12, 20, 39): (1, 3, 0),
|
|
1155
|
+
(12, 24, 43): (2, 3, 0),
|
|
1156
|
+
(12, 28, 47): (3, 3, 0),
|
|
1157
|
+
(13, 17, 35): (0, 3, 1),
|
|
1158
|
+
(13, 21, 39): (1, 3, 1),
|
|
1159
|
+
(13, 25, 43): (2, 3, 1),
|
|
1160
|
+
(13, 29, 47): (3, 3, 1),
|
|
1161
|
+
(14, 18, 35): (0, 3, 2),
|
|
1162
|
+
(14, 22, 39): (1, 3, 2),
|
|
1163
|
+
(14, 26, 43): (2, 3, 2),
|
|
1164
|
+
(14, 30, 47): (3, 3, 2),
|
|
1165
|
+
(15, 19, 35): (0, 3, 3),
|
|
1166
|
+
(15, 23, 39): (1, 3, 3),
|
|
1167
|
+
(15, 27, 43): (2, 3, 3),
|
|
1168
|
+
(15, 31, 47): (3, 3, 3)})
|
|
1169
|
+
"""
|
|
1170
|
+
|
|
1171
|
+
assert self.nrows() == self.ncols()
|
|
1172
|
+
|
|
1173
|
+
n = self.nrows()
|
|
1174
|
+
|
|
1175
|
+
# We will need 3n^2 columns in total:
|
|
1176
|
+
#
|
|
1177
|
+
# n^2 for the xCy columns
|
|
1178
|
+
# n^2 for the xRy columns
|
|
1179
|
+
# n^2 for the xy columns
|
|
1180
|
+
|
|
1181
|
+
dlx_rows = []
|
|
1182
|
+
cmap = {}
|
|
1183
|
+
|
|
1184
|
+
max_column_nr = -1
|
|
1185
|
+
|
|
1186
|
+
for r in range(n):
|
|
1187
|
+
valsrow = self.vals_in_row(r)
|
|
1188
|
+
|
|
1189
|
+
for c in range(n):
|
|
1190
|
+
valscol = self.vals_in_col(c)
|
|
1191
|
+
|
|
1192
|
+
# If this is an empty cell of self then we do nothing.
|
|
1193
|
+
if self[r, c] < 0:
|
|
1194
|
+
continue
|
|
1195
|
+
|
|
1196
|
+
for e in sorted(set(list(valsrow) + list(valscol))):
|
|
1197
|
+
# These should be constants
|
|
1198
|
+
c_OFFSET = e + c*n
|
|
1199
|
+
r_OFFSET = e + r*n + n*n
|
|
1200
|
+
xy_OFFSET = 2*n*n + r*n + c
|
|
1201
|
+
|
|
1202
|
+
cmap[(c_OFFSET, r_OFFSET, xy_OFFSET)] = (r, c, e)
|
|
1203
|
+
|
|
1204
|
+
# The disjoint mate has to be disjoint.
|
|
1205
|
+
if (not allow_subtrade) and self[r, c] == e:
|
|
1206
|
+
continue
|
|
1207
|
+
|
|
1208
|
+
# The permissible symbols must come from this row/column.
|
|
1209
|
+
if e not in valsrow:
|
|
1210
|
+
continue
|
|
1211
|
+
if e not in valscol:
|
|
1212
|
+
continue
|
|
1213
|
+
|
|
1214
|
+
dlx_rows.append([c_OFFSET, r_OFFSET, xy_OFFSET])
|
|
1215
|
+
|
|
1216
|
+
max_column_nr = max(max_column_nr, c_OFFSET,
|
|
1217
|
+
r_OFFSET, xy_OFFSET)
|
|
1218
|
+
|
|
1219
|
+
# We will have missed some columns. We
|
|
1220
|
+
# have to add 'dummy' rows so that the C++ DLX solver will find
|
|
1221
|
+
# a solution.
|
|
1222
|
+
used_columns = flatten(dlx_rows)
|
|
1223
|
+
for i in range(max_column_nr + 1):
|
|
1224
|
+
if i not in used_columns:
|
|
1225
|
+
dlx_rows.append([i])
|
|
1226
|
+
|
|
1227
|
+
return dlx_rows, cmap
|
|
1228
|
+
|
|
1229
|
+
def find_disjoint_mates(self, nr_to_find=None, allow_subtrade=False):
|
|
1230
|
+
r"""
|
|
1231
|
+
.. warning::
|
|
1232
|
+
|
|
1233
|
+
If allow_subtrade is ``True`` then we may return a partial
|
|
1234
|
+
latin square that is *not* disjoint to ``self``. In that case,
|
|
1235
|
+
use bitrade(P, Q) to get an actual bitrade.
|
|
1236
|
+
|
|
1237
|
+
EXAMPLES::
|
|
1238
|
+
|
|
1239
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1240
|
+
sage: B = back_circulant(4)
|
|
1241
|
+
sage: g = B.find_disjoint_mates(allow_subtrade = True)
|
|
1242
|
+
sage: B1 = next(g)
|
|
1243
|
+
sage: B0, B1 = bitrade(B, B1)
|
|
1244
|
+
sage: assert is_bitrade(B0, B1)
|
|
1245
|
+
sage: print(B0)
|
|
1246
|
+
[-1 1 2 -1]
|
|
1247
|
+
[-1 2 -1 0]
|
|
1248
|
+
[-1 -1 -1 -1]
|
|
1249
|
+
[-1 0 1 2]
|
|
1250
|
+
sage: print(B1)
|
|
1251
|
+
[-1 2 1 -1]
|
|
1252
|
+
[-1 0 -1 2]
|
|
1253
|
+
[-1 -1 -1 -1]
|
|
1254
|
+
[-1 1 2 0]
|
|
1255
|
+
"""
|
|
1256
|
+
|
|
1257
|
+
assert self.nrows() == self.ncols()
|
|
1258
|
+
|
|
1259
|
+
dlx_rows, cmap = self.disjoint_mate_dlxcpp_rows_and_map(allow_subtrade)
|
|
1260
|
+
|
|
1261
|
+
nr_found = 0
|
|
1262
|
+
|
|
1263
|
+
for x in DLXCPP(dlx_rows):
|
|
1264
|
+
nr_found += 1
|
|
1265
|
+
|
|
1266
|
+
from copy import deepcopy
|
|
1267
|
+
Q = deepcopy(self)
|
|
1268
|
+
|
|
1269
|
+
for y in x:
|
|
1270
|
+
if len(dlx_rows[y]) == 1:
|
|
1271
|
+
continue # dummy row
|
|
1272
|
+
(r, c, e) = cmap[tuple(dlx_rows[y])]
|
|
1273
|
+
Q[r, c] = e
|
|
1274
|
+
|
|
1275
|
+
yield Q
|
|
1276
|
+
|
|
1277
|
+
if nr_to_find is not None and nr_found >= nr_to_find:
|
|
1278
|
+
return
|
|
1279
|
+
|
|
1280
|
+
def contained_in(self, Q):
|
|
1281
|
+
r"""
|
|
1282
|
+
Return ``True`` if ``self`` is a subset of `Q`.
|
|
1283
|
+
|
|
1284
|
+
EXAMPLES::
|
|
1285
|
+
|
|
1286
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1287
|
+
sage: P = elementary_abelian_2group(2)
|
|
1288
|
+
sage: P[0, 0] = -1
|
|
1289
|
+
sage: P.contained_in(elementary_abelian_2group(2))
|
|
1290
|
+
True
|
|
1291
|
+
sage: back_circulant(4).contained_in(elementary_abelian_2group(2))
|
|
1292
|
+
False
|
|
1293
|
+
"""
|
|
1294
|
+
for r in range(self.nrows()):
|
|
1295
|
+
for c in range(self.ncols()):
|
|
1296
|
+
if self[r, c] >= 0 and Q[r, c] < 0:
|
|
1297
|
+
return False
|
|
1298
|
+
if self[r, c] >= 0 and self[r, c] != Q[r, c]:
|
|
1299
|
+
return False
|
|
1300
|
+
return True
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
def genus(T1, T2):
|
|
1304
|
+
"""
|
|
1305
|
+
Return the genus of hypermap embedding associated with the bitrade
|
|
1306
|
+
(T1, T2).
|
|
1307
|
+
|
|
1308
|
+
Informally, we compute the [tau_1, tau_2, tau_3]
|
|
1309
|
+
permutation representation of the bitrade. Each cycle of tau_1,
|
|
1310
|
+
tau_2, and tau_3 gives a rotation scheme for a black, white, and
|
|
1311
|
+
star vertex (respectively). The genus then comes from Euler's
|
|
1312
|
+
formula.
|
|
1313
|
+
|
|
1314
|
+
For more details see Carlo Hamalainen: *Partitioning
|
|
1315
|
+
3-homogeneous latin bitrades*. To appear in Geometriae Dedicata,
|
|
1316
|
+
available at :arxiv:`0710.0938`
|
|
1317
|
+
|
|
1318
|
+
EXAMPLES::
|
|
1319
|
+
|
|
1320
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1321
|
+
sage: (a, b, c, G) = alternating_group_bitrade_generators(1)
|
|
1322
|
+
sage: (T1, T2) = bitrade_from_group(a, b, c, G)
|
|
1323
|
+
sage: genus(T1, T2)
|
|
1324
|
+
1
|
|
1325
|
+
sage: (a, b, c, G) = pq_group_bitrade_generators(3, 7)
|
|
1326
|
+
sage: (T1, T2) = bitrade_from_group(a, b, c, G)
|
|
1327
|
+
sage: genus(T1, T2)
|
|
1328
|
+
3
|
|
1329
|
+
"""
|
|
1330
|
+
cells_map, t1, t2, t3 = tau123(T1, T2)
|
|
1331
|
+
return (len(t1.to_cycles()) + len(t2.to_cycles()) + len(t3.to_cycles()) - T1.nr_filled_cells() - 2) // (-2)
|
|
1332
|
+
|
|
1333
|
+
|
|
1334
|
+
def tau123(T1, T2):
|
|
1335
|
+
r"""
|
|
1336
|
+
Compute the tau_i representation for a bitrade (T1, T2).
|
|
1337
|
+
|
|
1338
|
+
See the
|
|
1339
|
+
functions tau1, tau2, and tau3 for the mathematical definitions.
|
|
1340
|
+
|
|
1341
|
+
OUTPUT:
|
|
1342
|
+
|
|
1343
|
+
- (cells_map, t1, t2, t3)
|
|
1344
|
+
|
|
1345
|
+
where cells_map is a map to/from the filled cells of T1, and t1,
|
|
1346
|
+
t2, t3 are the tau1, tau2, tau3 permutations.
|
|
1347
|
+
|
|
1348
|
+
EXAMPLES::
|
|
1349
|
+
|
|
1350
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1351
|
+
sage: (a, b, c, G) = pq_group_bitrade_generators(3, 7)
|
|
1352
|
+
sage: (T1, T2) = bitrade_from_group(a, b, c, G)
|
|
1353
|
+
sage: T1
|
|
1354
|
+
[ 0 1 3 -1 -1 -1 -1]
|
|
1355
|
+
[ 1 2 4 -1 -1 -1 -1]
|
|
1356
|
+
[ 2 3 5 -1 -1 -1 -1]
|
|
1357
|
+
[ 3 4 6 -1 -1 -1 -1]
|
|
1358
|
+
[ 4 5 0 -1 -1 -1 -1]
|
|
1359
|
+
[ 5 6 1 -1 -1 -1 -1]
|
|
1360
|
+
[ 6 0 2 -1 -1 -1 -1]
|
|
1361
|
+
sage: T2
|
|
1362
|
+
[ 1 3 0 -1 -1 -1 -1]
|
|
1363
|
+
[ 2 4 1 -1 -1 -1 -1]
|
|
1364
|
+
[ 3 5 2 -1 -1 -1 -1]
|
|
1365
|
+
[ 4 6 3 -1 -1 -1 -1]
|
|
1366
|
+
[ 5 0 4 -1 -1 -1 -1]
|
|
1367
|
+
[ 6 1 5 -1 -1 -1 -1]
|
|
1368
|
+
[ 0 2 6 -1 -1 -1 -1]
|
|
1369
|
+
sage: (cells_map, t1, t2, t3) = tau123(T1, T2)
|
|
1370
|
+
sage: D = cells_map
|
|
1371
|
+
sage: {i: v for i,v in D.items() if i in ZZ}
|
|
1372
|
+
{1: (0, 0),
|
|
1373
|
+
2: (0, 1),
|
|
1374
|
+
3: (0, 2),
|
|
1375
|
+
4: (1, 0),
|
|
1376
|
+
5: (1, 1),
|
|
1377
|
+
6: (1, 2),
|
|
1378
|
+
7: (2, 0),
|
|
1379
|
+
8: (2, 1),
|
|
1380
|
+
9: (2, 2),
|
|
1381
|
+
10: (3, 0),
|
|
1382
|
+
11: (3, 1),
|
|
1383
|
+
12: (3, 2),
|
|
1384
|
+
13: (4, 0),
|
|
1385
|
+
14: (4, 1),
|
|
1386
|
+
15: (4, 2),
|
|
1387
|
+
16: (5, 0),
|
|
1388
|
+
17: (5, 1),
|
|
1389
|
+
18: (5, 2),
|
|
1390
|
+
19: (6, 0),
|
|
1391
|
+
20: (6, 1),
|
|
1392
|
+
21: (6, 2)}
|
|
1393
|
+
sage: {i: v for i,v in D.items() if i not in ZZ}
|
|
1394
|
+
{(0, 0): 1,
|
|
1395
|
+
(0, 1): 2,
|
|
1396
|
+
(0, 2): 3,
|
|
1397
|
+
(1, 0): 4,
|
|
1398
|
+
(1, 1): 5,
|
|
1399
|
+
(1, 2): 6,
|
|
1400
|
+
(2, 0): 7,
|
|
1401
|
+
(2, 1): 8,
|
|
1402
|
+
(2, 2): 9,
|
|
1403
|
+
(3, 0): 10,
|
|
1404
|
+
(3, 1): 11,
|
|
1405
|
+
(3, 2): 12,
|
|
1406
|
+
(4, 0): 13,
|
|
1407
|
+
(4, 1): 14,
|
|
1408
|
+
(4, 2): 15,
|
|
1409
|
+
(5, 0): 16,
|
|
1410
|
+
(5, 1): 17,
|
|
1411
|
+
(5, 2): 18,
|
|
1412
|
+
(6, 0): 19,
|
|
1413
|
+
(6, 1): 20,
|
|
1414
|
+
(6, 2): 21}
|
|
1415
|
+
sage: cells_map_as_square(cells_map, max(T1.nrows(), T1.ncols()))
|
|
1416
|
+
[ 1 2 3 -1 -1 -1 -1]
|
|
1417
|
+
[ 4 5 6 -1 -1 -1 -1]
|
|
1418
|
+
[ 7 8 9 -1 -1 -1 -1]
|
|
1419
|
+
[10 11 12 -1 -1 -1 -1]
|
|
1420
|
+
[13 14 15 -1 -1 -1 -1]
|
|
1421
|
+
[16 17 18 -1 -1 -1 -1]
|
|
1422
|
+
[19 20 21 -1 -1 -1 -1]
|
|
1423
|
+
sage: t1
|
|
1424
|
+
[3, 1, 2, 6, 4, 5, 9, 7, 8, 12, 10, 11, 15, 13, 14, 18, 16, 17, 21, 19, 20]
|
|
1425
|
+
sage: t2
|
|
1426
|
+
[4, 8, 15, 7, 11, 18, 10, 14, 21, 13, 17, 3, 16, 20, 6, 19, 2, 9, 1, 5, 12]
|
|
1427
|
+
sage: t3
|
|
1428
|
+
[20, 18, 10, 2, 21, 13, 5, 3, 16, 8, 6, 19, 11, 9, 1, 14, 12, 4, 17, 15, 7]
|
|
1429
|
+
|
|
1430
|
+
::
|
|
1431
|
+
|
|
1432
|
+
sage: t1.to_cycles()
|
|
1433
|
+
[(1, 3, 2), (4, 6, 5), (7, 9, 8), (10, 12, 11), (13, 15, 14), (16, 18, 17), (19, 21, 20)]
|
|
1434
|
+
sage: t2.to_cycles()
|
|
1435
|
+
[(1, 4, 7, 10, 13, 16, 19), (2, 8, 14, 20, 5, 11, 17), (3, 15, 6, 18, 9, 21, 12)]
|
|
1436
|
+
sage: t3.to_cycles()
|
|
1437
|
+
[(1, 20, 15), (2, 18, 4), (3, 10, 8), (5, 21, 7), (6, 13, 11), (9, 16, 14), (12, 19, 17)]
|
|
1438
|
+
|
|
1439
|
+
The product t1\*t2\*t3 is the identity, i.e. it fixes every point::
|
|
1440
|
+
|
|
1441
|
+
sage: len((t1*t2*t3).fixed_points()) == T1.nr_filled_cells()
|
|
1442
|
+
True
|
|
1443
|
+
"""
|
|
1444
|
+
assert is_bitrade(T1, T2)
|
|
1445
|
+
|
|
1446
|
+
cells_map = T1.filled_cells_map()
|
|
1447
|
+
|
|
1448
|
+
t1 = tau1(T1, T2, cells_map)
|
|
1449
|
+
t2 = tau2(T1, T2, cells_map)
|
|
1450
|
+
t3 = tau3(T1, T2, cells_map)
|
|
1451
|
+
|
|
1452
|
+
return (cells_map, t1, t2, t3)
|
|
1453
|
+
|
|
1454
|
+
|
|
1455
|
+
def isotopism(p):
|
|
1456
|
+
"""
|
|
1457
|
+
Return a Permutation object that represents an isotopism (for rows,
|
|
1458
|
+
columns or symbols of a partial latin square).
|
|
1459
|
+
|
|
1460
|
+
Technically, all this function does is take as input a
|
|
1461
|
+
representation of a permutation of `0,...,n-1` and return a
|
|
1462
|
+
:class:`Permutation` object defined on `1,...,n`.
|
|
1463
|
+
|
|
1464
|
+
For a definition of isotopism, see the :wikipedia:`wikipedia section on
|
|
1465
|
+
isotopism <Latin_square#Equivalence_classes_of_Latin_squares>`.
|
|
1466
|
+
|
|
1467
|
+
INPUT:
|
|
1468
|
+
|
|
1469
|
+
According to the type of input (see examples below):
|
|
1470
|
+
|
|
1471
|
+
- an integer `n` -- the function returns the identity on `1,...,n`
|
|
1472
|
+
|
|
1473
|
+
- a string representing a permutation in disjoint cycles notation,
|
|
1474
|
+
e.g. `(0,1,2)(3,4,5)` -- the corresponding permutation is returned,
|
|
1475
|
+
shifted by 1 to act on `1,...,n`
|
|
1476
|
+
|
|
1477
|
+
- list/tuple of tuples -- assumes disjoint cycle notation, see previous
|
|
1478
|
+
entry
|
|
1479
|
+
|
|
1480
|
+
- a list of integers -- the function adds `1` to each member of the
|
|
1481
|
+
list, and returns the corresponding permutation
|
|
1482
|
+
|
|
1483
|
+
- a :class:`PermutationGroupElement` ``p`` -- returns a permutation
|
|
1484
|
+
describing ``p`` **without** any shift
|
|
1485
|
+
|
|
1486
|
+
EXAMPLES::
|
|
1487
|
+
|
|
1488
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1489
|
+
sage: isotopism(5) # identity on 5 points
|
|
1490
|
+
[1, 2, 3, 4, 5]
|
|
1491
|
+
|
|
1492
|
+
::
|
|
1493
|
+
|
|
1494
|
+
sage: G = PermutationGroup(['(1,2,3)(4,5)'])
|
|
1495
|
+
sage: g = G.gen(0)
|
|
1496
|
+
sage: isotopism(g)
|
|
1497
|
+
[2, 3, 1, 5, 4]
|
|
1498
|
+
|
|
1499
|
+
::
|
|
1500
|
+
|
|
1501
|
+
sage: isotopism([0,3,2,1]) # 0 goes to 0, 1 goes to 3, etc.
|
|
1502
|
+
[1, 4, 3, 2]
|
|
1503
|
+
|
|
1504
|
+
::
|
|
1505
|
+
|
|
1506
|
+
sage: isotopism( (0,1,2) ) # single cycle, presented as a tuple
|
|
1507
|
+
[2, 3, 1]
|
|
1508
|
+
|
|
1509
|
+
::
|
|
1510
|
+
|
|
1511
|
+
sage: x = isotopism( ((0,1,2), (3,4)) ) # tuple of cycles
|
|
1512
|
+
sage: x
|
|
1513
|
+
[2, 3, 1, 5, 4]
|
|
1514
|
+
sage: x.to_cycles()
|
|
1515
|
+
[(1, 2, 3), (4, 5)]
|
|
1516
|
+
"""
|
|
1517
|
+
|
|
1518
|
+
# Identity isotopism on p points:
|
|
1519
|
+
if isinstance(p, (Integer, int)):
|
|
1520
|
+
return Permutation(range(1, p + 1))
|
|
1521
|
+
|
|
1522
|
+
if isinstance(p, PermutationGroupElement):
|
|
1523
|
+
# fixme Ask the Sage mailing list about the tuple/list issue!
|
|
1524
|
+
return Permutation(list(p.tuple()))
|
|
1525
|
+
|
|
1526
|
+
if isinstance(p, list):
|
|
1527
|
+
# We expect a list like [0,3,2,1] which means
|
|
1528
|
+
# that 0 goes to 0, 1 goes to 3, etc.
|
|
1529
|
+
return Permutation([x+1 for x in p])
|
|
1530
|
+
|
|
1531
|
+
if isinstance(p, tuple):
|
|
1532
|
+
# We have a single cycle:
|
|
1533
|
+
if isinstance(p[0], Integer):
|
|
1534
|
+
return Permutation(tuple(x+1 for x in p))
|
|
1535
|
+
|
|
1536
|
+
# We have a tuple of cycles:
|
|
1537
|
+
if isinstance(p[0], tuple):
|
|
1538
|
+
x = isotopism(p[0])
|
|
1539
|
+
|
|
1540
|
+
for i in range(1, len(p)):
|
|
1541
|
+
x = x._left_to_right_multiply_on_left(isotopism(p[i]))
|
|
1542
|
+
|
|
1543
|
+
return x
|
|
1544
|
+
|
|
1545
|
+
# Not sure what we got!
|
|
1546
|
+
raise TypeError("unable to convert {!r} to isotopism".format(p))
|
|
1547
|
+
|
|
1548
|
+
|
|
1549
|
+
def cells_map_as_square(cells_map, n):
|
|
1550
|
+
"""
|
|
1551
|
+
Return a LatinSquare with cells numbered from 1, 2, ... to given
|
|
1552
|
+
the dictionary cells_map.
|
|
1553
|
+
|
|
1554
|
+
.. NOTE::
|
|
1555
|
+
|
|
1556
|
+
The value n should be the maximum of the number of rows and
|
|
1557
|
+
columns of the original partial latin square
|
|
1558
|
+
|
|
1559
|
+
EXAMPLES::
|
|
1560
|
+
|
|
1561
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1562
|
+
sage: (a, b, c, G) = alternating_group_bitrade_generators(1)
|
|
1563
|
+
sage: (T1, T2) = bitrade_from_group(a, b, c, G)
|
|
1564
|
+
sage: T1
|
|
1565
|
+
[ 0 -1 3 1]
|
|
1566
|
+
[-1 1 0 2]
|
|
1567
|
+
[ 1 3 2 -1]
|
|
1568
|
+
[ 2 0 -1 3]
|
|
1569
|
+
|
|
1570
|
+
There are 12 filled cells in T::
|
|
1571
|
+
|
|
1572
|
+
sage: cells_map_as_square(T1.filled_cells_map(), max(T1.nrows(), T1.ncols()))
|
|
1573
|
+
[ 1 -1 2 3]
|
|
1574
|
+
[-1 4 5 6]
|
|
1575
|
+
[ 7 8 9 -1]
|
|
1576
|
+
[10 11 -1 12]
|
|
1577
|
+
"""
|
|
1578
|
+
|
|
1579
|
+
assert n > 1
|
|
1580
|
+
|
|
1581
|
+
L = LatinSquare(n, n)
|
|
1582
|
+
|
|
1583
|
+
for r in range(n):
|
|
1584
|
+
for c in range(n):
|
|
1585
|
+
try:
|
|
1586
|
+
L[r, c] = cells_map[(r, c)]
|
|
1587
|
+
except KeyError:
|
|
1588
|
+
# There is no cell (r,c) so skip it
|
|
1589
|
+
L[r, c] = -1
|
|
1590
|
+
|
|
1591
|
+
return L
|
|
1592
|
+
|
|
1593
|
+
|
|
1594
|
+
def beta1(rce, T1, T2):
|
|
1595
|
+
"""
|
|
1596
|
+
Find the unique (x, c, e) in T2 such that (r, c, e) is in T1.
|
|
1597
|
+
|
|
1598
|
+
INPUT:
|
|
1599
|
+
|
|
1600
|
+
- ``rce`` -- tuple (or list) (r, c, e) in T1
|
|
1601
|
+
|
|
1602
|
+
- ``T1``, ``T2`` -- latin bitrade
|
|
1603
|
+
|
|
1604
|
+
OUTPUT: (x, c, e) in T2
|
|
1605
|
+
|
|
1606
|
+
EXAMPLES::
|
|
1607
|
+
|
|
1608
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1609
|
+
sage: T1 = back_circulant(5)
|
|
1610
|
+
sage: x = isotopism( (0,1,2,3,4) )
|
|
1611
|
+
sage: y = isotopism(5) # identity
|
|
1612
|
+
sage: z = isotopism(5) # identity
|
|
1613
|
+
sage: T2 = T1.apply_isotopism(x, y, z)
|
|
1614
|
+
sage: is_bitrade(T1, T2)
|
|
1615
|
+
True
|
|
1616
|
+
sage: beta1([0, 0, 0], T1, T2)
|
|
1617
|
+
(1, 0, 0)
|
|
1618
|
+
"""
|
|
1619
|
+
r = rce[0]
|
|
1620
|
+
c = rce[1]
|
|
1621
|
+
e = rce[2]
|
|
1622
|
+
|
|
1623
|
+
assert T1[r, c] == e
|
|
1624
|
+
assert e >= 0
|
|
1625
|
+
|
|
1626
|
+
for x in range(T1.nrows()):
|
|
1627
|
+
if T2[x, c] == e:
|
|
1628
|
+
return (x, c, e)
|
|
1629
|
+
|
|
1630
|
+
raise ValueError
|
|
1631
|
+
|
|
1632
|
+
|
|
1633
|
+
def beta2(rce, T1, T2):
|
|
1634
|
+
"""
|
|
1635
|
+
Find the unique (r, x, e) in T2 such that (r, c, e) is in T1.
|
|
1636
|
+
|
|
1637
|
+
INPUT:
|
|
1638
|
+
|
|
1639
|
+
- ``rce`` -- tuple (or list) (r, c, e) in T1
|
|
1640
|
+
|
|
1641
|
+
- ``T1``, ``T2`` -- latin bitrade
|
|
1642
|
+
|
|
1643
|
+
OUTPUT: (r, x, e) in T2
|
|
1644
|
+
|
|
1645
|
+
EXAMPLES::
|
|
1646
|
+
|
|
1647
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1648
|
+
sage: T1 = back_circulant(5)
|
|
1649
|
+
sage: x = isotopism( (0,1,2,3,4) )
|
|
1650
|
+
sage: y = isotopism(5) # identity
|
|
1651
|
+
sage: z = isotopism(5) # identity
|
|
1652
|
+
sage: T2 = T1.apply_isotopism(x, y, z)
|
|
1653
|
+
sage: is_bitrade(T1, T2)
|
|
1654
|
+
True
|
|
1655
|
+
sage: beta2([0, 0, 0], T1, T2)
|
|
1656
|
+
(0, 1, 0)
|
|
1657
|
+
"""
|
|
1658
|
+
r = rce[0]
|
|
1659
|
+
c = rce[1]
|
|
1660
|
+
e = rce[2]
|
|
1661
|
+
|
|
1662
|
+
assert T1[r, c] == e
|
|
1663
|
+
assert e >= 0
|
|
1664
|
+
|
|
1665
|
+
for x in range(T1.ncols()):
|
|
1666
|
+
if T2[r, x] == e:
|
|
1667
|
+
return (r, x, e)
|
|
1668
|
+
|
|
1669
|
+
raise ValueError
|
|
1670
|
+
|
|
1671
|
+
|
|
1672
|
+
def beta3(rce, T1, T2):
|
|
1673
|
+
"""
|
|
1674
|
+
Find the unique (r, c, x) in T2 such that (r, c, e) is in T1.
|
|
1675
|
+
|
|
1676
|
+
INPUT:
|
|
1677
|
+
|
|
1678
|
+
- ``rce`` -- tuple (or list) (r, c, e) in T1
|
|
1679
|
+
|
|
1680
|
+
- ``T1, T2`` -- latin bitrade
|
|
1681
|
+
|
|
1682
|
+
OUTPUT: (r, c, x) in T2.
|
|
1683
|
+
|
|
1684
|
+
EXAMPLES::
|
|
1685
|
+
|
|
1686
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1687
|
+
sage: T1 = back_circulant(5)
|
|
1688
|
+
sage: x = isotopism( (0,1,2,3,4) )
|
|
1689
|
+
sage: y = isotopism(5) # identity
|
|
1690
|
+
sage: z = isotopism(5) # identity
|
|
1691
|
+
sage: T2 = T1.apply_isotopism(x, y, z)
|
|
1692
|
+
sage: is_bitrade(T1, T2)
|
|
1693
|
+
True
|
|
1694
|
+
sage: beta3([0, 0, 0], T1, T2)
|
|
1695
|
+
(0, 0, 4)
|
|
1696
|
+
"""
|
|
1697
|
+
r = rce[0]
|
|
1698
|
+
c = rce[1]
|
|
1699
|
+
e = rce[2]
|
|
1700
|
+
|
|
1701
|
+
assert T1[r, c] == e
|
|
1702
|
+
assert e >= 0
|
|
1703
|
+
|
|
1704
|
+
# fixme this range could be iffy if we
|
|
1705
|
+
# work with latin bitrade rectangles...
|
|
1706
|
+
for x in range(T1.nrows()):
|
|
1707
|
+
if T2[r, c] == x:
|
|
1708
|
+
return (r, c, x)
|
|
1709
|
+
|
|
1710
|
+
raise ValueError
|
|
1711
|
+
|
|
1712
|
+
|
|
1713
|
+
def tau1(T1, T2, cells_map):
|
|
1714
|
+
r"""
|
|
1715
|
+
The definition of `\tau_1` is
|
|
1716
|
+
|
|
1717
|
+
.. MATH::
|
|
1718
|
+
|
|
1719
|
+
\tau_1 : T1 \rightarrow T1 \\
|
|
1720
|
+
\tau_1 = \beta_2^{-1} \beta_3
|
|
1721
|
+
|
|
1722
|
+
where the composition is left to right and `\beta_i : T2 \rightarrow T1`
|
|
1723
|
+
changes just the `i`-th coordinate of a triple.
|
|
1724
|
+
|
|
1725
|
+
EXAMPLES::
|
|
1726
|
+
|
|
1727
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1728
|
+
sage: T1 = back_circulant(5)
|
|
1729
|
+
sage: x = isotopism( (0,1,2,3,4) )
|
|
1730
|
+
sage: y = isotopism(5) # identity
|
|
1731
|
+
sage: z = isotopism(5) # identity
|
|
1732
|
+
sage: T2 = T1.apply_isotopism(x, y, z)
|
|
1733
|
+
sage: is_bitrade(T1, T2)
|
|
1734
|
+
True
|
|
1735
|
+
sage: (cells_map, t1, t2, t3) = tau123(T1, T2)
|
|
1736
|
+
sage: t1 = tau1(T1, T2, cells_map)
|
|
1737
|
+
sage: t1
|
|
1738
|
+
[2, 3, 4, 5, 1, 7, 8, 9, 10, 6, 12, 13, 14, 15, 11, 17, 18, 19, 20, 16, 22, 23, 24, 25, 21]
|
|
1739
|
+
sage: t1.to_cycles()
|
|
1740
|
+
[(1, 2, 3, 4, 5), (6, 7, 8, 9, 10), (11, 12, 13, 14, 15), (16, 17, 18, 19, 20), (21, 22, 23, 24, 25)]
|
|
1741
|
+
"""
|
|
1742
|
+
|
|
1743
|
+
# The cells_map has both directions, i.e. integer to
|
|
1744
|
+
# cell and cell to integer, so the size of T1 is
|
|
1745
|
+
# just half of len(cells_map).
|
|
1746
|
+
x = (int(len(cells_map)/2) + 1) * [-1]
|
|
1747
|
+
|
|
1748
|
+
for r in range(T1.nrows()):
|
|
1749
|
+
for c in range(T1.ncols()):
|
|
1750
|
+
e = T1[r, c]
|
|
1751
|
+
|
|
1752
|
+
if e < 0:
|
|
1753
|
+
continue
|
|
1754
|
+
|
|
1755
|
+
(r2, c2, e2) = beta2((r, c, e), T1, T2)
|
|
1756
|
+
(r3, c3, e3) = beta3((r2, c2, e2), T2, T1)
|
|
1757
|
+
|
|
1758
|
+
x[cells_map[(r, c)]] = cells_map[(r3, c3)]
|
|
1759
|
+
|
|
1760
|
+
x.pop(0)
|
|
1761
|
+
# remove the head of the list since we
|
|
1762
|
+
# have permutations on 1..(something).
|
|
1763
|
+
|
|
1764
|
+
return Permutation(x)
|
|
1765
|
+
|
|
1766
|
+
|
|
1767
|
+
def tau2(T1, T2, cells_map):
|
|
1768
|
+
r"""
|
|
1769
|
+
The definition of `\tau_2` is
|
|
1770
|
+
|
|
1771
|
+
.. MATH::
|
|
1772
|
+
|
|
1773
|
+
\tau_2 : T1 \rightarrow T1 \\
|
|
1774
|
+
\tau_2 = \beta_3^{-1} \beta_1
|
|
1775
|
+
|
|
1776
|
+
where the composition is left to right and `\beta_i : T2 \rightarrow T1`
|
|
1777
|
+
changes just the `i`-th coordinate of a triple.
|
|
1778
|
+
|
|
1779
|
+
EXAMPLES::
|
|
1780
|
+
|
|
1781
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1782
|
+
sage: T1 = back_circulant(5)
|
|
1783
|
+
sage: x = isotopism( (0,1,2,3,4) )
|
|
1784
|
+
sage: y = isotopism(5) # identity
|
|
1785
|
+
sage: z = isotopism(5) # identity
|
|
1786
|
+
sage: T2 = T1.apply_isotopism(x, y, z)
|
|
1787
|
+
sage: is_bitrade(T1, T2)
|
|
1788
|
+
True
|
|
1789
|
+
sage: (cells_map, t1, t2, t3) = tau123(T1, T2)
|
|
1790
|
+
sage: t2 = tau2(T1, T2, cells_map)
|
|
1791
|
+
sage: t2
|
|
1792
|
+
[21, 22, 23, 24, 25, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
|
|
1793
|
+
sage: t2.to_cycles()
|
|
1794
|
+
[(1, 21, 16, 11, 6), (2, 22, 17, 12, 7), (3, 23, 18, 13, 8), (4, 24, 19, 14, 9), (5, 25, 20, 15, 10)]
|
|
1795
|
+
"""
|
|
1796
|
+
|
|
1797
|
+
# The cells_map has both directions, i.e. integer to
|
|
1798
|
+
# cell and cell to integer, so the size of T1 is
|
|
1799
|
+
# just half of len(cells_map).
|
|
1800
|
+
x = (int(len(cells_map)/2) + 1) * [-1]
|
|
1801
|
+
|
|
1802
|
+
for r in range(T1.nrows()):
|
|
1803
|
+
for c in range(T1.ncols()):
|
|
1804
|
+
e = T1[r, c]
|
|
1805
|
+
|
|
1806
|
+
if e < 0:
|
|
1807
|
+
continue
|
|
1808
|
+
|
|
1809
|
+
(r2, c2, e2) = beta3((r, c, e), T1, T2)
|
|
1810
|
+
(r3, c3, e3) = beta1((r2, c2, e2), T2, T1)
|
|
1811
|
+
|
|
1812
|
+
x[cells_map[(r, c)]] = cells_map[(r3, c3)]
|
|
1813
|
+
|
|
1814
|
+
x.pop(0)
|
|
1815
|
+
# remove the head of the list since we
|
|
1816
|
+
# have permutations on 1..(something).
|
|
1817
|
+
|
|
1818
|
+
return Permutation(x)
|
|
1819
|
+
|
|
1820
|
+
|
|
1821
|
+
def tau3(T1, T2, cells_map):
|
|
1822
|
+
r"""
|
|
1823
|
+
The definition of `\tau_3` is
|
|
1824
|
+
|
|
1825
|
+
.. MATH::
|
|
1826
|
+
|
|
1827
|
+
\tau_3 : T1 \rightarrow T1 \\
|
|
1828
|
+
\tau_3 = \beta_1^{-1} \beta_2
|
|
1829
|
+
|
|
1830
|
+
where the composition is left to right and `\beta_i : T2 \rightarrow T1`
|
|
1831
|
+
changes just the `i`-th coordinate of a triple.
|
|
1832
|
+
|
|
1833
|
+
EXAMPLES::
|
|
1834
|
+
|
|
1835
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1836
|
+
sage: T1 = back_circulant(5)
|
|
1837
|
+
sage: x = isotopism( (0,1,2,3,4) )
|
|
1838
|
+
sage: y = isotopism(5) # identity
|
|
1839
|
+
sage: z = isotopism(5) # identity
|
|
1840
|
+
sage: T2 = T1.apply_isotopism(x, y, z)
|
|
1841
|
+
sage: is_bitrade(T1, T2)
|
|
1842
|
+
True
|
|
1843
|
+
sage: (cells_map, t1, t2, t3) = tau123(T1, T2)
|
|
1844
|
+
sage: t3 = tau3(T1, T2, cells_map)
|
|
1845
|
+
sage: t3
|
|
1846
|
+
[10, 6, 7, 8, 9, 15, 11, 12, 13, 14, 20, 16, 17, 18, 19, 25, 21, 22, 23, 24, 5, 1, 2, 3, 4]
|
|
1847
|
+
sage: t3.to_cycles()
|
|
1848
|
+
[(1, 10, 14, 18, 22), (2, 6, 15, 19, 23), (3, 7, 11, 20, 24), (4, 8, 12, 16, 25), (5, 9, 13, 17, 21)]
|
|
1849
|
+
"""
|
|
1850
|
+
|
|
1851
|
+
# The cells_map has both directions, i.e. integer to
|
|
1852
|
+
# cell and cell to integer, so the size of T1 is
|
|
1853
|
+
# just half of len(cells_map).
|
|
1854
|
+
x = (int(len(cells_map)/2) + 1) * [-1]
|
|
1855
|
+
|
|
1856
|
+
for r in range(T1.nrows()):
|
|
1857
|
+
for c in range(T1.ncols()):
|
|
1858
|
+
e = T1[r, c]
|
|
1859
|
+
|
|
1860
|
+
if e < 0:
|
|
1861
|
+
continue
|
|
1862
|
+
|
|
1863
|
+
(r2, c2, e2) = beta1((r, c, e), T1, T2)
|
|
1864
|
+
(r3, c3, e3) = beta2((r2, c2, e2), T2, T1)
|
|
1865
|
+
|
|
1866
|
+
x[cells_map[(r, c)]] = cells_map[(r3, c3)]
|
|
1867
|
+
|
|
1868
|
+
x.pop(0)
|
|
1869
|
+
# remove the head of the list since we
|
|
1870
|
+
# have permutations on 1..(something).
|
|
1871
|
+
|
|
1872
|
+
return Permutation(x)
|
|
1873
|
+
|
|
1874
|
+
|
|
1875
|
+
def back_circulant(n):
|
|
1876
|
+
"""
|
|
1877
|
+
The back-circulant latin square of order n is the Cayley table for
|
|
1878
|
+
(Z_n, +), the integers under addition modulo n.
|
|
1879
|
+
|
|
1880
|
+
INPUT:
|
|
1881
|
+
|
|
1882
|
+
- ``n`` -- integer; order of the latin square
|
|
1883
|
+
|
|
1884
|
+
EXAMPLES::
|
|
1885
|
+
|
|
1886
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1887
|
+
sage: back_circulant(5)
|
|
1888
|
+
[0 1 2 3 4]
|
|
1889
|
+
[1 2 3 4 0]
|
|
1890
|
+
[2 3 4 0 1]
|
|
1891
|
+
[3 4 0 1 2]
|
|
1892
|
+
[4 0 1 2 3]
|
|
1893
|
+
"""
|
|
1894
|
+
assert n >= 1
|
|
1895
|
+
|
|
1896
|
+
L = LatinSquare(n, n)
|
|
1897
|
+
|
|
1898
|
+
for r in range(n):
|
|
1899
|
+
for c in range(n):
|
|
1900
|
+
L[r, c] = (r + c) % n
|
|
1901
|
+
|
|
1902
|
+
return L
|
|
1903
|
+
|
|
1904
|
+
|
|
1905
|
+
def forward_circulant(n):
|
|
1906
|
+
"""
|
|
1907
|
+
The forward-circulant latin square of order n is the Cayley table
|
|
1908
|
+
for the operation r + c = (n-c+r) mod n.
|
|
1909
|
+
|
|
1910
|
+
INPUT:
|
|
1911
|
+
|
|
1912
|
+
- ``n`` -- integer; order of the latin square
|
|
1913
|
+
|
|
1914
|
+
EXAMPLES::
|
|
1915
|
+
|
|
1916
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1917
|
+
sage: forward_circulant(5)
|
|
1918
|
+
[0 4 3 2 1]
|
|
1919
|
+
[1 0 4 3 2]
|
|
1920
|
+
[2 1 0 4 3]
|
|
1921
|
+
[3 2 1 0 4]
|
|
1922
|
+
[4 3 2 1 0]
|
|
1923
|
+
"""
|
|
1924
|
+
assert n >= 1
|
|
1925
|
+
|
|
1926
|
+
L = LatinSquare(n, n)
|
|
1927
|
+
|
|
1928
|
+
for r in range(n):
|
|
1929
|
+
for c in range(n):
|
|
1930
|
+
L[r, c] = (n-c+r) % n
|
|
1931
|
+
|
|
1932
|
+
return L
|
|
1933
|
+
|
|
1934
|
+
|
|
1935
|
+
def direct_product(L1, L2, L3, L4):
|
|
1936
|
+
"""
|
|
1937
|
+
The 'direct product' of four latin squares L1, L2, L3, L4 of order
|
|
1938
|
+
n is the latin square of order 2n consisting of
|
|
1939
|
+
|
|
1940
|
+
::
|
|
1941
|
+
|
|
1942
|
+
-----------
|
|
1943
|
+
| L1 | L2 |
|
|
1944
|
+
-----------
|
|
1945
|
+
| L3 | L4 |
|
|
1946
|
+
-----------
|
|
1947
|
+
|
|
1948
|
+
where the subsquares L2 and L3 have entries offset by n.
|
|
1949
|
+
|
|
1950
|
+
EXAMPLES::
|
|
1951
|
+
|
|
1952
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1953
|
+
sage: direct_product(back_circulant(4), back_circulant(4), elementary_abelian_2group(2), elementary_abelian_2group(2))
|
|
1954
|
+
[0 1 2 3 4 5 6 7]
|
|
1955
|
+
[1 2 3 0 5 6 7 4]
|
|
1956
|
+
[2 3 0 1 6 7 4 5]
|
|
1957
|
+
[3 0 1 2 7 4 5 6]
|
|
1958
|
+
[4 5 6 7 0 1 2 3]
|
|
1959
|
+
[5 4 7 6 1 0 3 2]
|
|
1960
|
+
[6 7 4 5 2 3 0 1]
|
|
1961
|
+
[7 6 5 4 3 2 1 0]
|
|
1962
|
+
"""
|
|
1963
|
+
assert L1.nrows() == L2.nrows() == L3.nrows() == L4.nrows()
|
|
1964
|
+
assert L1.ncols() == L2.ncols() == L3.ncols() == L4.ncols()
|
|
1965
|
+
assert L1.nrows() == L1.ncols()
|
|
1966
|
+
|
|
1967
|
+
n = L1.nrows()
|
|
1968
|
+
|
|
1969
|
+
D = LatinSquare(2*n, 2*n)
|
|
1970
|
+
|
|
1971
|
+
for r in range(n):
|
|
1972
|
+
for c in range(n):
|
|
1973
|
+
D[r, c] = L1[r, c]
|
|
1974
|
+
D[r, c+n] = L2[r, c] + n
|
|
1975
|
+
D[r+n, c] = L3[r, c] + n
|
|
1976
|
+
D[r+n, c+n] = L4[r, c]
|
|
1977
|
+
|
|
1978
|
+
return D
|
|
1979
|
+
|
|
1980
|
+
|
|
1981
|
+
def elementary_abelian_2group(s):
|
|
1982
|
+
"""
|
|
1983
|
+
Return the latin square based on the Cayley table for the
|
|
1984
|
+
elementary abelian 2-group of order 2s.
|
|
1985
|
+
|
|
1986
|
+
INPUT:
|
|
1987
|
+
|
|
1988
|
+
- ``s`` -- integer; order of the latin square will be 2s
|
|
1989
|
+
|
|
1990
|
+
EXAMPLES::
|
|
1991
|
+
|
|
1992
|
+
sage: from sage.combinat.matrices.latin import *
|
|
1993
|
+
sage: elementary_abelian_2group(3)
|
|
1994
|
+
[0 1 2 3 4 5 6 7]
|
|
1995
|
+
[1 0 3 2 5 4 7 6]
|
|
1996
|
+
[2 3 0 1 6 7 4 5]
|
|
1997
|
+
[3 2 1 0 7 6 5 4]
|
|
1998
|
+
[4 5 6 7 0 1 2 3]
|
|
1999
|
+
[5 4 7 6 1 0 3 2]
|
|
2000
|
+
[6 7 4 5 2 3 0 1]
|
|
2001
|
+
[7 6 5 4 3 2 1 0]
|
|
2002
|
+
"""
|
|
2003
|
+
assert s > 0
|
|
2004
|
+
|
|
2005
|
+
if s == 1:
|
|
2006
|
+
L = LatinSquare(2, 2)
|
|
2007
|
+
L[0, 0] = 0
|
|
2008
|
+
L[0, 1] = 1
|
|
2009
|
+
L[1, 0] = 1
|
|
2010
|
+
L[1, 1] = 0
|
|
2011
|
+
|
|
2012
|
+
return L
|
|
2013
|
+
else:
|
|
2014
|
+
L_prev = elementary_abelian_2group(s-1)
|
|
2015
|
+
L = LatinSquare(2**s, 2**s)
|
|
2016
|
+
|
|
2017
|
+
offset = L.nrows() // 2
|
|
2018
|
+
|
|
2019
|
+
for r in range(L_prev.nrows()):
|
|
2020
|
+
for c in range(L_prev.ncols()):
|
|
2021
|
+
L[r, c] = L_prev[r, c]
|
|
2022
|
+
L[r+offset, c] = L_prev[r, c] + offset
|
|
2023
|
+
L[r, c+offset] = L_prev[r, c] + offset
|
|
2024
|
+
L[r+offset, c+offset] = L_prev[r, c]
|
|
2025
|
+
return L
|
|
2026
|
+
|
|
2027
|
+
|
|
2028
|
+
def coin():
|
|
2029
|
+
"""
|
|
2030
|
+
Simulate a fair coin (returns ``True`` or ``False``) using
|
|
2031
|
+
ZZ.random_element(2).
|
|
2032
|
+
|
|
2033
|
+
EXAMPLES::
|
|
2034
|
+
|
|
2035
|
+
sage: from sage.combinat.matrices.latin import coin
|
|
2036
|
+
sage: x = coin()
|
|
2037
|
+
sage: x == 0 or x == 1
|
|
2038
|
+
True
|
|
2039
|
+
"""
|
|
2040
|
+
return ZZ.random_element(2) == 0
|
|
2041
|
+
|
|
2042
|
+
|
|
2043
|
+
def next_conjugate(L):
|
|
2044
|
+
"""
|
|
2045
|
+
Permute L[r, c] = e to the conjugate L[c, e] = r.
|
|
2046
|
+
|
|
2047
|
+
We assume that L is an n by n matrix and has values in the range 0,
|
|
2048
|
+
1, ..., n-1.
|
|
2049
|
+
|
|
2050
|
+
EXAMPLES::
|
|
2051
|
+
|
|
2052
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2053
|
+
sage: L = back_circulant(6)
|
|
2054
|
+
sage: L
|
|
2055
|
+
[0 1 2 3 4 5]
|
|
2056
|
+
[1 2 3 4 5 0]
|
|
2057
|
+
[2 3 4 5 0 1]
|
|
2058
|
+
[3 4 5 0 1 2]
|
|
2059
|
+
[4 5 0 1 2 3]
|
|
2060
|
+
[5 0 1 2 3 4]
|
|
2061
|
+
sage: next_conjugate(L)
|
|
2062
|
+
[0 1 2 3 4 5]
|
|
2063
|
+
[5 0 1 2 3 4]
|
|
2064
|
+
[4 5 0 1 2 3]
|
|
2065
|
+
[3 4 5 0 1 2]
|
|
2066
|
+
[2 3 4 5 0 1]
|
|
2067
|
+
[1 2 3 4 5 0]
|
|
2068
|
+
sage: L == next_conjugate(next_conjugate(next_conjugate(L)))
|
|
2069
|
+
True
|
|
2070
|
+
"""
|
|
2071
|
+
assert L.nrows() == L.ncols()
|
|
2072
|
+
|
|
2073
|
+
n = L.nrows()
|
|
2074
|
+
|
|
2075
|
+
C = LatinSquare(n, n)
|
|
2076
|
+
|
|
2077
|
+
for r in range(n):
|
|
2078
|
+
for c in range(n):
|
|
2079
|
+
e = L[r, c]
|
|
2080
|
+
assert e >= 0 and e < n
|
|
2081
|
+
|
|
2082
|
+
C[c, e] = r
|
|
2083
|
+
|
|
2084
|
+
return C
|
|
2085
|
+
|
|
2086
|
+
|
|
2087
|
+
def row_containing_sym(L, c, x):
|
|
2088
|
+
"""
|
|
2089
|
+
Given an improper latin square L with L[r1, c] = L[r2, c] = x,
|
|
2090
|
+
return r1 or r2 with equal probability. This is an internal
|
|
2091
|
+
function and should only be used in LatinSquare_generator().
|
|
2092
|
+
|
|
2093
|
+
EXAMPLES::
|
|
2094
|
+
|
|
2095
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2096
|
+
sage: L = matrix([(0, 1, 0, 3), (3, 0, 2, 1), (1, 0, 3, 2), (2, 3, 1, 0)])
|
|
2097
|
+
sage: L
|
|
2098
|
+
[0 1 0 3]
|
|
2099
|
+
[3 0 2 1]
|
|
2100
|
+
[1 0 3 2]
|
|
2101
|
+
[2 3 1 0]
|
|
2102
|
+
sage: c = row_containing_sym(L, 1, 0)
|
|
2103
|
+
sage: c == 1 or c == 2
|
|
2104
|
+
True
|
|
2105
|
+
"""
|
|
2106
|
+
r1 = -1
|
|
2107
|
+
r2 = -1
|
|
2108
|
+
|
|
2109
|
+
for r in range(L.nrows()):
|
|
2110
|
+
if r1 >= 0 and r2 >= 0:
|
|
2111
|
+
break
|
|
2112
|
+
|
|
2113
|
+
if L[r, c] == x and r1 < 0:
|
|
2114
|
+
r1 = r
|
|
2115
|
+
continue
|
|
2116
|
+
|
|
2117
|
+
if L[r, c] == x and r2 < 0:
|
|
2118
|
+
r2 = r
|
|
2119
|
+
break
|
|
2120
|
+
|
|
2121
|
+
assert r1 >= 0 and r2 >= 0
|
|
2122
|
+
|
|
2123
|
+
return r1 if coin() else r2
|
|
2124
|
+
|
|
2125
|
+
|
|
2126
|
+
def column_containing_sym(L, r, x):
|
|
2127
|
+
"""
|
|
2128
|
+
Given an improper latin square L with L[r, c1] = L[r, c2] = x,
|
|
2129
|
+
return c1 or c2 with equal probability. This is an internal
|
|
2130
|
+
function and should only be used in LatinSquare_generator().
|
|
2131
|
+
|
|
2132
|
+
EXAMPLES::
|
|
2133
|
+
|
|
2134
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2135
|
+
sage: L = matrix([(1, 0, 2, 3), (0, 2, 3, 0), (2, 3, 0, 1), (3, 0, 1, 2)])
|
|
2136
|
+
sage: L
|
|
2137
|
+
[1 0 2 3]
|
|
2138
|
+
[0 2 3 0]
|
|
2139
|
+
[2 3 0 1]
|
|
2140
|
+
[3 0 1 2]
|
|
2141
|
+
sage: c = column_containing_sym(L, 1, 0)
|
|
2142
|
+
sage: c == 0 or c == 3
|
|
2143
|
+
True
|
|
2144
|
+
"""
|
|
2145
|
+
|
|
2146
|
+
c1 = -1
|
|
2147
|
+
c2 = -1
|
|
2148
|
+
|
|
2149
|
+
for c in range(L.ncols()):
|
|
2150
|
+
if c1 >= 0 and c2 >= 0:
|
|
2151
|
+
break
|
|
2152
|
+
|
|
2153
|
+
if L[r, c] == x and c1 < 0:
|
|
2154
|
+
c1 = c
|
|
2155
|
+
continue
|
|
2156
|
+
|
|
2157
|
+
if L[r, c] == x and c2 < 0:
|
|
2158
|
+
c2 = c
|
|
2159
|
+
break
|
|
2160
|
+
|
|
2161
|
+
assert c1 >= 0 and c2 >= 0
|
|
2162
|
+
|
|
2163
|
+
return c1 if coin() else c2
|
|
2164
|
+
|
|
2165
|
+
|
|
2166
|
+
def LatinSquare_generator(L_start, check_assertions=False):
|
|
2167
|
+
"""
|
|
2168
|
+
Generator for a sequence of uniformly distributed latin squares,
|
|
2169
|
+
given L_start as the initial latin square.
|
|
2170
|
+
|
|
2171
|
+
This code implements
|
|
2172
|
+
the Markov chain algorithm of Jacobson and Matthews (1996), see
|
|
2173
|
+
below for the BibTex entry. This generator will never throw the
|
|
2174
|
+
``StopIteration`` exception, so it provides an infinite sequence of
|
|
2175
|
+
latin squares.
|
|
2176
|
+
|
|
2177
|
+
EXAMPLES:
|
|
2178
|
+
|
|
2179
|
+
Use the back circulant latin square of order 4 as the initial
|
|
2180
|
+
square and print the next two latin squares given by the Markov
|
|
2181
|
+
chain::
|
|
2182
|
+
|
|
2183
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2184
|
+
sage: g = LatinSquare_generator(back_circulant(4))
|
|
2185
|
+
sage: next(g).is_latin_square()
|
|
2186
|
+
True
|
|
2187
|
+
|
|
2188
|
+
REFERENCES:
|
|
2189
|
+
|
|
2190
|
+
.. [JacMat96] Mark T. Jacobson and Peter Matthews, "Generating uniformly
|
|
2191
|
+
distributed random Latin squares", Journal of Combinatorial Designs,
|
|
2192
|
+
4 (1996)
|
|
2193
|
+
"""
|
|
2194
|
+
if check_assertions:
|
|
2195
|
+
assert L_start.is_latin_square()
|
|
2196
|
+
|
|
2197
|
+
n = L_start.nrows()
|
|
2198
|
+
|
|
2199
|
+
r1 = r2 = c1 = c2 = x = y = z = -1
|
|
2200
|
+
proper = True
|
|
2201
|
+
|
|
2202
|
+
from copy import copy
|
|
2203
|
+
L = copy(L_start)
|
|
2204
|
+
|
|
2205
|
+
L_cer = LatinSquare(n, n)
|
|
2206
|
+
L_erc = LatinSquare(n, n)
|
|
2207
|
+
|
|
2208
|
+
while True:
|
|
2209
|
+
if proper:
|
|
2210
|
+
if check_assertions:
|
|
2211
|
+
assert L.is_latin_square()
|
|
2212
|
+
|
|
2213
|
+
#################################
|
|
2214
|
+
# Update the other two conjugates
|
|
2215
|
+
for r in range(n):
|
|
2216
|
+
for c in range(n):
|
|
2217
|
+
e = L[r, c]
|
|
2218
|
+
|
|
2219
|
+
L_cer[c, e] = r
|
|
2220
|
+
L_erc[e, r] = c
|
|
2221
|
+
#################################
|
|
2222
|
+
|
|
2223
|
+
yield L
|
|
2224
|
+
|
|
2225
|
+
r1 = ZZ.random_element(n)
|
|
2226
|
+
c1 = ZZ.random_element(n)
|
|
2227
|
+
x = L[r1, c1]
|
|
2228
|
+
|
|
2229
|
+
y = x
|
|
2230
|
+
while y == x:
|
|
2231
|
+
y = ZZ.random_element(n)
|
|
2232
|
+
|
|
2233
|
+
# Now find y in row r1 and column c1.
|
|
2234
|
+
if check_assertions:
|
|
2235
|
+
r2 = 0
|
|
2236
|
+
c2 = 0
|
|
2237
|
+
while L[r1, c2] != y:
|
|
2238
|
+
c2 += 1
|
|
2239
|
+
while L[r2, c1] != y:
|
|
2240
|
+
r2 += 1
|
|
2241
|
+
|
|
2242
|
+
assert L_erc[y, r1] == c2
|
|
2243
|
+
assert L_cer[c1, y] == r2
|
|
2244
|
+
|
|
2245
|
+
c2 = L_erc[y, r1]
|
|
2246
|
+
r2 = L_cer[c1, y]
|
|
2247
|
+
|
|
2248
|
+
if check_assertions:
|
|
2249
|
+
assert L[r1, c2] == y
|
|
2250
|
+
if check_assertions:
|
|
2251
|
+
assert L[r2, c1] == y
|
|
2252
|
+
|
|
2253
|
+
L[r1, c1] = y
|
|
2254
|
+
L[r1, c2] = x
|
|
2255
|
+
L[r2, c1] = x
|
|
2256
|
+
|
|
2257
|
+
# Now deal with the unknown point.
|
|
2258
|
+
# We want to form z + (y - x)
|
|
2259
|
+
z = L[r2, c2]
|
|
2260
|
+
|
|
2261
|
+
if z == x:
|
|
2262
|
+
L[r2, c2] = y
|
|
2263
|
+
else:
|
|
2264
|
+
# z and y have positive coefficients
|
|
2265
|
+
# x is the improper term with a negative coefficient
|
|
2266
|
+
proper = False
|
|
2267
|
+
else: # improper square,
|
|
2268
|
+
# L[r2, c2] = y + z - x
|
|
2269
|
+
# y and z are proper while x is the
|
|
2270
|
+
# improper symbol in the cell L[r2, c2].
|
|
2271
|
+
|
|
2272
|
+
r1 = row_containing_sym(L, c2, x)
|
|
2273
|
+
c1 = column_containing_sym(L, r2, x)
|
|
2274
|
+
|
|
2275
|
+
if check_assertions:
|
|
2276
|
+
assert L[r1, c2] == x
|
|
2277
|
+
if check_assertions:
|
|
2278
|
+
assert L[r2, c1] == x
|
|
2279
|
+
|
|
2280
|
+
# choose one of the proper symbols
|
|
2281
|
+
# uniformly at random (we will use whatever
|
|
2282
|
+
# lands in variable y).
|
|
2283
|
+
if coin():
|
|
2284
|
+
y, z = z, y
|
|
2285
|
+
|
|
2286
|
+
# Add/subtract the symbolic difference (y - x)
|
|
2287
|
+
L[r2, c2] = z
|
|
2288
|
+
L[r1, c2] = y
|
|
2289
|
+
L[r2, c1] = y
|
|
2290
|
+
|
|
2291
|
+
if L[r1, c1] == y:
|
|
2292
|
+
L[r1, c1] = x
|
|
2293
|
+
proper = True
|
|
2294
|
+
else: # got another improper square
|
|
2295
|
+
z = L[r1, c1]
|
|
2296
|
+
x, y = y, x
|
|
2297
|
+
r2 = r1
|
|
2298
|
+
c2 = c1
|
|
2299
|
+
|
|
2300
|
+
# Now we have L[r2, c2] = z+y-x as
|
|
2301
|
+
# usual
|
|
2302
|
+
proper = False # for emphasis
|
|
2303
|
+
|
|
2304
|
+
|
|
2305
|
+
def group_to_LatinSquare(G):
|
|
2306
|
+
"""
|
|
2307
|
+
Construct a latin square on the symbols [0, 1, ..., n-1] for a
|
|
2308
|
+
group with an n by n Cayley table.
|
|
2309
|
+
|
|
2310
|
+
EXAMPLES::
|
|
2311
|
+
|
|
2312
|
+
sage: from sage.combinat.matrices.latin import group_to_LatinSquare
|
|
2313
|
+
|
|
2314
|
+
sage: group_to_LatinSquare(DihedralGroup(2))
|
|
2315
|
+
[0 1 2 3]
|
|
2316
|
+
[1 0 3 2]
|
|
2317
|
+
[2 3 0 1]
|
|
2318
|
+
[3 2 1 0]
|
|
2319
|
+
|
|
2320
|
+
::
|
|
2321
|
+
|
|
2322
|
+
sage: G = libgap.Group(PermutationGroupElement((1,2,3)))
|
|
2323
|
+
sage: group_to_LatinSquare(G)
|
|
2324
|
+
[0 1 2]
|
|
2325
|
+
[1 2 0]
|
|
2326
|
+
[2 0 1]
|
|
2327
|
+
"""
|
|
2328
|
+
if isinstance(G, GapElement):
|
|
2329
|
+
rows = (list(x) for x in list(libgap.MultiplicationTable(G)))
|
|
2330
|
+
new_rows = []
|
|
2331
|
+
|
|
2332
|
+
for x in rows:
|
|
2333
|
+
new_rows.append([int(xx) - 1 for xx in x])
|
|
2334
|
+
|
|
2335
|
+
return matrix(new_rows)
|
|
2336
|
+
|
|
2337
|
+
# Otherwise we must have some kind of Sage permutation group object,
|
|
2338
|
+
# such as sage.groups.perm_gps.permgroup.PermutationGroup_generic
|
|
2339
|
+
# or maybe sage.groups.perm_gps.permgroup_named.
|
|
2340
|
+
|
|
2341
|
+
T = G.cayley_table()
|
|
2342
|
+
return matrix(ZZ, T.table())
|
|
2343
|
+
|
|
2344
|
+
|
|
2345
|
+
def alternating_group_bitrade_generators(m):
|
|
2346
|
+
r"""
|
|
2347
|
+
Construct generators a, b, c for the alternating group on 3m+1
|
|
2348
|
+
points, such that a\*b\*c = 1.
|
|
2349
|
+
|
|
2350
|
+
EXAMPLES::
|
|
2351
|
+
|
|
2352
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2353
|
+
sage: a, b, c, G = alternating_group_bitrade_generators(1)
|
|
2354
|
+
sage: (a, b, c, G)
|
|
2355
|
+
((1,2,3), (1,4,2), (2,4,3), Permutation Group with generators [(1,2,3), (1,4,2)])
|
|
2356
|
+
sage: a*b*c
|
|
2357
|
+
()
|
|
2358
|
+
|
|
2359
|
+
::
|
|
2360
|
+
|
|
2361
|
+
sage: (T1, T2) = bitrade_from_group(a, b, c, G)
|
|
2362
|
+
sage: T1
|
|
2363
|
+
[ 0 -1 3 1]
|
|
2364
|
+
[-1 1 0 2]
|
|
2365
|
+
[ 1 3 2 -1]
|
|
2366
|
+
[ 2 0 -1 3]
|
|
2367
|
+
sage: T2
|
|
2368
|
+
[ 1 -1 0 3]
|
|
2369
|
+
[-1 0 2 1]
|
|
2370
|
+
[ 2 1 3 -1]
|
|
2371
|
+
[ 0 3 -1 2]
|
|
2372
|
+
"""
|
|
2373
|
+
assert m >= 1
|
|
2374
|
+
|
|
2375
|
+
a = tuple(range(1, 2*m+1 + 1))
|
|
2376
|
+
|
|
2377
|
+
b = tuple(range(m + 1, 0, -1)) + tuple(range(2*m+2, 3*m+1 + 1))
|
|
2378
|
+
|
|
2379
|
+
a = PermutationConstructor(a)
|
|
2380
|
+
b = PermutationConstructor(b)
|
|
2381
|
+
c = PermutationConstructor((a*b)**(-1))
|
|
2382
|
+
|
|
2383
|
+
G = PermutationGroup([a, b])
|
|
2384
|
+
|
|
2385
|
+
return (a, b, c, G)
|
|
2386
|
+
|
|
2387
|
+
|
|
2388
|
+
def pq_group_bitrade_generators(p, q):
|
|
2389
|
+
"""
|
|
2390
|
+
Generators for a group of order pq where p and q are primes such
|
|
2391
|
+
that (q % p) == 1.
|
|
2392
|
+
|
|
2393
|
+
EXAMPLES::
|
|
2394
|
+
|
|
2395
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2396
|
+
sage: pq_group_bitrade_generators(3,7)
|
|
2397
|
+
((2,3,5)(4,7,6), (1,2,3,4,5,6,7), (1,4,2)(3,5,6), Permutation Group with generators [(2,3,5)(4,7,6), (1,2,3,4,5,6,7)])
|
|
2398
|
+
"""
|
|
2399
|
+
assert is_prime(p)
|
|
2400
|
+
assert is_prime(q)
|
|
2401
|
+
assert (q % p) == 1
|
|
2402
|
+
|
|
2403
|
+
# beta is a primitive root of the
|
|
2404
|
+
# congruence x^p = 1 mod q
|
|
2405
|
+
F = FiniteField(q)
|
|
2406
|
+
fgen = F.multiplicative_generator()
|
|
2407
|
+
beta = fgen**((q-1)/p)
|
|
2408
|
+
|
|
2409
|
+
assert beta != 1
|
|
2410
|
+
assert (beta**p % q) == 1
|
|
2411
|
+
|
|
2412
|
+
Q = tuple(range(1, q+1))
|
|
2413
|
+
|
|
2414
|
+
P = []
|
|
2415
|
+
seenValues = {}
|
|
2416
|
+
for i in range(2, q):
|
|
2417
|
+
if i in seenValues:
|
|
2418
|
+
continue
|
|
2419
|
+
|
|
2420
|
+
cycle = []
|
|
2421
|
+
for k in range(p):
|
|
2422
|
+
x = (1 + (i-1)*beta**k) % q
|
|
2423
|
+
if x == 0:
|
|
2424
|
+
x = q
|
|
2425
|
+
|
|
2426
|
+
seenValues[x] = True
|
|
2427
|
+
cycle.append(x)
|
|
2428
|
+
P.append(tuple(map(Integer, cycle)))
|
|
2429
|
+
|
|
2430
|
+
G = PermutationGroup([P, Q])
|
|
2431
|
+
assert G.order() == p*q
|
|
2432
|
+
assert not G.is_abelian()
|
|
2433
|
+
|
|
2434
|
+
a = PermutationConstructor(P)
|
|
2435
|
+
b = PermutationConstructor(Q)
|
|
2436
|
+
c = PermutationConstructor((a*b)**(-1))
|
|
2437
|
+
|
|
2438
|
+
return (a, b, c, PermutationGroup([P, Q]))
|
|
2439
|
+
|
|
2440
|
+
|
|
2441
|
+
def p3_group_bitrade_generators(p):
|
|
2442
|
+
"""
|
|
2443
|
+
Generators for a group of order p3 where p is a prime.
|
|
2444
|
+
|
|
2445
|
+
EXAMPLES::
|
|
2446
|
+
|
|
2447
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2448
|
+
sage: p3_group_bitrade_generators(3) # random output
|
|
2449
|
+
((2,6,7)(3,8,9),
|
|
2450
|
+
(1,2,3)(4,7,8)(5,6,9),
|
|
2451
|
+
(1,9,2)(3,7,4)(5,8,6),
|
|
2452
|
+
Permutation Group with generators [(2,6,7)(3,8,9), (1,2,3)(4,7,8)(5,6,9)])
|
|
2453
|
+
"""
|
|
2454
|
+
assert is_prime(p)
|
|
2455
|
+
|
|
2456
|
+
F = libgap.FreeGroup(3)
|
|
2457
|
+
a, b, c = F.GeneratorsOfGroup()
|
|
2458
|
+
|
|
2459
|
+
rels = []
|
|
2460
|
+
rels.append(a**p)
|
|
2461
|
+
rels.append(b**p)
|
|
2462
|
+
rels.append(c**p)
|
|
2463
|
+
rels.append(a*b*((b*a*c)**(-1)))
|
|
2464
|
+
rels.append(c*a*((a*c)**(-1)))
|
|
2465
|
+
rels.append(c*b*((b*c)**(-1)))
|
|
2466
|
+
|
|
2467
|
+
G = F.FactorGroupFpGroupByRels(rels)
|
|
2468
|
+
u, v, _ = G.GeneratorsOfGroup()
|
|
2469
|
+
|
|
2470
|
+
iso = libgap.IsomorphismPermGroup(G)
|
|
2471
|
+
|
|
2472
|
+
x = PermutationConstructor(libgap.Image(iso, u))
|
|
2473
|
+
y = PermutationConstructor(libgap.Image(iso, v))
|
|
2474
|
+
|
|
2475
|
+
return (x, y, (x*y)**(-1), PermutationGroup([x, y]))
|
|
2476
|
+
|
|
2477
|
+
|
|
2478
|
+
def check_bitrade_generators(a, b, c):
|
|
2479
|
+
r"""
|
|
2480
|
+
Three group elements a, b, c will generate a bitrade if a\*b\*c = 1
|
|
2481
|
+
and the subgroups a, b, c intersect (pairwise) in just the
|
|
2482
|
+
identity.
|
|
2483
|
+
|
|
2484
|
+
EXAMPLES::
|
|
2485
|
+
|
|
2486
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2487
|
+
sage: a, b, c, G = p3_group_bitrade_generators(3)
|
|
2488
|
+
sage: check_bitrade_generators(a, b, c)
|
|
2489
|
+
True
|
|
2490
|
+
sage: check_bitrade_generators(a, b, libgap(gap('()')))
|
|
2491
|
+
False
|
|
2492
|
+
"""
|
|
2493
|
+
A = PermutationGroup([a])
|
|
2494
|
+
B = PermutationGroup([b])
|
|
2495
|
+
C = PermutationGroup([c])
|
|
2496
|
+
|
|
2497
|
+
if a*b != c**(-1):
|
|
2498
|
+
return False
|
|
2499
|
+
|
|
2500
|
+
X = libgap.Intersection(libgap.Intersection(A, B), C)
|
|
2501
|
+
return X.Size() == 1
|
|
2502
|
+
|
|
2503
|
+
|
|
2504
|
+
def is_bitrade(T1, T2):
|
|
2505
|
+
"""
|
|
2506
|
+
Combinatorially, a pair (T1, T2) of partial latin squares is a
|
|
2507
|
+
bitrade if they are disjoint, have the same shape, and have row and
|
|
2508
|
+
column balance. For definitions of each of these terms see the
|
|
2509
|
+
relevant function in this file.
|
|
2510
|
+
|
|
2511
|
+
EXAMPLES::
|
|
2512
|
+
|
|
2513
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2514
|
+
sage: T1 = back_circulant(5)
|
|
2515
|
+
sage: x = isotopism( (0,1,2,3,4) )
|
|
2516
|
+
sage: y = isotopism(5) # identity
|
|
2517
|
+
sage: z = isotopism(5) # identity
|
|
2518
|
+
sage: T2 = T1.apply_isotopism(x, y, z)
|
|
2519
|
+
sage: is_bitrade(T1, T2)
|
|
2520
|
+
True
|
|
2521
|
+
"""
|
|
2522
|
+
return (is_disjoint(T1, T2) and is_same_shape(T1, T2) and
|
|
2523
|
+
is_row_and_col_balanced(T1, T2))
|
|
2524
|
+
|
|
2525
|
+
|
|
2526
|
+
def is_primary_bitrade(a, b, c, G):
|
|
2527
|
+
"""
|
|
2528
|
+
A bitrade generated from elements a, b, c is primary if a, b, c =
|
|
2529
|
+
G.
|
|
2530
|
+
|
|
2531
|
+
EXAMPLES::
|
|
2532
|
+
|
|
2533
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2534
|
+
sage: (a, b, c, G) = p3_group_bitrade_generators(5)
|
|
2535
|
+
sage: is_primary_bitrade(a, b, c, G)
|
|
2536
|
+
True
|
|
2537
|
+
"""
|
|
2538
|
+
return G == PermutationGroup([a, b, c])
|
|
2539
|
+
|
|
2540
|
+
|
|
2541
|
+
def tau_to_bitrade(t1, t2, t3):
|
|
2542
|
+
"""
|
|
2543
|
+
Given permutations t1, t2, t3 that represent a latin bitrade,
|
|
2544
|
+
convert them to an explicit latin bitrade (T1, T2). The result is
|
|
2545
|
+
unique up to isotopism.
|
|
2546
|
+
|
|
2547
|
+
EXAMPLES::
|
|
2548
|
+
|
|
2549
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2550
|
+
sage: T1 = back_circulant(5)
|
|
2551
|
+
sage: x = isotopism( (0,1,2,3,4) )
|
|
2552
|
+
sage: y = isotopism(5) # identity
|
|
2553
|
+
sage: z = isotopism(5) # identity
|
|
2554
|
+
sage: T2 = T1.apply_isotopism(x, y, z)
|
|
2555
|
+
sage: _, t1, t2, t3 = tau123(T1, T2)
|
|
2556
|
+
sage: U1, U2 = tau_to_bitrade(t1, t2, t3)
|
|
2557
|
+
sage: assert is_bitrade(U1, U2)
|
|
2558
|
+
sage: U1
|
|
2559
|
+
[0 1 2 3 4]
|
|
2560
|
+
[1 2 3 4 0]
|
|
2561
|
+
[2 3 4 0 1]
|
|
2562
|
+
[3 4 0 1 2]
|
|
2563
|
+
[4 0 1 2 3]
|
|
2564
|
+
sage: U2
|
|
2565
|
+
[4 0 1 2 3]
|
|
2566
|
+
[0 1 2 3 4]
|
|
2567
|
+
[1 2 3 4 0]
|
|
2568
|
+
[2 3 4 0 1]
|
|
2569
|
+
[3 4 0 1 2]
|
|
2570
|
+
"""
|
|
2571
|
+
c1 = t1.to_cycles()
|
|
2572
|
+
c2 = t2.to_cycles()
|
|
2573
|
+
c3 = t3.to_cycles()
|
|
2574
|
+
|
|
2575
|
+
pt_to_cycle1 = {}
|
|
2576
|
+
pt_to_cycle2 = {}
|
|
2577
|
+
pt_to_cycle3 = {}
|
|
2578
|
+
|
|
2579
|
+
for i in range(len(c1)):
|
|
2580
|
+
for j in range(len(c1[i])):
|
|
2581
|
+
pt_to_cycle1[c1[i][j]] = i
|
|
2582
|
+
|
|
2583
|
+
for i in range(len(c2)):
|
|
2584
|
+
for j in range(len(c2[i])):
|
|
2585
|
+
pt_to_cycle2[c2[i][j]] = i
|
|
2586
|
+
|
|
2587
|
+
for i in range(len(c3)):
|
|
2588
|
+
for j in range(len(c3[i])):
|
|
2589
|
+
pt_to_cycle3[c3[i][j]] = i
|
|
2590
|
+
|
|
2591
|
+
n = max(len(c1), len(c2), len(c3))
|
|
2592
|
+
|
|
2593
|
+
T1 = LatinSquare(n)
|
|
2594
|
+
T2 = LatinSquare(n)
|
|
2595
|
+
|
|
2596
|
+
for r in range(len(c1)):
|
|
2597
|
+
for c in range(len(c2)):
|
|
2598
|
+
for s in range(len(c3)):
|
|
2599
|
+
nr_common = len(reduce(set.intersection,
|
|
2600
|
+
[set(c1[r]), set(c2[c]), set(c3[s])]))
|
|
2601
|
+
assert nr_common in [0, 1]
|
|
2602
|
+
|
|
2603
|
+
if nr_common == 1:
|
|
2604
|
+
T1[r, c] = s
|
|
2605
|
+
|
|
2606
|
+
for cycle in c1:
|
|
2607
|
+
for pt1 in cycle:
|
|
2608
|
+
pt2 = t1[pt1 - 1]
|
|
2609
|
+
pt3 = t2[pt2 - 1]
|
|
2610
|
+
assert t3[pt3 - 1] == pt1
|
|
2611
|
+
|
|
2612
|
+
r = pt_to_cycle1[pt1]
|
|
2613
|
+
c = pt_to_cycle2[pt2]
|
|
2614
|
+
s = pt_to_cycle3[pt3]
|
|
2615
|
+
|
|
2616
|
+
T2[r, c] = s
|
|
2617
|
+
|
|
2618
|
+
return T1, T2
|
|
2619
|
+
|
|
2620
|
+
|
|
2621
|
+
def bitrade_from_group(a, b, c, G):
|
|
2622
|
+
"""
|
|
2623
|
+
Given group elements a, b, c in G such that abc = 1 and the
|
|
2624
|
+
subgroups a, b, c intersect (pairwise) only in the identity,
|
|
2625
|
+
construct a bitrade (T1, T2) where rows, columns, and symbols
|
|
2626
|
+
correspond to cosets of a, b, and c, respectively.
|
|
2627
|
+
|
|
2628
|
+
EXAMPLES::
|
|
2629
|
+
|
|
2630
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2631
|
+
sage: a, b, c, G = alternating_group_bitrade_generators(1)
|
|
2632
|
+
sage: (T1, T2) = bitrade_from_group(a, b, c, G)
|
|
2633
|
+
sage: T1
|
|
2634
|
+
[ 0 -1 3 1]
|
|
2635
|
+
[-1 1 0 2]
|
|
2636
|
+
[ 1 3 2 -1]
|
|
2637
|
+
[ 2 0 -1 3]
|
|
2638
|
+
sage: T2
|
|
2639
|
+
[ 1 -1 0 3]
|
|
2640
|
+
[-1 0 2 1]
|
|
2641
|
+
[ 2 1 3 -1]
|
|
2642
|
+
[ 0 3 -1 2]
|
|
2643
|
+
"""
|
|
2644
|
+
hom = libgap.ActionHomomorphism(G, libgap.RightCosets(G, libgap.TrivialSubgroup(G)), libgap.OnRight)
|
|
2645
|
+
|
|
2646
|
+
t1 = libgap.Image(hom, a)
|
|
2647
|
+
t2 = libgap.Image(hom, b)
|
|
2648
|
+
t3 = libgap.Image(hom, c)
|
|
2649
|
+
|
|
2650
|
+
t1 = Permutation(str(t1).replace('\n', ''))
|
|
2651
|
+
t2 = Permutation(str(t2).replace('\n', ''))
|
|
2652
|
+
t3 = Permutation(str(t3).replace('\n', ''))
|
|
2653
|
+
|
|
2654
|
+
return tau_to_bitrade(t1, t2, t3)
|
|
2655
|
+
|
|
2656
|
+
|
|
2657
|
+
def is_disjoint(T1, T2):
|
|
2658
|
+
"""
|
|
2659
|
+
The partial latin squares T1 and T2 are disjoint if T1[r, c] !=
|
|
2660
|
+
T2[r, c] or T1[r, c] == T2[r, c] == -1 for each cell [r, c].
|
|
2661
|
+
|
|
2662
|
+
EXAMPLES::
|
|
2663
|
+
|
|
2664
|
+
sage: from sage.combinat.matrices.latin import is_disjoint, back_circulant, isotopism
|
|
2665
|
+
sage: is_disjoint(back_circulant(2), back_circulant(2))
|
|
2666
|
+
False
|
|
2667
|
+
|
|
2668
|
+
::
|
|
2669
|
+
|
|
2670
|
+
sage: T1 = back_circulant(5)
|
|
2671
|
+
sage: x = isotopism( (0,1,2,3,4) )
|
|
2672
|
+
sage: y = isotopism(5) # identity
|
|
2673
|
+
sage: z = isotopism(5) # identity
|
|
2674
|
+
sage: T2 = T1.apply_isotopism(x, y, z)
|
|
2675
|
+
sage: is_disjoint(T1, T2)
|
|
2676
|
+
True
|
|
2677
|
+
"""
|
|
2678
|
+
for i in range(T1.nrows()):
|
|
2679
|
+
for j in range(T1.ncols()):
|
|
2680
|
+
if T1[i, j] < 0 and T2[i, j] < 0:
|
|
2681
|
+
continue
|
|
2682
|
+
if T1[i, j] == T2[i, j]:
|
|
2683
|
+
return False
|
|
2684
|
+
|
|
2685
|
+
return True
|
|
2686
|
+
|
|
2687
|
+
|
|
2688
|
+
def is_same_shape(T1, T2):
|
|
2689
|
+
"""
|
|
2690
|
+
Two partial latin squares T1, T2 have the same shape if T1[r, c] =
|
|
2691
|
+
0 if and only if T2[r, c] = 0.
|
|
2692
|
+
|
|
2693
|
+
EXAMPLES::
|
|
2694
|
+
|
|
2695
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2696
|
+
sage: is_same_shape(elementary_abelian_2group(2), back_circulant(4))
|
|
2697
|
+
True
|
|
2698
|
+
sage: is_same_shape(LatinSquare(5), LatinSquare(5))
|
|
2699
|
+
True
|
|
2700
|
+
sage: is_same_shape(forward_circulant(5), LatinSquare(5))
|
|
2701
|
+
False
|
|
2702
|
+
"""
|
|
2703
|
+
for i in range(T1.nrows()):
|
|
2704
|
+
for j in range(T1.ncols()):
|
|
2705
|
+
if T1[i, j] < 0 and T2[i, j] < 0:
|
|
2706
|
+
continue
|
|
2707
|
+
if T1[i, j] >= 0 and T2[i, j] >= 0:
|
|
2708
|
+
continue
|
|
2709
|
+
|
|
2710
|
+
return False
|
|
2711
|
+
|
|
2712
|
+
return True
|
|
2713
|
+
|
|
2714
|
+
|
|
2715
|
+
def is_row_and_col_balanced(T1, T2):
|
|
2716
|
+
"""
|
|
2717
|
+
Partial latin squares T1 and T2 are balanced if the symbols
|
|
2718
|
+
appearing in row r of T1 are the same as the symbols appearing in
|
|
2719
|
+
row r of T2, for each r, and if the same condition holds on
|
|
2720
|
+
columns.
|
|
2721
|
+
|
|
2722
|
+
EXAMPLES::
|
|
2723
|
+
|
|
2724
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2725
|
+
sage: T1 = matrix([[0,1,-1,-1], [-1,-1,-1,-1], [-1,-1,-1,-1], [-1,-1,-1,-1]])
|
|
2726
|
+
sage: T2 = matrix([[0,1,-1,-1], [-1,-1,-1,-1], [-1,-1,-1,-1], [-1,-1,-1,-1]])
|
|
2727
|
+
sage: is_row_and_col_balanced(T1, T2)
|
|
2728
|
+
True
|
|
2729
|
+
sage: T2 = matrix([[0,3,-1,-1], [-1,-1,-1,-1], [-1,-1,-1,-1], [-1,-1,-1,-1]])
|
|
2730
|
+
sage: is_row_and_col_balanced(T1, T2)
|
|
2731
|
+
False
|
|
2732
|
+
"""
|
|
2733
|
+
for r in range(T1.nrows()):
|
|
2734
|
+
val1 = set(x for x in T1.row(r) if x >= 0)
|
|
2735
|
+
val2 = set(x for x in T2.row(r) if x >= 0)
|
|
2736
|
+
if val1 != val2:
|
|
2737
|
+
return False
|
|
2738
|
+
|
|
2739
|
+
for c in range(T1.ncols()):
|
|
2740
|
+
val1 = set(x for x in T1.column(c) if x >= 0)
|
|
2741
|
+
val2 = set(x for x in T2.column(c) if x >= 0)
|
|
2742
|
+
if val1 != val2:
|
|
2743
|
+
return False
|
|
2744
|
+
|
|
2745
|
+
return True
|
|
2746
|
+
|
|
2747
|
+
|
|
2748
|
+
def dlxcpp_rows_and_map(P):
|
|
2749
|
+
"""
|
|
2750
|
+
Internal function for ``dlxcpp_find_completions``. Given a partial
|
|
2751
|
+
latin square P we construct a list of rows of a 0-1 matrix M such
|
|
2752
|
+
that an exact cover of M corresponds to a completion of P to a
|
|
2753
|
+
latin square.
|
|
2754
|
+
|
|
2755
|
+
EXAMPLES::
|
|
2756
|
+
|
|
2757
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2758
|
+
sage: dlxcpp_rows_and_map(LatinSquare(2))
|
|
2759
|
+
([[0, 4, 8],
|
|
2760
|
+
[1, 5, 8],
|
|
2761
|
+
[2, 4, 9],
|
|
2762
|
+
[3, 5, 9],
|
|
2763
|
+
[0, 6, 10],
|
|
2764
|
+
[1, 7, 10],
|
|
2765
|
+
[2, 6, 11],
|
|
2766
|
+
[3, 7, 11]],
|
|
2767
|
+
{(0, 4, 8): (0, 0, 0),
|
|
2768
|
+
(0, 6, 10): (1, 0, 0),
|
|
2769
|
+
(1, 5, 8): (0, 0, 1),
|
|
2770
|
+
(1, 7, 10): (1, 0, 1),
|
|
2771
|
+
(2, 4, 9): (0, 1, 0),
|
|
2772
|
+
(2, 6, 11): (1, 1, 0),
|
|
2773
|
+
(3, 5, 9): (0, 1, 1),
|
|
2774
|
+
(3, 7, 11): (1, 1, 1)})
|
|
2775
|
+
"""
|
|
2776
|
+
assert P.nrows() == P.ncols()
|
|
2777
|
+
|
|
2778
|
+
n = P.nrows()
|
|
2779
|
+
|
|
2780
|
+
# We will need 3n^2 columns in total:
|
|
2781
|
+
#
|
|
2782
|
+
# n^2 for the xCy columns
|
|
2783
|
+
# n^2 for the xRy columns
|
|
2784
|
+
# n^2 for the xy columns
|
|
2785
|
+
|
|
2786
|
+
dlx_rows = []
|
|
2787
|
+
cmap = {}
|
|
2788
|
+
|
|
2789
|
+
for r in range(n):
|
|
2790
|
+
valsrow = P.vals_in_row(r)
|
|
2791
|
+
|
|
2792
|
+
for c in range(n):
|
|
2793
|
+
valscol = P.vals_in_col(c)
|
|
2794
|
+
|
|
2795
|
+
for e in range(n):
|
|
2796
|
+
# These should be constants
|
|
2797
|
+
c_OFFSET = e + c*n
|
|
2798
|
+
r_OFFSET = e + r*n + n*n
|
|
2799
|
+
xy_OFFSET = 2*n*n + r*n + c
|
|
2800
|
+
|
|
2801
|
+
cmap[(c_OFFSET, r_OFFSET, xy_OFFSET)] = (r, c, e)
|
|
2802
|
+
|
|
2803
|
+
# We only want the correct value to pop in here
|
|
2804
|
+
if P[r, c] >= 0 and P[r, c] != e:
|
|
2805
|
+
continue
|
|
2806
|
+
if P[r, c] < 0 and e in valsrow:
|
|
2807
|
+
continue
|
|
2808
|
+
if P[r, c] < 0 and e in valscol:
|
|
2809
|
+
continue
|
|
2810
|
+
|
|
2811
|
+
dlx_rows.append([c_OFFSET, r_OFFSET, xy_OFFSET])
|
|
2812
|
+
|
|
2813
|
+
return dlx_rows, cmap
|
|
2814
|
+
|
|
2815
|
+
|
|
2816
|
+
def dlxcpp_find_completions(P, nr_to_find=None):
|
|
2817
|
+
"""
|
|
2818
|
+
Return a list of all latin squares L of the same order as P such
|
|
2819
|
+
that P is contained in L. The optional parameter nr_to_find
|
|
2820
|
+
limits the number of latin squares that are found.
|
|
2821
|
+
|
|
2822
|
+
EXAMPLES::
|
|
2823
|
+
|
|
2824
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2825
|
+
sage: dlxcpp_find_completions(LatinSquare(2))
|
|
2826
|
+
[[0 1]
|
|
2827
|
+
[1 0], [1 0]
|
|
2828
|
+
[0 1]]
|
|
2829
|
+
|
|
2830
|
+
::
|
|
2831
|
+
|
|
2832
|
+
sage: dlxcpp_find_completions(LatinSquare(2), 1)
|
|
2833
|
+
[[0 1]
|
|
2834
|
+
[1 0]]
|
|
2835
|
+
"""
|
|
2836
|
+
assert P.nrows() == P.ncols()
|
|
2837
|
+
|
|
2838
|
+
dlx_rows, cmap = dlxcpp_rows_and_map(P)
|
|
2839
|
+
|
|
2840
|
+
SOLUTIONS = {}
|
|
2841
|
+
for x in DLXCPP(dlx_rows):
|
|
2842
|
+
x.sort()
|
|
2843
|
+
SOLUTIONS[tuple(x)] = True
|
|
2844
|
+
|
|
2845
|
+
if nr_to_find is not None and len(SOLUTIONS) >= nr_to_find:
|
|
2846
|
+
break
|
|
2847
|
+
|
|
2848
|
+
comps = []
|
|
2849
|
+
|
|
2850
|
+
for i in SOLUTIONS:
|
|
2851
|
+
soln = list(i)
|
|
2852
|
+
|
|
2853
|
+
from copy import deepcopy
|
|
2854
|
+
Q = deepcopy(P)
|
|
2855
|
+
|
|
2856
|
+
for x in soln:
|
|
2857
|
+
(r, c, e) = cmap[tuple(dlx_rows[x])]
|
|
2858
|
+
|
|
2859
|
+
if Q[r, c] >= 0:
|
|
2860
|
+
assert Q[r, c] == e
|
|
2861
|
+
else:
|
|
2862
|
+
Q[r, c] = e
|
|
2863
|
+
|
|
2864
|
+
comps.append(Q)
|
|
2865
|
+
|
|
2866
|
+
return comps
|
|
2867
|
+
|
|
2868
|
+
|
|
2869
|
+
def bitrade(T1, T2):
|
|
2870
|
+
r"""
|
|
2871
|
+
Form the bitrade (Q1, Q2) from (T1, T2) by setting empty the cells
|
|
2872
|
+
(r, c) such that T1[r, c] == T2[r, c].
|
|
2873
|
+
|
|
2874
|
+
EXAMPLES::
|
|
2875
|
+
|
|
2876
|
+
sage: from sage.combinat.matrices.latin import *
|
|
2877
|
+
sage: B1 = back_circulant(5)
|
|
2878
|
+
sage: alpha = isotopism((0,1,2,3,4))
|
|
2879
|
+
sage: beta = isotopism((1,0,2,3,4))
|
|
2880
|
+
sage: gamma = isotopism((2,1,0,3,4))
|
|
2881
|
+
sage: B2 = B1.apply_isotopism(alpha, beta, gamma)
|
|
2882
|
+
sage: T1, T2 = bitrade(B1, B2)
|
|
2883
|
+
sage: T1
|
|
2884
|
+
[ 0 1 -1 3 4]
|
|
2885
|
+
[ 1 -1 -1 4 0]
|
|
2886
|
+
[ 2 -1 4 0 1]
|
|
2887
|
+
[ 3 4 0 1 2]
|
|
2888
|
+
[ 4 0 1 2 3]
|
|
2889
|
+
sage: T2
|
|
2890
|
+
[ 3 4 -1 0 1]
|
|
2891
|
+
[ 0 -1 -1 1 4]
|
|
2892
|
+
[ 1 -1 0 4 2]
|
|
2893
|
+
[ 4 0 1 2 3]
|
|
2894
|
+
[ 2 1 4 3 0]
|
|
2895
|
+
"""
|
|
2896
|
+
assert T1.nrows() == T1.ncols()
|
|
2897
|
+
assert T2.nrows() == T2.ncols()
|
|
2898
|
+
assert T1.nrows() == T2.nrows()
|
|
2899
|
+
|
|
2900
|
+
n = T1.nrows()
|
|
2901
|
+
|
|
2902
|
+
from copy import copy
|
|
2903
|
+
Q1 = copy(T1)
|
|
2904
|
+
Q2 = copy(T2)
|
|
2905
|
+
|
|
2906
|
+
for r in range(n):
|
|
2907
|
+
for c in range(n):
|
|
2908
|
+
if T1[r, c] == T2[r, c]:
|
|
2909
|
+
Q1[r, c] = -1
|
|
2910
|
+
Q2[r, c] = -1
|
|
2911
|
+
|
|
2912
|
+
return Q1, Q2
|