passagemath-categories 10.6.32__cp314-cp314t-musllinux_1_2_aarch64.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_categories-10.6.32.dist-info/METADATA +156 -0
- passagemath_categories-10.6.32.dist-info/RECORD +719 -0
- passagemath_categories-10.6.32.dist-info/WHEEL +5 -0
- passagemath_categories-10.6.32.dist-info/top_level.txt +2 -0
- passagemath_categories.libs/libgcc_s-2d945d6c.so.1 +0 -0
- passagemath_categories.libs/libgmp-28992bcb.so.10.5.0 +0 -0
- passagemath_categories.libs/libstdc++-85f2cd6d.so.6.0.33 +0 -0
- sage/all__sagemath_categories.py +28 -0
- sage/arith/all.py +38 -0
- sage/arith/constants.pxd +27 -0
- sage/arith/functions.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/arith/functions.pxd +4 -0
- sage/arith/functions.pyx +221 -0
- sage/arith/misc.py +6552 -0
- sage/arith/multi_modular.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/arith/multi_modular.pxd +39 -0
- sage/arith/multi_modular.pyx +994 -0
- sage/arith/rational_reconstruction.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/arith/rational_reconstruction.pxd +4 -0
- sage/arith/rational_reconstruction.pyx +115 -0
- sage/arith/srange.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/arith/srange.pyx +571 -0
- sage/calculus/all__sagemath_categories.py +2 -0
- sage/calculus/functional.py +481 -0
- sage/calculus/functions.py +151 -0
- sage/categories/additive_groups.py +73 -0
- sage/categories/additive_magmas.py +1044 -0
- sage/categories/additive_monoids.py +114 -0
- sage/categories/additive_semigroups.py +184 -0
- sage/categories/affine_weyl_groups.py +238 -0
- sage/categories/algebra_ideals.py +95 -0
- sage/categories/algebra_modules.py +96 -0
- sage/categories/algebras.py +349 -0
- sage/categories/algebras_with_basis.py +377 -0
- sage/categories/all.py +160 -0
- sage/categories/aperiodic_semigroups.py +29 -0
- sage/categories/associative_algebras.py +47 -0
- sage/categories/bialgebras.py +101 -0
- sage/categories/bialgebras_with_basis.py +414 -0
- sage/categories/bimodules.py +206 -0
- sage/categories/chain_complexes.py +268 -0
- sage/categories/classical_crystals.py +480 -0
- sage/categories/coalgebras.py +405 -0
- sage/categories/coalgebras_with_basis.py +232 -0
- sage/categories/coercion_methods.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/categories/coercion_methods.pyx +52 -0
- sage/categories/commutative_additive_groups.py +104 -0
- sage/categories/commutative_additive_monoids.py +45 -0
- sage/categories/commutative_additive_semigroups.py +48 -0
- sage/categories/commutative_algebra_ideals.py +87 -0
- sage/categories/commutative_algebras.py +94 -0
- sage/categories/commutative_ring_ideals.py +58 -0
- sage/categories/commutative_rings.py +736 -0
- sage/categories/complete_discrete_valuation.py +293 -0
- sage/categories/complex_reflection_groups.py +145 -0
- sage/categories/complex_reflection_or_generalized_coxeter_groups.py +1249 -0
- sage/categories/coxeter_group_algebras.py +186 -0
- sage/categories/coxeter_groups.py +3402 -0
- sage/categories/crystals.py +2628 -0
- sage/categories/cw_complexes.py +216 -0
- sage/categories/dedekind_domains.py +137 -0
- sage/categories/discrete_valuation.py +325 -0
- sage/categories/distributive_magmas_and_additive_magmas.py +100 -0
- sage/categories/division_rings.py +114 -0
- sage/categories/domains.py +95 -0
- sage/categories/drinfeld_modules.py +789 -0
- sage/categories/dual.py +42 -0
- sage/categories/enumerated_sets.py +1146 -0
- sage/categories/euclidean_domains.py +271 -0
- sage/categories/examples/algebras_with_basis.py +102 -0
- sage/categories/examples/all.py +1 -0
- sage/categories/examples/commutative_additive_monoids.py +130 -0
- sage/categories/examples/commutative_additive_semigroups.py +199 -0
- sage/categories/examples/coxeter_groups.py +8 -0
- sage/categories/examples/crystals.py +236 -0
- sage/categories/examples/cw_complexes.py +163 -0
- sage/categories/examples/facade_sets.py +187 -0
- sage/categories/examples/filtered_algebras_with_basis.py +204 -0
- sage/categories/examples/filtered_modules_with_basis.py +154 -0
- sage/categories/examples/finite_coxeter_groups.py +252 -0
- sage/categories/examples/finite_dimensional_algebras_with_basis.py +148 -0
- sage/categories/examples/finite_dimensional_lie_algebras_with_basis.py +495 -0
- sage/categories/examples/finite_enumerated_sets.py +208 -0
- sage/categories/examples/finite_monoids.py +150 -0
- sage/categories/examples/finite_semigroups.py +190 -0
- sage/categories/examples/finite_weyl_groups.py +191 -0
- sage/categories/examples/graded_connected_hopf_algebras_with_basis.py +152 -0
- sage/categories/examples/graded_modules_with_basis.py +168 -0
- sage/categories/examples/graphs.py +122 -0
- sage/categories/examples/hopf_algebras_with_basis.py +145 -0
- sage/categories/examples/infinite_enumerated_sets.py +190 -0
- sage/categories/examples/lie_algebras.py +352 -0
- sage/categories/examples/lie_algebras_with_basis.py +196 -0
- sage/categories/examples/magmas.py +162 -0
- sage/categories/examples/manifolds.py +94 -0
- sage/categories/examples/monoids.py +144 -0
- sage/categories/examples/posets.py +178 -0
- sage/categories/examples/semigroups.py +580 -0
- sage/categories/examples/semigroups_cython.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/categories/examples/semigroups_cython.pyx +221 -0
- sage/categories/examples/semirings.py +249 -0
- sage/categories/examples/sets_cat.py +706 -0
- sage/categories/examples/sets_with_grading.py +101 -0
- sage/categories/examples/with_realizations.py +542 -0
- sage/categories/fields.py +991 -0
- sage/categories/filtered_algebras.py +63 -0
- sage/categories/filtered_algebras_with_basis.py +548 -0
- sage/categories/filtered_hopf_algebras_with_basis.py +138 -0
- sage/categories/filtered_modules.py +210 -0
- sage/categories/filtered_modules_with_basis.py +1209 -0
- sage/categories/finite_complex_reflection_groups.py +1506 -0
- sage/categories/finite_coxeter_groups.py +1138 -0
- sage/categories/finite_crystals.py +103 -0
- sage/categories/finite_dimensional_algebras_with_basis.py +1860 -0
- sage/categories/finite_dimensional_bialgebras_with_basis.py +33 -0
- sage/categories/finite_dimensional_coalgebras_with_basis.py +33 -0
- sage/categories/finite_dimensional_graded_lie_algebras_with_basis.py +231 -0
- sage/categories/finite_dimensional_hopf_algebras_with_basis.py +38 -0
- sage/categories/finite_dimensional_lie_algebras_with_basis.py +2774 -0
- sage/categories/finite_dimensional_modules_with_basis.py +1407 -0
- sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py +167 -0
- sage/categories/finite_dimensional_semisimple_algebras_with_basis.py +270 -0
- sage/categories/finite_enumerated_sets.py +769 -0
- sage/categories/finite_fields.py +252 -0
- sage/categories/finite_groups.py +256 -0
- sage/categories/finite_lattice_posets.py +242 -0
- sage/categories/finite_monoids.py +316 -0
- sage/categories/finite_permutation_groups.py +339 -0
- sage/categories/finite_posets.py +1994 -0
- sage/categories/finite_semigroups.py +136 -0
- sage/categories/finite_sets.py +93 -0
- sage/categories/finite_weyl_groups.py +39 -0
- sage/categories/finitely_generated_lambda_bracket_algebras.py +112 -0
- sage/categories/finitely_generated_lie_conformal_algebras.py +114 -0
- sage/categories/finitely_generated_magmas.py +57 -0
- sage/categories/finitely_generated_semigroups.py +214 -0
- sage/categories/function_fields.py +76 -0
- sage/categories/g_sets.py +77 -0
- sage/categories/gcd_domains.py +65 -0
- sage/categories/generalized_coxeter_groups.py +94 -0
- sage/categories/graded_algebras.py +85 -0
- sage/categories/graded_algebras_with_basis.py +258 -0
- sage/categories/graded_bialgebras.py +32 -0
- sage/categories/graded_bialgebras_with_basis.py +32 -0
- sage/categories/graded_coalgebras.py +65 -0
- sage/categories/graded_coalgebras_with_basis.py +51 -0
- sage/categories/graded_hopf_algebras.py +41 -0
- sage/categories/graded_hopf_algebras_with_basis.py +169 -0
- sage/categories/graded_lie_algebras.py +91 -0
- sage/categories/graded_lie_algebras_with_basis.py +44 -0
- sage/categories/graded_lie_conformal_algebras.py +74 -0
- sage/categories/graded_modules.py +133 -0
- sage/categories/graded_modules_with_basis.py +329 -0
- sage/categories/graphs.py +138 -0
- sage/categories/group_algebras.py +430 -0
- sage/categories/groupoid.py +94 -0
- sage/categories/groups.py +667 -0
- sage/categories/h_trivial_semigroups.py +64 -0
- sage/categories/hecke_modules.py +185 -0
- sage/categories/highest_weight_crystals.py +980 -0
- sage/categories/hopf_algebras.py +219 -0
- sage/categories/hopf_algebras_with_basis.py +309 -0
- sage/categories/infinite_enumerated_sets.py +115 -0
- sage/categories/integral_domains.py +203 -0
- sage/categories/j_trivial_semigroups.py +29 -0
- sage/categories/kac_moody_algebras.py +82 -0
- sage/categories/kahler_algebras.py +203 -0
- sage/categories/l_trivial_semigroups.py +63 -0
- sage/categories/lambda_bracket_algebras.py +280 -0
- sage/categories/lambda_bracket_algebras_with_basis.py +107 -0
- sage/categories/lattice_posets.py +89 -0
- sage/categories/left_modules.py +49 -0
- sage/categories/lie_algebras.py +1070 -0
- sage/categories/lie_algebras_with_basis.py +261 -0
- sage/categories/lie_conformal_algebras.py +350 -0
- sage/categories/lie_conformal_algebras_with_basis.py +147 -0
- sage/categories/lie_groups.py +73 -0
- sage/categories/loop_crystals.py +1290 -0
- sage/categories/magmas.py +1189 -0
- sage/categories/magmas_and_additive_magmas.py +149 -0
- sage/categories/magmatic_algebras.py +365 -0
- sage/categories/manifolds.py +352 -0
- sage/categories/matrix_algebras.py +40 -0
- sage/categories/metric_spaces.py +387 -0
- sage/categories/modular_abelian_varieties.py +78 -0
- sage/categories/modules.py +989 -0
- sage/categories/modules_with_basis.py +2794 -0
- sage/categories/monoid_algebras.py +38 -0
- sage/categories/monoids.py +739 -0
- sage/categories/noetherian_rings.py +87 -0
- sage/categories/number_fields.py +242 -0
- sage/categories/ore_modules.py +189 -0
- sage/categories/partially_ordered_monoids.py +49 -0
- sage/categories/permutation_groups.py +63 -0
- sage/categories/pointed_sets.py +42 -0
- sage/categories/polyhedra.py +74 -0
- sage/categories/poor_man_map.py +270 -0
- sage/categories/posets.py +722 -0
- sage/categories/principal_ideal_domains.py +270 -0
- sage/categories/quantum_group_representations.py +543 -0
- sage/categories/quotient_fields.py +728 -0
- sage/categories/r_trivial_semigroups.py +45 -0
- sage/categories/regular_crystals.py +898 -0
- sage/categories/regular_supercrystals.py +170 -0
- sage/categories/right_modules.py +49 -0
- sage/categories/ring_ideals.py +74 -0
- sage/categories/rings.py +1904 -0
- sage/categories/rngs.py +175 -0
- sage/categories/schemes.py +393 -0
- sage/categories/semigroups.py +1060 -0
- sage/categories/semirings.py +71 -0
- sage/categories/semisimple_algebras.py +114 -0
- sage/categories/sets_with_grading.py +235 -0
- sage/categories/shephard_groups.py +43 -0
- sage/categories/signed_tensor.py +120 -0
- sage/categories/simplicial_complexes.py +134 -0
- sage/categories/simplicial_sets.py +1206 -0
- sage/categories/super_algebras.py +149 -0
- sage/categories/super_algebras_with_basis.py +144 -0
- sage/categories/super_hopf_algebras_with_basis.py +126 -0
- sage/categories/super_lie_conformal_algebras.py +193 -0
- sage/categories/super_modules.py +229 -0
- sage/categories/super_modules_with_basis.py +193 -0
- sage/categories/supercommutative_algebras.py +99 -0
- sage/categories/supercrystals.py +406 -0
- sage/categories/tensor.py +110 -0
- sage/categories/topological_spaces.py +170 -0
- sage/categories/triangular_kac_moody_algebras.py +439 -0
- sage/categories/tutorial.py +58 -0
- sage/categories/unique_factorization_domains.py +318 -0
- sage/categories/unital_algebras.py +426 -0
- sage/categories/vector_bundles.py +159 -0
- sage/categories/vector_spaces.py +357 -0
- sage/categories/weyl_groups.py +853 -0
- sage/combinat/all__sagemath_categories.py +34 -0
- sage/combinat/backtrack.py +180 -0
- sage/combinat/combinat.py +2269 -0
- sage/combinat/combinat_cython.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/combinat_cython.pxd +6 -0
- sage/combinat/combinat_cython.pyx +390 -0
- sage/combinat/combination.py +796 -0
- sage/combinat/combinatorial_map.py +416 -0
- sage/combinat/composition.py +2192 -0
- sage/combinat/dlx.py +510 -0
- sage/combinat/integer_lists/__init__.py +7 -0
- sage/combinat/integer_lists/base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/integer_lists/base.pxd +16 -0
- sage/combinat/integer_lists/base.pyx +713 -0
- sage/combinat/integer_lists/invlex.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/integer_lists/invlex.pxd +4 -0
- sage/combinat/integer_lists/invlex.pyx +1650 -0
- sage/combinat/integer_lists/lists.py +328 -0
- sage/combinat/integer_lists/nn.py +48 -0
- sage/combinat/integer_vector.py +1818 -0
- sage/combinat/integer_vector_weighted.py +413 -0
- sage/combinat/matrices/all__sagemath_categories.py +5 -0
- sage/combinat/matrices/dancing_links.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/matrices/dancing_links.pyx +1159 -0
- sage/combinat/matrices/dancing_links_c.h +380 -0
- sage/combinat/matrices/dlxcpp.py +136 -0
- sage/combinat/partition.py +10070 -0
- sage/combinat/partitions.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/partitions.pyx +743 -0
- sage/combinat/permutation.py +10168 -0
- sage/combinat/permutation_cython.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/permutation_cython.pxd +11 -0
- sage/combinat/permutation_cython.pyx +407 -0
- sage/combinat/q_analogues.py +1090 -0
- sage/combinat/ranker.py +268 -0
- sage/combinat/subset.py +1561 -0
- sage/combinat/subsets_hereditary.py +202 -0
- sage/combinat/subsets_pairwise.py +184 -0
- sage/combinat/tools.py +63 -0
- sage/combinat/tuple.py +348 -0
- sage/data_structures/all.py +2 -0
- sage/data_structures/all__sagemath_categories.py +2 -0
- sage/data_structures/binary_matrix.pxd +138 -0
- sage/data_structures/binary_search.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/binary_search.pxd +3 -0
- sage/data_structures/binary_search.pyx +66 -0
- sage/data_structures/bitset.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/bitset.pxd +40 -0
- sage/data_structures/bitset.pyx +2385 -0
- sage/data_structures/bitset_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/bitset_base.pxd +926 -0
- sage/data_structures/bitset_base.pyx +117 -0
- sage/data_structures/bitset_intrinsics.h +487 -0
- sage/data_structures/blas_dict.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/blas_dict.pxd +12 -0
- sage/data_structures/blas_dict.pyx +469 -0
- sage/data_structures/list_of_pairs.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/list_of_pairs.pxd +16 -0
- sage/data_structures/list_of_pairs.pyx +122 -0
- sage/data_structures/mutable_poset.py +3312 -0
- sage/data_structures/pairing_heap.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/pairing_heap.h +346 -0
- sage/data_structures/pairing_heap.pxd +88 -0
- sage/data_structures/pairing_heap.pyx +1464 -0
- sage/data_structures/sparse_bitset.pxd +62 -0
- sage/data_structures/stream.py +5070 -0
- sage/databases/all__sagemath_categories.py +7 -0
- sage/databases/sql_db.py +2236 -0
- sage/ext/all__sagemath_categories.py +3 -0
- sage/ext/fast_callable.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/ext/fast_callable.pxd +4 -0
- sage/ext/fast_callable.pyx +2746 -0
- sage/ext/fast_eval.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/ext/fast_eval.pxd +1 -0
- sage/ext/fast_eval.pyx +102 -0
- sage/ext/interpreters/__init__.py +1 -0
- sage/ext/interpreters/all__sagemath_categories.py +2 -0
- sage/ext/interpreters/wrapper_el.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/ext/interpreters/wrapper_el.pxd +18 -0
- sage/ext/interpreters/wrapper_el.pyx +148 -0
- sage/ext/interpreters/wrapper_py.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/ext/interpreters/wrapper_py.pxd +17 -0
- sage/ext/interpreters/wrapper_py.pyx +133 -0
- sage/functions/airy.py +937 -0
- sage/functions/all.py +97 -0
- sage/functions/bessel.py +2102 -0
- sage/functions/error.py +784 -0
- sage/functions/exp_integral.py +1529 -0
- sage/functions/gamma.py +1087 -0
- sage/functions/generalized.py +672 -0
- sage/functions/hyperbolic.py +747 -0
- sage/functions/hypergeometric.py +1156 -0
- sage/functions/jacobi.py +1705 -0
- sage/functions/log.py +1402 -0
- sage/functions/min_max.py +338 -0
- sage/functions/orthogonal_polys.py +3106 -0
- sage/functions/other.py +2303 -0
- sage/functions/piecewise.py +1505 -0
- sage/functions/prime_pi.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/functions/prime_pi.pyx +262 -0
- sage/functions/special.py +1212 -0
- sage/functions/spike_function.py +278 -0
- sage/functions/transcendental.py +690 -0
- sage/functions/trig.py +1062 -0
- sage/functions/wigner.py +726 -0
- sage/geometry/abc.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/geometry/abc.pyx +82 -0
- sage/geometry/all__sagemath_categories.py +1 -0
- sage/groups/all__sagemath_categories.py +11 -0
- sage/groups/generic.py +1733 -0
- sage/groups/groups_catalog.py +113 -0
- sage/groups/perm_gps/all__sagemath_categories.py +1 -0
- sage/groups/perm_gps/partn_ref/all.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_categories.py +1 -0
- sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.pxd +52 -0
- sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.pyx +906 -0
- sage/groups/perm_gps/partn_ref/canonical_augmentation.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/canonical_augmentation.pxd +85 -0
- sage/groups/perm_gps/partn_ref/canonical_augmentation.pyx +534 -0
- sage/groups/perm_gps/partn_ref/data_structures.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/data_structures.pxd +576 -0
- sage/groups/perm_gps/partn_ref/data_structures.pyx +1792 -0
- sage/groups/perm_gps/partn_ref/double_coset.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/double_coset.pxd +45 -0
- sage/groups/perm_gps/partn_ref/double_coset.pyx +739 -0
- sage/groups/perm_gps/partn_ref/refinement_lists.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_lists.pxd +18 -0
- sage/groups/perm_gps/partn_ref/refinement_lists.pyx +82 -0
- sage/groups/perm_gps/partn_ref/refinement_python.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_python.pxd +16 -0
- sage/groups/perm_gps/partn_ref/refinement_python.pyx +564 -0
- sage/groups/perm_gps/partn_ref/refinement_sets.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_sets.pxd +60 -0
- sage/groups/perm_gps/partn_ref/refinement_sets.pyx +858 -0
- sage/interfaces/abc.py +140 -0
- sage/interfaces/all.py +58 -0
- sage/interfaces/all__sagemath_categories.py +1 -0
- sage/interfaces/expect.py +1643 -0
- sage/interfaces/interface.py +1682 -0
- sage/interfaces/process.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/interfaces/process.pxd +5 -0
- sage/interfaces/process.pyx +288 -0
- sage/interfaces/quit.py +167 -0
- sage/interfaces/sage0.py +604 -0
- sage/interfaces/sagespawn.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/interfaces/sagespawn.pyx +308 -0
- sage/interfaces/tab_completion.py +101 -0
- sage/misc/all__sagemath_categories.py +78 -0
- sage/misc/allocator.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/allocator.pxd +6 -0
- sage/misc/allocator.pyx +47 -0
- sage/misc/binary_tree.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/binary_tree.pxd +29 -0
- sage/misc/binary_tree.pyx +537 -0
- sage/misc/callable_dict.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/callable_dict.pyx +89 -0
- sage/misc/citation.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/citation.pyx +159 -0
- sage/misc/converting_dict.py +293 -0
- sage/misc/defaults.py +129 -0
- sage/misc/derivative.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/derivative.pyx +223 -0
- sage/misc/functional.py +2005 -0
- sage/misc/html.py +589 -0
- sage/misc/latex.py +2673 -0
- sage/misc/latex_macros.py +236 -0
- sage/misc/latex_standalone.py +1833 -0
- sage/misc/map_threaded.py +38 -0
- sage/misc/mathml.py +76 -0
- sage/misc/method_decorator.py +88 -0
- sage/misc/mrange.py +755 -0
- sage/misc/multireplace.py +41 -0
- sage/misc/object_multiplexer.py +92 -0
- sage/misc/parser.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/parser.pyx +1107 -0
- sage/misc/random_testing.py +264 -0
- sage/misc/rest_index_of_methods.py +377 -0
- sage/misc/search.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/search.pxd +2 -0
- sage/misc/search.pyx +68 -0
- sage/misc/stopgap.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/stopgap.pyx +95 -0
- sage/misc/table.py +853 -0
- sage/monoids/all__sagemath_categories.py +1 -0
- sage/monoids/indexed_free_monoid.py +1071 -0
- sage/monoids/monoid.py +82 -0
- sage/numerical/all__sagemath_categories.py +1 -0
- sage/numerical/backends/all__sagemath_categories.py +1 -0
- sage/numerical/backends/generic_backend.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/numerical/backends/generic_backend.pxd +61 -0
- sage/numerical/backends/generic_backend.pyx +1893 -0
- sage/numerical/backends/generic_sdp_backend.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/numerical/backends/generic_sdp_backend.pxd +38 -0
- sage/numerical/backends/generic_sdp_backend.pyx +755 -0
- sage/parallel/all.py +6 -0
- sage/parallel/decorate.py +575 -0
- sage/parallel/map_reduce.py +1997 -0
- sage/parallel/multiprocessing_sage.py +76 -0
- sage/parallel/ncpus.py +35 -0
- sage/parallel/parallelism.py +364 -0
- sage/parallel/reference.py +47 -0
- sage/parallel/use_fork.py +333 -0
- sage/rings/abc.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/abc.pxd +31 -0
- sage/rings/abc.pyx +526 -0
- sage/rings/algebraic_closure_finite_field.py +1154 -0
- sage/rings/all__sagemath_categories.py +91 -0
- sage/rings/big_oh.py +227 -0
- sage/rings/continued_fraction.py +2754 -0
- sage/rings/continued_fraction_gosper.py +220 -0
- sage/rings/factorint.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/factorint.pyx +295 -0
- sage/rings/fast_arith.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/fast_arith.pxd +21 -0
- sage/rings/fast_arith.pyx +535 -0
- sage/rings/finite_rings/all__sagemath_categories.py +9 -0
- sage/rings/finite_rings/conway_polynomials.py +542 -0
- sage/rings/finite_rings/element_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/element_base.pxd +12 -0
- sage/rings/finite_rings/element_base.pyx +1176 -0
- sage/rings/finite_rings/finite_field_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/finite_field_base.pxd +7 -0
- sage/rings/finite_rings/finite_field_base.pyx +2171 -0
- sage/rings/finite_rings/finite_field_constructor.py +827 -0
- sage/rings/finite_rings/finite_field_prime_modn.py +372 -0
- sage/rings/finite_rings/galois_group.py +154 -0
- sage/rings/finite_rings/hom_finite_field.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/hom_finite_field.pxd +23 -0
- sage/rings/finite_rings/hom_finite_field.pyx +856 -0
- sage/rings/finite_rings/hom_prime_finite_field.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/hom_prime_finite_field.pxd +15 -0
- sage/rings/finite_rings/hom_prime_finite_field.pyx +164 -0
- sage/rings/finite_rings/homset.py +357 -0
- sage/rings/finite_rings/integer_mod.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/integer_mod.pxd +56 -0
- sage/rings/finite_rings/integer_mod.pyx +4586 -0
- sage/rings/finite_rings/integer_mod_limits.h +11 -0
- sage/rings/finite_rings/integer_mod_ring.py +2044 -0
- sage/rings/finite_rings/residue_field.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/residue_field.pxd +30 -0
- sage/rings/finite_rings/residue_field.pyx +1811 -0
- sage/rings/finite_rings/stdint.pxd +19 -0
- sage/rings/fraction_field.py +1452 -0
- sage/rings/fraction_field_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/fraction_field_element.pyx +1357 -0
- sage/rings/function_field/all.py +7 -0
- sage/rings/function_field/all__sagemath_categories.py +2 -0
- sage/rings/function_field/constructor.py +218 -0
- sage/rings/function_field/element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/function_field/element.pxd +11 -0
- sage/rings/function_field/element.pyx +1008 -0
- sage/rings/function_field/element_rational.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/function_field/element_rational.pyx +513 -0
- sage/rings/function_field/extensions.py +230 -0
- sage/rings/function_field/function_field.py +1468 -0
- sage/rings/function_field/function_field_rational.py +1005 -0
- sage/rings/function_field/ideal.py +1155 -0
- sage/rings/function_field/ideal_rational.py +629 -0
- sage/rings/function_field/jacobian_base.py +826 -0
- sage/rings/function_field/jacobian_hess.py +1053 -0
- sage/rings/function_field/jacobian_khuri_makdisi.py +1027 -0
- sage/rings/function_field/maps.py +1039 -0
- sage/rings/function_field/order.py +281 -0
- sage/rings/function_field/order_basis.py +586 -0
- sage/rings/function_field/order_rational.py +576 -0
- sage/rings/function_field/place.py +426 -0
- sage/rings/function_field/place_rational.py +181 -0
- sage/rings/generic.py +320 -0
- sage/rings/homset.py +332 -0
- sage/rings/ideal.py +1885 -0
- sage/rings/ideal_monoid.py +215 -0
- sage/rings/infinity.py +1890 -0
- sage/rings/integer.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/integer.pxd +45 -0
- sage/rings/integer.pyx +7874 -0
- sage/rings/integer_ring.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/integer_ring.pxd +8 -0
- sage/rings/integer_ring.pyx +1693 -0
- sage/rings/laurent_series_ring.py +931 -0
- sage/rings/laurent_series_ring_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/laurent_series_ring_element.pxd +11 -0
- sage/rings/laurent_series_ring_element.pyx +1927 -0
- sage/rings/lazy_series.py +7815 -0
- sage/rings/lazy_series_ring.py +4356 -0
- sage/rings/localization.py +1043 -0
- sage/rings/morphism.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/morphism.pxd +39 -0
- sage/rings/morphism.pyx +3299 -0
- sage/rings/multi_power_series_ring.py +1145 -0
- sage/rings/multi_power_series_ring_element.py +2184 -0
- sage/rings/noncommutative_ideals.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/noncommutative_ideals.pyx +423 -0
- sage/rings/number_field/all__sagemath_categories.py +1 -0
- sage/rings/number_field/number_field_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/number_field/number_field_base.pxd +8 -0
- sage/rings/number_field/number_field_base.pyx +507 -0
- sage/rings/number_field/number_field_element_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/number_field/number_field_element_base.pxd +6 -0
- sage/rings/number_field/number_field_element_base.pyx +36 -0
- sage/rings/number_field/number_field_ideal.py +3550 -0
- sage/rings/padics/all__sagemath_categories.py +4 -0
- sage/rings/padics/local_generic.py +1670 -0
- sage/rings/padics/local_generic_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/padics/local_generic_element.pxd +5 -0
- sage/rings/padics/local_generic_element.pyx +1017 -0
- sage/rings/padics/misc.py +256 -0
- sage/rings/padics/padic_generic.py +1911 -0
- sage/rings/padics/pow_computer.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/padics/pow_computer.pxd +38 -0
- sage/rings/padics/pow_computer.pyx +671 -0
- sage/rings/padics/precision_error.py +24 -0
- sage/rings/polynomial/all__sagemath_categories.py +25 -0
- sage/rings/polynomial/commutative_polynomial.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/commutative_polynomial.pxd +6 -0
- sage/rings/polynomial/commutative_polynomial.pyx +24 -0
- sage/rings/polynomial/cyclotomic.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/cyclotomic.pyx +404 -0
- sage/rings/polynomial/flatten.py +711 -0
- sage/rings/polynomial/ideal.py +102 -0
- sage/rings/polynomial/infinite_polynomial_element.py +1768 -0
- sage/rings/polynomial/infinite_polynomial_ring.py +1653 -0
- sage/rings/polynomial/laurent_polynomial.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/laurent_polynomial.pxd +18 -0
- sage/rings/polynomial/laurent_polynomial.pyx +2190 -0
- sage/rings/polynomial/laurent_polynomial_ideal.py +590 -0
- sage/rings/polynomial/laurent_polynomial_ring.py +832 -0
- sage/rings/polynomial/laurent_polynomial_ring_base.py +708 -0
- sage/rings/polynomial/multi_polynomial.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/multi_polynomial.pxd +12 -0
- sage/rings/polynomial/multi_polynomial.pyx +3082 -0
- sage/rings/polynomial/multi_polynomial_element.py +2570 -0
- sage/rings/polynomial/multi_polynomial_ideal.py +5771 -0
- sage/rings/polynomial/multi_polynomial_ring.py +947 -0
- sage/rings/polynomial/multi_polynomial_ring_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/multi_polynomial_ring_base.pxd +15 -0
- sage/rings/polynomial/multi_polynomial_ring_base.pyx +1855 -0
- sage/rings/polynomial/multi_polynomial_sequence.py +2204 -0
- sage/rings/polynomial/polydict.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/polydict.pxd +45 -0
- sage/rings/polynomial/polydict.pyx +2701 -0
- sage/rings/polynomial/polynomial_compiled.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/polynomial_compiled.pxd +59 -0
- sage/rings/polynomial/polynomial_compiled.pyx +509 -0
- sage/rings/polynomial/polynomial_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/polynomial_element.pxd +64 -0
- sage/rings/polynomial/polynomial_element.pyx +13255 -0
- sage/rings/polynomial/polynomial_element_generic.py +1637 -0
- sage/rings/polynomial/polynomial_fateman.py +97 -0
- sage/rings/polynomial/polynomial_quotient_ring.py +2465 -0
- sage/rings/polynomial/polynomial_quotient_ring_element.py +779 -0
- sage/rings/polynomial/polynomial_ring.py +3784 -0
- sage/rings/polynomial/polynomial_ring_constructor.py +1051 -0
- sage/rings/polynomial/polynomial_ring_homomorphism.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/polynomial_ring_homomorphism.pxd +5 -0
- sage/rings/polynomial/polynomial_ring_homomorphism.pyx +121 -0
- sage/rings/polynomial/polynomial_singular_interface.py +549 -0
- sage/rings/polynomial/symmetric_ideal.py +989 -0
- sage/rings/polynomial/symmetric_reduction.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/symmetric_reduction.pxd +8 -0
- sage/rings/polynomial/symmetric_reduction.pyx +669 -0
- sage/rings/polynomial/term_order.py +2279 -0
- sage/rings/polynomial/toy_buchberger.py +449 -0
- sage/rings/polynomial/toy_d_basis.py +387 -0
- sage/rings/polynomial/toy_variety.py +362 -0
- sage/rings/power_series_mpoly.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/power_series_mpoly.pxd +9 -0
- sage/rings/power_series_mpoly.pyx +161 -0
- sage/rings/power_series_poly.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/power_series_poly.pxd +10 -0
- sage/rings/power_series_poly.pyx +1317 -0
- sage/rings/power_series_ring.py +1441 -0
- sage/rings/power_series_ring_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/power_series_ring_element.pxd +12 -0
- sage/rings/power_series_ring_element.pyx +3028 -0
- sage/rings/puiseux_series_ring.py +487 -0
- sage/rings/puiseux_series_ring_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/puiseux_series_ring_element.pxd +7 -0
- sage/rings/puiseux_series_ring_element.pyx +1055 -0
- sage/rings/qqbar_decorators.py +167 -0
- sage/rings/quotient_ring.py +1598 -0
- sage/rings/quotient_ring_element.py +979 -0
- sage/rings/rational.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/rational.pxd +20 -0
- sage/rings/rational.pyx +4284 -0
- sage/rings/rational_field.py +1730 -0
- sage/rings/real_double.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/real_double.pxd +16 -0
- sage/rings/real_double.pyx +2218 -0
- sage/rings/real_lazy.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/real_lazy.pxd +30 -0
- sage/rings/real_lazy.pyx +1773 -0
- sage/rings/ring.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/ring.pxd +30 -0
- sage/rings/ring.pyx +850 -0
- sage/rings/semirings/all.py +3 -0
- sage/rings/semirings/non_negative_integer_semiring.py +107 -0
- sage/rings/semirings/tropical_mpolynomial.py +972 -0
- sage/rings/semirings/tropical_polynomial.py +997 -0
- sage/rings/semirings/tropical_semiring.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/semirings/tropical_semiring.pyx +676 -0
- sage/rings/semirings/tropical_variety.py +1701 -0
- sage/rings/sum_of_squares.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/sum_of_squares.pxd +3 -0
- sage/rings/sum_of_squares.pyx +336 -0
- sage/rings/tests.py +504 -0
- sage/schemes/affine/affine_homset.py +508 -0
- sage/schemes/affine/affine_morphism.py +1574 -0
- sage/schemes/affine/affine_point.py +460 -0
- sage/schemes/affine/affine_rational_point.py +308 -0
- sage/schemes/affine/affine_space.py +1264 -0
- sage/schemes/affine/affine_subscheme.py +592 -0
- sage/schemes/affine/all.py +25 -0
- sage/schemes/all__sagemath_categories.py +5 -0
- sage/schemes/generic/algebraic_scheme.py +2092 -0
- sage/schemes/generic/all.py +5 -0
- sage/schemes/generic/ambient_space.py +400 -0
- sage/schemes/generic/divisor.py +465 -0
- sage/schemes/generic/divisor_group.py +313 -0
- sage/schemes/generic/glue.py +84 -0
- sage/schemes/generic/homset.py +820 -0
- sage/schemes/generic/hypersurface.py +234 -0
- sage/schemes/generic/morphism.py +2107 -0
- sage/schemes/generic/point.py +237 -0
- sage/schemes/generic/scheme.py +1190 -0
- sage/schemes/generic/spec.py +199 -0
- sage/schemes/product_projective/all.py +6 -0
- sage/schemes/product_projective/homset.py +236 -0
- sage/schemes/product_projective/morphism.py +517 -0
- sage/schemes/product_projective/point.py +568 -0
- sage/schemes/product_projective/rational_point.py +550 -0
- sage/schemes/product_projective/space.py +1301 -0
- sage/schemes/product_projective/subscheme.py +466 -0
- sage/schemes/projective/all.py +24 -0
- sage/schemes/projective/proj_bdd_height.py +453 -0
- sage/schemes/projective/projective_homset.py +718 -0
- sage/schemes/projective/projective_morphism.py +2792 -0
- sage/schemes/projective/projective_point.py +1484 -0
- sage/schemes/projective/projective_rational_point.py +569 -0
- sage/schemes/projective/projective_space.py +2571 -0
- sage/schemes/projective/projective_subscheme.py +1574 -0
- sage/sets/all.py +17 -0
- sage/sets/cartesian_product.py +376 -0
- sage/sets/condition_set.py +525 -0
- sage/sets/disjoint_set.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/sets/disjoint_set.pxd +36 -0
- sage/sets/disjoint_set.pyx +998 -0
- sage/sets/disjoint_union_enumerated_sets.py +625 -0
- sage/sets/family.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/sets/family.pxd +12 -0
- sage/sets/family.pyx +1556 -0
- sage/sets/finite_enumerated_set.py +406 -0
- sage/sets/finite_set_map_cy.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/sets/finite_set_map_cy.pxd +34 -0
- sage/sets/finite_set_map_cy.pyx +708 -0
- sage/sets/finite_set_maps.py +591 -0
- sage/sets/image_set.py +448 -0
- sage/sets/integer_range.py +829 -0
- sage/sets/non_negative_integers.py +241 -0
- sage/sets/positive_integers.py +93 -0
- sage/sets/primes.py +188 -0
- sage/sets/real_set.py +2760 -0
- sage/sets/recursively_enumerated_set.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/sets/recursively_enumerated_set.pxd +31 -0
- sage/sets/recursively_enumerated_set.pyx +2082 -0
- sage/sets/set.py +2083 -0
- sage/sets/set_from_iterator.py +1021 -0
- sage/sets/totally_ordered_finite_set.py +329 -0
- sage/symbolic/all__sagemath_categories.py +1 -0
- sage/symbolic/function.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/symbolic/function.pxd +29 -0
- sage/symbolic/function.pyx +1488 -0
- sage/symbolic/symbols.py +56 -0
- sage/tests/all__sagemath_categories.py +1 -0
- sage/tests/cython.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/tests/cython.pyx +37 -0
- sage/tests/stl_vector.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/tests/stl_vector.pyx +171 -0
- sage/typeset/all.py +6 -0
- sage/typeset/ascii_art.py +295 -0
- sage/typeset/character_art.py +789 -0
- sage/typeset/character_art_factory.py +572 -0
- sage/typeset/symbols.py +334 -0
- sage/typeset/unicode_art.py +183 -0
- sage/typeset/unicode_characters.py +101 -0
|
@@ -0,0 +1,1997 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-categories
|
|
2
|
+
r"""
|
|
3
|
+
Parallel computations using RecursivelyEnumeratedSet and Map-Reduce
|
|
4
|
+
|
|
5
|
+
There is an efficient way to distribute computations on a set
|
|
6
|
+
`S` of objects defined by :func:`RecursivelyEnumeratedSet`
|
|
7
|
+
(see :mod:`sage.sets.recursively_enumerated_set` for more details)
|
|
8
|
+
over which one would like to perform the following kind of operations:
|
|
9
|
+
|
|
10
|
+
* Compute the cardinality of a (very large) set defined recursively
|
|
11
|
+
(through a call to :class:`RecursivelyEnumeratedSet_forest`)
|
|
12
|
+
|
|
13
|
+
* More generally, compute any kind of generating series over this set
|
|
14
|
+
|
|
15
|
+
* Test a conjecture, e.g. find an element of `S` satisfying a specific
|
|
16
|
+
property, or check that none does or that they all do
|
|
17
|
+
|
|
18
|
+
* Count/list the elements of `S` that have a specific property
|
|
19
|
+
|
|
20
|
+
* Apply any map/reduce kind of operation over the elements of `S`
|
|
21
|
+
|
|
22
|
+
AUTHORS:
|
|
23
|
+
|
|
24
|
+
- Florent Hivert -- code, documentation (2012--2016)
|
|
25
|
+
|
|
26
|
+
- Jean Baptiste Priez -- prototype, debugging help on MacOSX (2011-June, 2016)
|
|
27
|
+
|
|
28
|
+
- Nathann Cohen -- some documentation (2012)
|
|
29
|
+
|
|
30
|
+
Contents
|
|
31
|
+
--------
|
|
32
|
+
|
|
33
|
+
- :ref:`basic-usage`
|
|
34
|
+
- :ref:`advanced-use`
|
|
35
|
+
- :ref:`profiling`
|
|
36
|
+
- :ref:`logging`
|
|
37
|
+
- :ref:`protocol-description`
|
|
38
|
+
- :ref:`examples`
|
|
39
|
+
|
|
40
|
+
How is this different from usual MapReduce?
|
|
41
|
+
-------------------------------------------
|
|
42
|
+
|
|
43
|
+
This implementation is specific to :class:`RecursivelyEnumeratedSet_forest`, and uses its
|
|
44
|
+
properties to do its job. Not only mapping and reducing but also
|
|
45
|
+
**generating the elements** of `S` is done on different processors.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
.. _basic-usage:
|
|
49
|
+
|
|
50
|
+
How can I use all that stuff?
|
|
51
|
+
-----------------------------
|
|
52
|
+
|
|
53
|
+
First, you need to set the environment variable ``SAGE_NUM_THREADS`` to the
|
|
54
|
+
desired number of parallel threads to be used::
|
|
55
|
+
|
|
56
|
+
sage: import os # not tested
|
|
57
|
+
sage: os.environ["SAGE_NUM_THREADS"] = '8' # not tested
|
|
58
|
+
|
|
59
|
+
Second, you need the information necessary to describe a
|
|
60
|
+
:class:`RecursivelyEnumeratedSet_forest` representing your set `S` (see
|
|
61
|
+
:mod:`sage.sets.recursively_enumerated_set`). Then, you need to provide a
|
|
62
|
+
"map" function as well as a "reduce" function. Here are some examples:
|
|
63
|
+
|
|
64
|
+
* **Counting the number of elements.** In this situation, the map function
|
|
65
|
+
can be set to ``lambda x: 1``, and the reduce function just adds the
|
|
66
|
+
values together, i.e. ``lambda x, y: x + y``.
|
|
67
|
+
|
|
68
|
+
We count binary words of length `\leq 16`::
|
|
69
|
+
|
|
70
|
+
sage: seeds = [[]]
|
|
71
|
+
sage: succ = lambda l: [l + [0], l + [1]] if len(l) < 16 else []
|
|
72
|
+
sage: S = RecursivelyEnumeratedSet(seeds, succ,
|
|
73
|
+
....: structure='forest', enumeration='depth')
|
|
74
|
+
sage: map_function = lambda x: 1
|
|
75
|
+
sage: reduce_function = lambda x, y: x + y
|
|
76
|
+
sage: reduce_init = 0
|
|
77
|
+
sage: S.map_reduce(map_function, reduce_function, reduce_init)
|
|
78
|
+
131071
|
|
79
|
+
|
|
80
|
+
This matches the number of binary words of length `\leq 16`::
|
|
81
|
+
|
|
82
|
+
sage: factor(131071 + 1)
|
|
83
|
+
2^17
|
|
84
|
+
|
|
85
|
+
Note that the map and reduce functions here have the default values of the
|
|
86
|
+
:meth:`sage.sets.recursively_enumerated_set.RecursivelyEnumeratedSet_forest.map_reduce` method
|
|
87
|
+
so that the number of elements can be obtained more simply with::
|
|
88
|
+
|
|
89
|
+
sage: S.map_reduce()
|
|
90
|
+
131071
|
|
91
|
+
|
|
92
|
+
Instead of using :func:`RecursivelyEnumeratedSet`, one can directly use
|
|
93
|
+
:class:`RESetMapReduce`, which gives finer
|
|
94
|
+
control over the parallel execution (see :ref:`advanced-use` below)::
|
|
95
|
+
|
|
96
|
+
sage: from sage.parallel.map_reduce import RESetMapReduce
|
|
97
|
+
sage: S = RESetMapReduce(
|
|
98
|
+
....: roots=[[]],
|
|
99
|
+
....: children=lambda l: [l + [0], l + [1]] if len(l) < 16 else [],
|
|
100
|
+
....: map_function=lambda x: 1,
|
|
101
|
+
....: reduce_function=lambda x, y: x + y,
|
|
102
|
+
....: reduce_init=0)
|
|
103
|
+
sage: S.run()
|
|
104
|
+
131071
|
|
105
|
+
|
|
106
|
+
* **Generating series.** For this, take a Map function that associates a
|
|
107
|
+
monomial to each element of `S`, while the Reduce function is still equal to
|
|
108
|
+
``lambda x, y: x + y``.
|
|
109
|
+
|
|
110
|
+
We compute the generating series for counting binary words of each
|
|
111
|
+
length `\leq 16`::
|
|
112
|
+
|
|
113
|
+
sage: S = RecursivelyEnumeratedSet(
|
|
114
|
+
....: [[]], lambda l: [l + [0], l + [1]] if len(l) < 16 else [],
|
|
115
|
+
....: structure='forest', enumeration='depth')
|
|
116
|
+
sage: x = polygen(ZZ)
|
|
117
|
+
sage: sp = S.map_reduce(
|
|
118
|
+
....: map_function=lambda z: x**len(z),
|
|
119
|
+
....: reduce_function=lambda x, y: x + y,
|
|
120
|
+
....: reduce_init=0)
|
|
121
|
+
sage: sp
|
|
122
|
+
65536*x^16 + 32768*x^15 + 16384*x^14 + 8192*x^13 + 4096*x^12
|
|
123
|
+
+ 2048*x^11 + 1024*x^10 + 512*x^9 + 256*x^8 + 128*x^7 + 64*x^6
|
|
124
|
+
+ 32*x^5 + 16*x^4 + 8*x^3 + 4*x^2 + 2*x + 1
|
|
125
|
+
|
|
126
|
+
This is of course `\sum_{i=0}^{16} (2x)^i`::
|
|
127
|
+
|
|
128
|
+
sage: sp == sum((2*x)^i for i in range(17))
|
|
129
|
+
True
|
|
130
|
+
|
|
131
|
+
Here is another example where we count permutations of size `\leq 8` (here
|
|
132
|
+
we use the default values)::
|
|
133
|
+
|
|
134
|
+
sage: S = RecursivelyEnumeratedSet(
|
|
135
|
+
....: [[]],
|
|
136
|
+
....: lambda l: ([l[:i] + [len(l)] + l[i:]
|
|
137
|
+
....: for i in range(len(l) + 1)] if len(l) < 8 else []),
|
|
138
|
+
....: structure='forest',
|
|
139
|
+
....: enumeration='depth')
|
|
140
|
+
sage: x = polygen(ZZ)
|
|
141
|
+
sage: sp = S.map_reduce(lambda z: x**len(z)); sp
|
|
142
|
+
40320*x^8 + 5040*x^7 + 720*x^6 + 120*x^5 + 24*x^4 + 6*x^3 + 2*x^2 + x + 1
|
|
143
|
+
|
|
144
|
+
This is of course `\sum_{i=0}^{8} i! x^i`::
|
|
145
|
+
|
|
146
|
+
sage: sp == sum(factorial(i)*x^i for i in range(9))
|
|
147
|
+
True
|
|
148
|
+
|
|
149
|
+
* **Post Processing.** We now demonstrate the use of ``post_process``. We
|
|
150
|
+
generate the permutation as previously, but we only perform the map/reduce
|
|
151
|
+
computation on those of even ``len``. Of course we get the even part of the
|
|
152
|
+
previous generating series::
|
|
153
|
+
|
|
154
|
+
sage: S = RecursivelyEnumeratedSet(
|
|
155
|
+
....: [[]],
|
|
156
|
+
....: lambda l: ([l[:i] + [len(l) + 1] + l[i:]
|
|
157
|
+
....: for i in range(len(l) + 1)] if len(l) < 8 else []),
|
|
158
|
+
....: post_process=lambda l: l if len(l) % 2 == 0 else None,
|
|
159
|
+
....: structure='forest',
|
|
160
|
+
....: enumeration='depth')
|
|
161
|
+
sage: sp = S.map_reduce(lambda z: x**len(z)); sp
|
|
162
|
+
40320*x^8 + 720*x^6 + 24*x^4 + 2*x^2 + 1
|
|
163
|
+
|
|
164
|
+
This is also useful for example to call a constructor on the generated
|
|
165
|
+
elements::
|
|
166
|
+
|
|
167
|
+
sage: S = RecursivelyEnumeratedSet(
|
|
168
|
+
....: [[]],
|
|
169
|
+
....: lambda l: ([l[:i] + [len(l) + 1] + l[i:]
|
|
170
|
+
....: for i in range(len(l) + 1)] if len(l) < 5 else []),
|
|
171
|
+
....: post_process=lambda l: Permutation(l) if len(l) == 5 else None,
|
|
172
|
+
....: structure='forest',
|
|
173
|
+
....: enumeration='depth')
|
|
174
|
+
sage: x = polygen(ZZ)
|
|
175
|
+
sage: sp = S.map_reduce(lambda z: x**z.number_of_inversions()); sp
|
|
176
|
+
x^10 + 4*x^9 + 9*x^8 + 15*x^7 + 20*x^6 + 22*x^5 + 20*x^4 + 15*x^3 + 9*x^2 + 4*x + 1
|
|
177
|
+
|
|
178
|
+
We get here a polynomial which is the `q`-factorial (in the variable `x`) of `5`,
|
|
179
|
+
that is, `\prod_{i=1}^{5} \frac{1-x^i}{1-x}`::
|
|
180
|
+
|
|
181
|
+
sage: x = polygen(ZZ)
|
|
182
|
+
sage: prod((1-x^i)//(1-x) for i in range(1, 6))
|
|
183
|
+
x^10 + 4*x^9 + 9*x^8 + 15*x^7 + 20*x^6 + 22*x^5 + 20*x^4 + 15*x^3 + 9*x^2 + 4*x + 1
|
|
184
|
+
|
|
185
|
+
Compare::
|
|
186
|
+
|
|
187
|
+
sage: from sage.combinat.q_analogues import q_factorial
|
|
188
|
+
sage: q_factorial(5)
|
|
189
|
+
q^10 + 4*q^9 + 9*q^8 + 15*q^7 + 20*q^6 + 22*q^5 + 20*q^4 + 15*q^3 + 9*q^2 + 4*q + 1
|
|
190
|
+
|
|
191
|
+
* **Listing the objects.** One can also compute the list of objects in a
|
|
192
|
+
:class:`RecursivelyEnumeratedSet_forest>`
|
|
193
|
+
using :class:`RESetMapReduce`. As an example, we compute the set of numbers
|
|
194
|
+
between 1 and 63, generated by their binary expansion::
|
|
195
|
+
|
|
196
|
+
sage: S = RecursivelyEnumeratedSet(
|
|
197
|
+
....: [1],
|
|
198
|
+
....: lambda l: [(l<<1)|0, (l<<1)|1] if l < 1<<5 else [],
|
|
199
|
+
....: structure='forest',
|
|
200
|
+
....: enumeration='depth')
|
|
201
|
+
|
|
202
|
+
Here is the list computed without :class:`RESetMapReduce`::
|
|
203
|
+
|
|
204
|
+
sage: serial = list(S)
|
|
205
|
+
sage: serial
|
|
206
|
+
[1, 2, 4, 8, 16, 32, 33, 17, 34, 35, 9, 18, 36, 37, 19, 38, 39, 5, 10,
|
|
207
|
+
20, 40, 41, 21, 42, 43, 11, 22, 44, 45, 23, 46, 47, 3, 6, 12, 24, 48,
|
|
208
|
+
49, 25, 50, 51, 13, 26, 52, 53, 27, 54, 55, 7, 14, 28, 56, 57, 29, 58,
|
|
209
|
+
59, 15, 30, 60, 61, 31, 62, 63]
|
|
210
|
+
|
|
211
|
+
Here is how to perform the parallel computation. The order of the lists
|
|
212
|
+
depends on the synchronisation of the various computation processes and
|
|
213
|
+
therefore should be considered as random::
|
|
214
|
+
|
|
215
|
+
sage: parall = S.map_reduce(lambda x: [x], lambda x, y: x + y, [])
|
|
216
|
+
sage: parall # random
|
|
217
|
+
[1, 3, 7, 15, 31, 63, 62, 30, 61, 60, 14, 29, 59, 58, 28, 57, 56, 6, 13,
|
|
218
|
+
27, 55, 54, 26, 53, 52, 12, 25, 51, 50, 24, 49, 48, 2, 5, 11, 23, 47,
|
|
219
|
+
46, 22, 45, 44, 10, 21, 43, 42, 20, 41, 40, 4, 9, 19, 39, 38, 18, 37,
|
|
220
|
+
36, 8, 17, 35, 34, 16, 33, 32]
|
|
221
|
+
sage: sorted(serial) == sorted(parall)
|
|
222
|
+
True
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
.. _advanced-use:
|
|
226
|
+
|
|
227
|
+
Advanced use
|
|
228
|
+
------------
|
|
229
|
+
|
|
230
|
+
Fine control over the execution of a map/reduce computation is achieved
|
|
231
|
+
via parameters passed to the :meth:`RESetMapReduce.run` method.
|
|
232
|
+
The following three parameters can be used:
|
|
233
|
+
|
|
234
|
+
- ``max_proc`` -- (integer, default: ``None``) if given, the
|
|
235
|
+
maximum number of worker processors to use. The actual number
|
|
236
|
+
is also bounded by the value of the environment variable
|
|
237
|
+
``SAGE_NUM_THREADS`` (the number of cores by default).
|
|
238
|
+
- ``timeout`` -- a timeout on the computation (default: ``None``)
|
|
239
|
+
- ``reduce_locally`` -- whether the workers should reduce locally
|
|
240
|
+
their work or sends results to the master as soon as possible.
|
|
241
|
+
See :class:`RESetMapReduceWorker` for details.
|
|
242
|
+
|
|
243
|
+
Here is an example or how to deal with timeout::
|
|
244
|
+
|
|
245
|
+
sage: from sage.parallel.map_reduce import (RESetMPExample, AbortError)
|
|
246
|
+
sage: EX = RESetMPExample(maxl=100)
|
|
247
|
+
sage: try:
|
|
248
|
+
....: res = EX.run(timeout=float(0.01))
|
|
249
|
+
....: except AbortError:
|
|
250
|
+
....: print("Computation timeout")
|
|
251
|
+
....: else:
|
|
252
|
+
....: print("Computation normally finished")
|
|
253
|
+
....: res
|
|
254
|
+
Computation timeout
|
|
255
|
+
|
|
256
|
+
The following should not timeout even on a very slow machine::
|
|
257
|
+
|
|
258
|
+
sage: EX = RESetMPExample(maxl=8)
|
|
259
|
+
sage: try:
|
|
260
|
+
....: res = EX.run(timeout=60)
|
|
261
|
+
....: except AbortError:
|
|
262
|
+
....: print("Computation Timeout")
|
|
263
|
+
....: else:
|
|
264
|
+
....: print("Computation normally finished")
|
|
265
|
+
....: res
|
|
266
|
+
Computation normally finished
|
|
267
|
+
40320*x^8 + 5040*x^7 + 720*x^6 + 120*x^5 + 24*x^4 + 6*x^3 + 2*x^2 + x + 1
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
As for ``reduce_locally``, one should not see any difference, except for speed
|
|
271
|
+
during normal usage. Most of the time one should leave it set to ``True``,
|
|
272
|
+
unless one sets up a mechanism to consume the partial results as soon as they
|
|
273
|
+
arrive. See :class:`RESetParallelIterator` and in particular the ``__iter__``
|
|
274
|
+
method for a example of consumer use.
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
.. _profiling:
|
|
278
|
+
|
|
279
|
+
Profiling
|
|
280
|
+
---------
|
|
281
|
+
|
|
282
|
+
It is possible to profile a map/reduce computation. First we create a
|
|
283
|
+
:class:`RESetMapReduce` object::
|
|
284
|
+
|
|
285
|
+
sage: from sage.parallel.map_reduce import RESetMapReduce
|
|
286
|
+
sage: S = RESetMapReduce(
|
|
287
|
+
....: roots=[[]],
|
|
288
|
+
....: children=lambda l: [l + [0], l + [1]] if len(l) < 16 else [],
|
|
289
|
+
....: map_function=lambda x: 1,
|
|
290
|
+
....: reduce_function=lambda x, y: x + y,
|
|
291
|
+
....: reduce_init=0)
|
|
292
|
+
|
|
293
|
+
The profiling is activated by the ``profile`` parameter. The value provided
|
|
294
|
+
should be a prefix (including a possible directory) for the profile dump::
|
|
295
|
+
|
|
296
|
+
sage: import tempfile
|
|
297
|
+
sage: d = tempfile.TemporaryDirectory(prefix='RESetMR_profile')
|
|
298
|
+
sage: res = S.run(profile=d.name) # random
|
|
299
|
+
[RESetMapReduceWorker-1:58] (20:00:41.444) Profiling in
|
|
300
|
+
/home/user/.sage/temp/.../32414/RESetMR_profilewRCRAx/profcomp1
|
|
301
|
+
...
|
|
302
|
+
[RESetMapReduceWorker-1:57] (20:00:41.444) Profiling in
|
|
303
|
+
/home/user/.sage/temp/.../32414/RESetMR_profilewRCRAx/profcomp0
|
|
304
|
+
...
|
|
305
|
+
sage: res
|
|
306
|
+
131071
|
|
307
|
+
|
|
308
|
+
In this example, the profiles have been dumped in files such as
|
|
309
|
+
``profcomp0``. One can then load and print them as follows. See
|
|
310
|
+
:class:`cProfile.Profile` for more details::
|
|
311
|
+
|
|
312
|
+
sage: import cProfile, pstats
|
|
313
|
+
sage: st = pstats.Stats(d.name+'0')
|
|
314
|
+
sage: st.strip_dirs().sort_stats('cumulative').print_stats() # random
|
|
315
|
+
...
|
|
316
|
+
Ordered by: cumulative time
|
|
317
|
+
|
|
318
|
+
ncalls tottime percall cumtime percall filename:lineno(function)
|
|
319
|
+
1 0.023 0.023 0.432 0.432 map_reduce.py:1211(run_myself)
|
|
320
|
+
11968 0.151 0.000 0.223 0.000 map_reduce.py:1292(walk_branch_locally)
|
|
321
|
+
...
|
|
322
|
+
<pstats.Stats instance at 0x7fedea40c6c8>
|
|
323
|
+
|
|
324
|
+
Like a good neighbor we clean up our temporary directory as soon as
|
|
325
|
+
possible::
|
|
326
|
+
|
|
327
|
+
sage: d.cleanup()
|
|
328
|
+
|
|
329
|
+
.. SEEALSO::
|
|
330
|
+
|
|
331
|
+
`The Python Profilers <https://docs.python.org/2/library/profile.html>`_
|
|
332
|
+
for more detail on profiling in python.
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
.. _logging:
|
|
336
|
+
|
|
337
|
+
Logging
|
|
338
|
+
-------
|
|
339
|
+
|
|
340
|
+
The computation progress is logged through a :class:`logging.Logger` in
|
|
341
|
+
``sage.parallel.map_reduce.logger`` together with :class:`logging.StreamHandler`
|
|
342
|
+
and a :class:`logging.Formatter`. They are currently configured to print
|
|
343
|
+
warning messages to the console.
|
|
344
|
+
|
|
345
|
+
.. SEEALSO::
|
|
346
|
+
|
|
347
|
+
`Logging facility for Python <https://docs.python.org/2/library/logging.html>`_
|
|
348
|
+
for more detail on logging and log system configuration.
|
|
349
|
+
|
|
350
|
+
.. NOTE::
|
|
351
|
+
|
|
352
|
+
Calls to logger which involve printing the node are commented out in the
|
|
353
|
+
code, because the printing (to a string) of the node can be very time
|
|
354
|
+
consuming depending on the node and it happens before the decision whether
|
|
355
|
+
the logger should record the string or drop it.
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
.. _protocol-description:
|
|
359
|
+
|
|
360
|
+
How does it work ?
|
|
361
|
+
------------------
|
|
362
|
+
|
|
363
|
+
The scheduling algorithm we use here is any adaptation of :wikipedia:`Work_stealing`:
|
|
364
|
+
|
|
365
|
+
In a work stealing scheduler, each processor in a computer system has a
|
|
366
|
+
queue of work items (computational tasks, threads) to perform. [...]. Each
|
|
367
|
+
work items are initially put on the queue of the processor executing the
|
|
368
|
+
work item. When a processor runs out of work, it looks at the queues of
|
|
369
|
+
other processors and "steals" their work items. In effect, work stealing
|
|
370
|
+
distributes the scheduling work over idle processors, and as long as all
|
|
371
|
+
processors have work to do, no scheduling overhead occurs.
|
|
372
|
+
|
|
373
|
+
For communication we use Python's basic :mod:`multiprocessing` module. We
|
|
374
|
+
first describe the different actors and communication tools used by the
|
|
375
|
+
system. The work is done under the coordination of a **master** object (an
|
|
376
|
+
instance of :class:`RESetMapReduce`) by a bunch of **worker** objects
|
|
377
|
+
(instances of :class:`RESetMapReduceWorker`).
|
|
378
|
+
|
|
379
|
+
Each running map reduce instance works on a :class:`RecursivelyEnumeratedSet_forest>` called here `C` and is
|
|
380
|
+
coordinated by a :class:`RESetMapReduce` object called the **master**. The
|
|
381
|
+
master is in charge of launching the work, gathering the results and cleaning
|
|
382
|
+
up at the end of the computation. It doesn't perform any computation
|
|
383
|
+
associated to the generation of the element `C` nor the computation of the
|
|
384
|
+
mapped function. It however occasionally perform a reduce, but most reducing
|
|
385
|
+
is by default done by the workers. Also thanks to the work-stealing algorithm,
|
|
386
|
+
the master is only involved in detecting the termination of the computation
|
|
387
|
+
but all the load balancing is done at the level of the workers.
|
|
388
|
+
|
|
389
|
+
Workers are instances of :class:`RESetMapReduceWorker`. They are responsible
|
|
390
|
+
for doing the actual computations: element generation, mapping and reducing.
|
|
391
|
+
They are also responsible for the load balancing thanks to work-stealing.
|
|
392
|
+
|
|
393
|
+
Here is a description of the attributes of the **master** relevant to the
|
|
394
|
+
map-reduce protocol:
|
|
395
|
+
|
|
396
|
+
- ``_results`` -- a :class:`~multiprocessing.queues.SimpleQueue` where
|
|
397
|
+
the master gathers the results sent by the workers
|
|
398
|
+
- ``_active_tasks`` -- a :class:`~multiprocessing.Semaphore` recording
|
|
399
|
+
the number of active tasks; the work is complete when it reaches 0
|
|
400
|
+
- ``_done`` -- a :class:`~multiprocessing.Lock` which ensures that
|
|
401
|
+
shutdown is done only once
|
|
402
|
+
- ``_aborted`` -- a :func:`~multiprocessing.Value` storing a shared
|
|
403
|
+
:class:`ctypes.c_bool` which is ``True`` if the computation was aborted
|
|
404
|
+
before all workers ran out of work
|
|
405
|
+
- ``_workers`` -- list of :class:`RESetMapReduceWorker` objects
|
|
406
|
+
Each worker is identified by its position in this list
|
|
407
|
+
|
|
408
|
+
Each **worker** is a process (:class:`RESetMapReduceWorker` inherits from
|
|
409
|
+
:class:`~multiprocessing.Process`) which contains:
|
|
410
|
+
|
|
411
|
+
- ``worker._iproc`` -- the identifier of the worker that is its position in the
|
|
412
|
+
master's list of workers
|
|
413
|
+
- ``worker._todo`` -- a :class:`collections.deque` storing of nodes of the
|
|
414
|
+
worker. It is used as a stack by the worker. Thiefs steal from the bottom of
|
|
415
|
+
this queue.
|
|
416
|
+
- ``worker._request`` -- a :class:`~multiprocessing.queues.SimpleQueue` storing
|
|
417
|
+
steal request submitted to ``worker``
|
|
418
|
+
- ``worker._read_task``, ``worker._write_task`` -- a
|
|
419
|
+
:class:`~multiprocessing.queues.Pipe` used to transfer node during steal
|
|
420
|
+
- ``worker._thief`` -- a :class:`~threading.Thread` which is in charge of
|
|
421
|
+
stealing from ``worker._todo``
|
|
422
|
+
|
|
423
|
+
Here is a schematic of the architecture:
|
|
424
|
+
|
|
425
|
+
.. _figure-map_reduce_arch:
|
|
426
|
+
|
|
427
|
+
.. figure:: ../../media/map_reduce_arch.png
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
How thefts are performed
|
|
431
|
+
------------------------
|
|
432
|
+
|
|
433
|
+
During normal time, that is, when all workers are active, a worker ``W`` is
|
|
434
|
+
iterating though a loop inside
|
|
435
|
+
:meth:`RESetMapReduceWorker.walk_branch_locally`. Work nodes are taken from
|
|
436
|
+
and new nodes ``W._todo`` are appended to ``W._todo``. When a worker ``W``
|
|
437
|
+
runs out of work, that is, when ``worker._todo`` is empty, it tries to steal
|
|
438
|
+
some work (i.e., a node) from another worker. This is performed in the
|
|
439
|
+
:meth:`RESetMapReduceWorker.steal` method.
|
|
440
|
+
|
|
441
|
+
From the point of view of ``W``, here is what happens:
|
|
442
|
+
|
|
443
|
+
- ``W`` signals to the master that it is idle: ``master._signal_task_done``;
|
|
444
|
+
- ``W`` chooses a victim ``V`` at random;
|
|
445
|
+
- ``W`` sends a request to ``V``: it puts its identifier into ``V._request``;
|
|
446
|
+
- ``W`` tries to read a node from ``W._read_task``. Then three things may happen:
|
|
447
|
+
|
|
448
|
+
+ a proper node is read. Then the theft was a success and ``W`` starts
|
|
449
|
+
working locally on the received node.
|
|
450
|
+
+ ``None`` is received. This means that ``V`` was idle. Then ``W`` tries
|
|
451
|
+
another victim.
|
|
452
|
+
+ :exc:`AbortError` is received. This means either that the computation was
|
|
453
|
+
aborted or that it simply succeeded and that no more work is required by
|
|
454
|
+
``W``. Therefore an :exc:`AbortError` exception is raised leading ``W`` to
|
|
455
|
+
shutdown.
|
|
456
|
+
|
|
457
|
+
We now describe the protocol on the victim's side. Each worker process contains
|
|
458
|
+
a :class:`Thread` which we call ``T`` for thief which acts like some kind of
|
|
459
|
+
Troyan horse during theft. It is normally blocked waiting for a steal request.
|
|
460
|
+
|
|
461
|
+
From the point of view of ``V`` and ``T``, here is what happens:
|
|
462
|
+
|
|
463
|
+
- during normal time, ``T`` is blocked waiting on ``V._request``;
|
|
464
|
+
- upon steal request, ``T`` wakes up receiving the identification of ``W``;
|
|
465
|
+
- ``T`` signals to the master that a new task is starting by
|
|
466
|
+
``master._signal_task_start``;
|
|
467
|
+
- Two things may happen depending if the queue ``V._todo`` is empty or not.
|
|
468
|
+
Remark that due to the GIL, there is no parallel execution between the
|
|
469
|
+
victim ``V`` and its thief thread ``T``.
|
|
470
|
+
|
|
471
|
+
+ If ``V._todo`` is empty, then ``None`` is answered on
|
|
472
|
+
``W._write_task``. The task is immediately signaled to end the master
|
|
473
|
+
through ``master._signal_task_done``.
|
|
474
|
+
+ Otherwise, a node is removed from the bottom of ``V._todo``. The node is
|
|
475
|
+
sent to ``W`` on ``W._write_task``. The task will be ended by ``W``, that
|
|
476
|
+
is, when finished working on the subtree rooted at the node, ``W`` will
|
|
477
|
+
call ``master._signal_task_done``.
|
|
478
|
+
|
|
479
|
+
The end of the computation
|
|
480
|
+
--------------------------
|
|
481
|
+
|
|
482
|
+
To detect when a computation is finished, a synchronized integer is kept which
|
|
483
|
+
counts the number of active tasks. This is essentially a semaphore but
|
|
484
|
+
semaphores are broken on Darwin OSes so we ship two implementations depending
|
|
485
|
+
on the OS (see :class:`ActiveTaskCounter` and :class:`ActiveTaskCounterDarwin`
|
|
486
|
+
and the note below).
|
|
487
|
+
|
|
488
|
+
When a worker finishes working on a task, it calls
|
|
489
|
+
``master._signal_task_done``. This decreases the task counter
|
|
490
|
+
``master._active_tasks``. When it reaches 0, it means that there are no more
|
|
491
|
+
nodes: the work is completed. The worker executes ``master._shutdown``
|
|
492
|
+
which sends :exc:`AbortError` to all ``worker._request`` and
|
|
493
|
+
``worker._write_task`` queues. Each worker or thief thread receiving such
|
|
494
|
+
a message raises the corresponding exception, therefore stopping its work. A
|
|
495
|
+
lock called ``master._done`` ensures that shutdown is only done once.
|
|
496
|
+
|
|
497
|
+
Finally, it is also possible to interrupt the computation before its ends,
|
|
498
|
+
by calling ``master.abort()``. This is achieved by setting
|
|
499
|
+
``master._active_tasks`` to 0 and calling ``master._shutdown``.
|
|
500
|
+
|
|
501
|
+
.. warning:: The macOS Semaphore bug
|
|
502
|
+
|
|
503
|
+
Darwin OSes do not correctly implement POSIX's semaphore semantic.
|
|
504
|
+
Indeed, on these systems, acquire may fail and return False not only when
|
|
505
|
+
the semaphore is equal to zero but also **because someone else is trying
|
|
506
|
+
to acquire** at the same time. This makes using Semaphores impossible
|
|
507
|
+
on macOS so that on these systems we use a synchronized integer instead.
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
.. _examples:
|
|
511
|
+
|
|
512
|
+
Are there examples of classes?
|
|
513
|
+
------------------------------
|
|
514
|
+
|
|
515
|
+
Yes! Here they are:
|
|
516
|
+
|
|
517
|
+
- :class:`RESetMPExample` -- a simple basic example
|
|
518
|
+
- :class:`RESetParallelIterator` -- a more advanced example using non standard
|
|
519
|
+
communication configuration
|
|
520
|
+
|
|
521
|
+
Tests
|
|
522
|
+
-----
|
|
523
|
+
|
|
524
|
+
Generating series for the sum of strictly decreasing lists of integers
|
|
525
|
+
smaller than 15::
|
|
526
|
+
|
|
527
|
+
sage: y = polygen(ZZ, 'y')
|
|
528
|
+
sage: R = RESetMapReduce(
|
|
529
|
+
....: roots=[([], 0, 0)] + [([i], i, i) for i in range(1, 15)],
|
|
530
|
+
....: children=lambda list_sum_last:
|
|
531
|
+
....: [(list_sum_last[0] + [i], list_sum_last[1] + i, i)
|
|
532
|
+
....: for i in range(1, list_sum_last[2])],
|
|
533
|
+
....: map_function=lambda li_sum_dummy: y**li_sum_dummy[1])
|
|
534
|
+
sage: sg = R.run()
|
|
535
|
+
sage: sg == prod((1 + y**i) for i in range(1, 15))
|
|
536
|
+
True
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
Classes and methods
|
|
540
|
+
-------------------
|
|
541
|
+
"""
|
|
542
|
+
|
|
543
|
+
# ****************************************************************************
|
|
544
|
+
# This program is free software: you can redistribute it and/or modify
|
|
545
|
+
# it under the terms of the GNU General Public License as published by
|
|
546
|
+
# the Free Software Foundation, either version 2 of the License, or
|
|
547
|
+
# (at your option) any later version.
|
|
548
|
+
# https://www.gnu.org/licenses/
|
|
549
|
+
# ****************************************************************************
|
|
550
|
+
import copy
|
|
551
|
+
import ctypes
|
|
552
|
+
import logging
|
|
553
|
+
import multiprocessing as mp
|
|
554
|
+
import queue
|
|
555
|
+
import random
|
|
556
|
+
import sys
|
|
557
|
+
from collections import deque
|
|
558
|
+
from threading import Thread
|
|
559
|
+
|
|
560
|
+
from sage.misc.lazy_attribute import lazy_attribute
|
|
561
|
+
from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet # _generic
|
|
562
|
+
|
|
563
|
+
logger = logging.getLogger(__name__)
|
|
564
|
+
logger.__doc__ = ("""
|
|
565
|
+
A logger for :mod:`sage.parallel.map_reduce`
|
|
566
|
+
|
|
567
|
+
.. SEEALSO::
|
|
568
|
+
|
|
569
|
+
`Logging facility for Python <https://docs.python.org/2/library/logging.html>`_
|
|
570
|
+
for more detail on logging and log system configuration.
|
|
571
|
+
""")
|
|
572
|
+
logger.setLevel(logging.WARN)
|
|
573
|
+
# logger.setLevel(logging.INFO)
|
|
574
|
+
# logger.setLevel(logging.DEBUG)
|
|
575
|
+
ch = logging.StreamHandler()
|
|
576
|
+
ch.setLevel(logging.DEBUG)
|
|
577
|
+
formatter = logging.Formatter(
|
|
578
|
+
'[%(processName)s-%(threadName)s] (%(asctime)s.%(msecs)03.f) %(message)s',
|
|
579
|
+
datefmt='%H:%M:%S')
|
|
580
|
+
ch.setFormatter(formatter)
|
|
581
|
+
logger.addHandler(ch)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
# Set up a multiprocessing context to use for this modules (using the
|
|
585
|
+
# 'fork' method which is basically same as on Python 2)
|
|
586
|
+
mp = mp.get_context('fork')
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def proc_number(max_proc=None):
|
|
590
|
+
r"""
|
|
591
|
+
Return the number of processes to use.
|
|
592
|
+
|
|
593
|
+
INPUT:
|
|
594
|
+
|
|
595
|
+
- ``max_proc`` -- an upper bound on the number of processes or ``None``
|
|
596
|
+
|
|
597
|
+
EXAMPLES::
|
|
598
|
+
|
|
599
|
+
sage: from sage.parallel.map_reduce import proc_number
|
|
600
|
+
sage: proc_number() # random
|
|
601
|
+
8
|
|
602
|
+
sage: proc_number(max_proc=1)
|
|
603
|
+
1
|
|
604
|
+
sage: proc_number(max_proc=2) in (1, 2)
|
|
605
|
+
True
|
|
606
|
+
"""
|
|
607
|
+
from sage.parallel.ncpus import ncpus
|
|
608
|
+
n = ncpus()
|
|
609
|
+
if max_proc is None:
|
|
610
|
+
return n
|
|
611
|
+
else:
|
|
612
|
+
return min(max_proc, n)
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
class AbortError(Exception):
|
|
616
|
+
r"""
|
|
617
|
+
Exception for aborting parallel computations.
|
|
618
|
+
|
|
619
|
+
This is used both as exception or as abort message.
|
|
620
|
+
|
|
621
|
+
TESTS::
|
|
622
|
+
|
|
623
|
+
sage: from sage.parallel.map_reduce import AbortError
|
|
624
|
+
sage: raise AbortError
|
|
625
|
+
Traceback (most recent call last):
|
|
626
|
+
...
|
|
627
|
+
AbortError
|
|
628
|
+
"""
|
|
629
|
+
pass
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
class ActiveTaskCounterDarwin:
|
|
633
|
+
r"""
|
|
634
|
+
Handling the number of active tasks.
|
|
635
|
+
|
|
636
|
+
A class for handling the number of active tasks in a distributed
|
|
637
|
+
computation process. This is essentially a semaphore, but Darwin OSes
|
|
638
|
+
do not correctly implement POSIX's semaphore semantic. So we use
|
|
639
|
+
a shared integer with a lock.
|
|
640
|
+
"""
|
|
641
|
+
def __init__(self, task_number):
|
|
642
|
+
r"""
|
|
643
|
+
TESTS::
|
|
644
|
+
|
|
645
|
+
sage: from sage.parallel.map_reduce import ActiveTaskCounterDarwin as ATC
|
|
646
|
+
sage: t = ATC(4)
|
|
647
|
+
sage: TestSuite(t).run(skip='_test_pickling', verbose=True)
|
|
648
|
+
running ._test_new() . . . pass
|
|
649
|
+
"""
|
|
650
|
+
self._active_tasks = mp.Value(ctypes.c_int, task_number)
|
|
651
|
+
self._lock = mp.Lock()
|
|
652
|
+
|
|
653
|
+
def __repr__(self):
|
|
654
|
+
"""
|
|
655
|
+
TESTS::
|
|
656
|
+
|
|
657
|
+
sage: from sage.parallel.map_reduce import ActiveTaskCounterDarwin as ATC
|
|
658
|
+
sage: ATC(4)
|
|
659
|
+
ActiveTaskCounter(value=4)
|
|
660
|
+
"""
|
|
661
|
+
return "ActiveTaskCounter(value=%s)" % (self._active_tasks.value)
|
|
662
|
+
|
|
663
|
+
def task_start(self):
|
|
664
|
+
r"""
|
|
665
|
+
Increment the task counter by one.
|
|
666
|
+
|
|
667
|
+
OUTPUT:
|
|
668
|
+
|
|
669
|
+
Calling :meth:`task_start` on a zero or negative counter returns 0,
|
|
670
|
+
otherwise increment the counter and returns its value after the
|
|
671
|
+
incrementation.
|
|
672
|
+
|
|
673
|
+
EXAMPLES::
|
|
674
|
+
|
|
675
|
+
sage: from sage.parallel.map_reduce import ActiveTaskCounterDarwin as ATC
|
|
676
|
+
sage: c = ATC(4); c
|
|
677
|
+
ActiveTaskCounter(value=4)
|
|
678
|
+
sage: c.task_start()
|
|
679
|
+
5
|
|
680
|
+
sage: c
|
|
681
|
+
ActiveTaskCounter(value=5)
|
|
682
|
+
|
|
683
|
+
Calling :meth:`task_start` on a zero counter does nothing::
|
|
684
|
+
|
|
685
|
+
sage: c = ATC(0)
|
|
686
|
+
sage: c.task_start()
|
|
687
|
+
0
|
|
688
|
+
sage: c
|
|
689
|
+
ActiveTaskCounter(value=0)
|
|
690
|
+
"""
|
|
691
|
+
logger.debug("_signal_task_start called")
|
|
692
|
+
with self._lock:
|
|
693
|
+
# The following test is not necessary but is allows active thieves to
|
|
694
|
+
# stop before receiving the poison pill.
|
|
695
|
+
if self._active_tasks.value <= 0:
|
|
696
|
+
return 0
|
|
697
|
+
self._active_tasks.value += 1
|
|
698
|
+
return self._active_tasks.value
|
|
699
|
+
|
|
700
|
+
def task_done(self):
|
|
701
|
+
r"""
|
|
702
|
+
Decrement the task counter by one.
|
|
703
|
+
|
|
704
|
+
OUTPUT:
|
|
705
|
+
|
|
706
|
+
Calling :meth:`task_done` decrements the counter and returns
|
|
707
|
+
its new value.
|
|
708
|
+
|
|
709
|
+
EXAMPLES::
|
|
710
|
+
|
|
711
|
+
sage: from sage.parallel.map_reduce import ActiveTaskCounterDarwin as ATC
|
|
712
|
+
sage: c = ATC(4); c
|
|
713
|
+
ActiveTaskCounter(value=4)
|
|
714
|
+
sage: c.task_done()
|
|
715
|
+
3
|
|
716
|
+
sage: c
|
|
717
|
+
ActiveTaskCounter(value=3)
|
|
718
|
+
|
|
719
|
+
sage: c = ATC(0)
|
|
720
|
+
sage: c.task_done()
|
|
721
|
+
-1
|
|
722
|
+
"""
|
|
723
|
+
logger.debug("_signal_task_done called")
|
|
724
|
+
with self._lock:
|
|
725
|
+
self._active_tasks.value -= 1
|
|
726
|
+
return self._active_tasks.value
|
|
727
|
+
|
|
728
|
+
def abort(self):
|
|
729
|
+
r"""
|
|
730
|
+
Set the task counter to zero.
|
|
731
|
+
|
|
732
|
+
EXAMPLES::
|
|
733
|
+
|
|
734
|
+
sage: from sage.parallel.map_reduce import ActiveTaskCounterDarwin as ATC
|
|
735
|
+
sage: c = ATC(4); c
|
|
736
|
+
ActiveTaskCounter(value=4)
|
|
737
|
+
sage: c.abort()
|
|
738
|
+
sage: c
|
|
739
|
+
ActiveTaskCounter(value=0)
|
|
740
|
+
"""
|
|
741
|
+
with self._lock:
|
|
742
|
+
self._active_tasks.value = 0
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
class ActiveTaskCounterPosix:
|
|
746
|
+
r"""
|
|
747
|
+
Handling the number of active tasks.
|
|
748
|
+
|
|
749
|
+
A class for handling the number of active tasks in a distributed
|
|
750
|
+
computation process. This is the standard implementation on POSIX
|
|
751
|
+
compliant OSes. We essentially wrap a semaphore.
|
|
752
|
+
|
|
753
|
+
.. NOTE::
|
|
754
|
+
|
|
755
|
+
A legitimate question is whether there is a need in keeping the two
|
|
756
|
+
implementations. I ran the following experiment on my machine::
|
|
757
|
+
|
|
758
|
+
S = RecursivelyEnumeratedSet(
|
|
759
|
+
[[]],
|
|
760
|
+
lambda l: ([l[:i] + [len(l)] + l[i:]
|
|
761
|
+
for i in range(len(l) + 1)]
|
|
762
|
+
if len(l) < NNN else []),
|
|
763
|
+
structure='forest',
|
|
764
|
+
enumeration='depth')
|
|
765
|
+
%time sp = S.map_reduce(lambda z: x**len(z)); sp
|
|
766
|
+
|
|
767
|
+
For NNN = 10, averaging a dozen of runs, I got:
|
|
768
|
+
|
|
769
|
+
- Posix compliant implementation: 17.04 s
|
|
770
|
+
- Darwin implementation: 18.26 s
|
|
771
|
+
|
|
772
|
+
So there is a non negligible overhead. It will probably be worth it
|
|
773
|
+
if we try to cythonize the code. So I'm keeping both implementations.
|
|
774
|
+
"""
|
|
775
|
+
def __init__(self, task_number):
|
|
776
|
+
r"""
|
|
777
|
+
TESTS::
|
|
778
|
+
|
|
779
|
+
sage: from sage.parallel.map_reduce import ActiveTaskCounter as ATC
|
|
780
|
+
sage: t = ATC(4)
|
|
781
|
+
sage: TestSuite(t).run(skip='_test_pickling', verbose=True)
|
|
782
|
+
running ._test_new() . . . pass
|
|
783
|
+
"""
|
|
784
|
+
self._active_tasks = mp.Semaphore(task_number)
|
|
785
|
+
|
|
786
|
+
def __repr__(self):
|
|
787
|
+
"""
|
|
788
|
+
TESTS::
|
|
789
|
+
|
|
790
|
+
sage: from sage.parallel.map_reduce import ActiveTaskCounter as ATC
|
|
791
|
+
sage: ATC(4)
|
|
792
|
+
ActiveTaskCounter(value=4)
|
|
793
|
+
"""
|
|
794
|
+
return "ActiveTaskCounter(value=%s)" % (self._active_tasks.get_value())
|
|
795
|
+
|
|
796
|
+
def task_start(self):
|
|
797
|
+
r"""
|
|
798
|
+
Increment the task counter by one.
|
|
799
|
+
|
|
800
|
+
OUTPUT:
|
|
801
|
+
|
|
802
|
+
Calling :meth:`task_start` on a zero or negative counter returns 0,
|
|
803
|
+
otherwise it increments the counter and returns its new value.
|
|
804
|
+
|
|
805
|
+
EXAMPLES::
|
|
806
|
+
|
|
807
|
+
sage: from sage.parallel.map_reduce import ActiveTaskCounter as ATC
|
|
808
|
+
sage: c = ATC(4); c
|
|
809
|
+
ActiveTaskCounter(value=4)
|
|
810
|
+
sage: c.task_start()
|
|
811
|
+
5
|
|
812
|
+
sage: c
|
|
813
|
+
ActiveTaskCounter(value=5)
|
|
814
|
+
|
|
815
|
+
Calling :meth:`task_start` on a zero counter does nothing::
|
|
816
|
+
|
|
817
|
+
sage: c = ATC(0)
|
|
818
|
+
sage: c.task_start()
|
|
819
|
+
0
|
|
820
|
+
sage: c
|
|
821
|
+
ActiveTaskCounter(value=0)
|
|
822
|
+
"""
|
|
823
|
+
logger.debug("_signal_task_start called")
|
|
824
|
+
# The following test is not necessary but is allows active thieves to
|
|
825
|
+
# stop before receiving the poison pill.
|
|
826
|
+
if self._active_tasks._semlock._is_zero():
|
|
827
|
+
return 0
|
|
828
|
+
self._active_tasks.release()
|
|
829
|
+
return self._active_tasks.get_value()
|
|
830
|
+
|
|
831
|
+
task_start.__doc__ = ActiveTaskCounterDarwin.task_start.__doc__
|
|
832
|
+
|
|
833
|
+
def task_done(self):
|
|
834
|
+
r"""
|
|
835
|
+
Decrement the task counter by one.
|
|
836
|
+
|
|
837
|
+
OUTPUT:
|
|
838
|
+
|
|
839
|
+
Calling :meth:`task_done` decrements the counter and returns
|
|
840
|
+
its new value.
|
|
841
|
+
|
|
842
|
+
EXAMPLES::
|
|
843
|
+
|
|
844
|
+
sage: from sage.parallel.map_reduce import ActiveTaskCounter as ATC
|
|
845
|
+
sage: c = ATC(4); c
|
|
846
|
+
ActiveTaskCounter(value=4)
|
|
847
|
+
sage: c.task_done()
|
|
848
|
+
3
|
|
849
|
+
sage: c
|
|
850
|
+
ActiveTaskCounter(value=3)
|
|
851
|
+
|
|
852
|
+
sage: c = ATC(0)
|
|
853
|
+
sage: c.task_done()
|
|
854
|
+
-1
|
|
855
|
+
"""
|
|
856
|
+
logger.debug("_signal_task_done called")
|
|
857
|
+
# We test if the semaphore counting the number of active tasks is
|
|
858
|
+
# becoming negative. This should not happen in normal
|
|
859
|
+
# computations. However, in case of abort, we artificially put the
|
|
860
|
+
# semaphore to 0 to stop the computation so it is needed.
|
|
861
|
+
if not self._active_tasks.acquire(False):
|
|
862
|
+
return -1
|
|
863
|
+
return self._active_tasks.get_value()
|
|
864
|
+
|
|
865
|
+
def abort(self):
|
|
866
|
+
r"""
|
|
867
|
+
Set the task counter to zero.
|
|
868
|
+
|
|
869
|
+
EXAMPLES::
|
|
870
|
+
|
|
871
|
+
sage: from sage.parallel.map_reduce import ActiveTaskCounter as ATC
|
|
872
|
+
sage: c = ATC(4); c
|
|
873
|
+
ActiveTaskCounter(value=4)
|
|
874
|
+
sage: c.abort()
|
|
875
|
+
sage: c
|
|
876
|
+
ActiveTaskCounter(value=0)
|
|
877
|
+
"""
|
|
878
|
+
while self._active_tasks.acquire(False):
|
|
879
|
+
pass
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
ActiveTaskCounter = (ActiveTaskCounterDarwin if sys.platform == 'darwin'
|
|
883
|
+
else ActiveTaskCounterPosix)
|
|
884
|
+
|
|
885
|
+
# ActiveTaskCounter = ActiveTaskCounterDarwin # to debug Darwin implementation
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
class RESetMapReduce:
|
|
889
|
+
r"""
|
|
890
|
+
Map-Reduce on recursively enumerated sets.
|
|
891
|
+
|
|
892
|
+
INPUT:
|
|
893
|
+
|
|
894
|
+
Description of the set:
|
|
895
|
+
|
|
896
|
+
- either ``forest=f`` -- where ``f`` is a :class:`RecursivelyEnumeratedSet_forest>`
|
|
897
|
+
|
|
898
|
+
- or a triple ``roots, children, post_process`` as follows
|
|
899
|
+
|
|
900
|
+
- ``roots=r`` -- the root of the enumeration
|
|
901
|
+
- ``children=c`` -- a function iterating through children nodes,
|
|
902
|
+
given a parent node
|
|
903
|
+
- ``post_process=p`` -- a post-processing function
|
|
904
|
+
|
|
905
|
+
The option ``post_process`` allows for customizing the nodes that
|
|
906
|
+
are actually produced. Furthermore, if ``post_process(x)`` returns ``None``,
|
|
907
|
+
then ``x`` won't be output at all.
|
|
908
|
+
|
|
909
|
+
Description of the map/reduce operation:
|
|
910
|
+
|
|
911
|
+
- ``map_function=f`` -- (default: ``None``)
|
|
912
|
+
- ``reduce_function=red`` -- (default: ``None``)
|
|
913
|
+
- ``reduce_init=init`` -- (default: ``None``)
|
|
914
|
+
|
|
915
|
+
.. SEEALSO::
|
|
916
|
+
|
|
917
|
+
:mod:`the Map/Reduce module <sage.parallel.map_reduce>` for
|
|
918
|
+
details and examples.
|
|
919
|
+
"""
|
|
920
|
+
def __init__(self,
|
|
921
|
+
roots=None,
|
|
922
|
+
children=None,
|
|
923
|
+
post_process=None,
|
|
924
|
+
map_function=None,
|
|
925
|
+
reduce_function=None,
|
|
926
|
+
reduce_init=None,
|
|
927
|
+
forest=None):
|
|
928
|
+
r"""
|
|
929
|
+
TESTS::
|
|
930
|
+
|
|
931
|
+
sage: from sage.parallel.map_reduce import RESetMapReduce
|
|
932
|
+
sage: R = RESetMapReduce([[]], lambda: [[]])
|
|
933
|
+
sage: R
|
|
934
|
+
<sage.parallel.map_reduce.RESetMapReduce object at 0x...>
|
|
935
|
+
|
|
936
|
+
To silence the coverage checker::
|
|
937
|
+
|
|
938
|
+
sage: TestSuite(R).run(skip=['_test_pickling'])
|
|
939
|
+
"""
|
|
940
|
+
if forest is not None:
|
|
941
|
+
if not all(x is None for x in (roots, children, post_process)):
|
|
942
|
+
raise ValueError("forest arg is incompatible with roots, children and post_process")
|
|
943
|
+
self._forest = forest
|
|
944
|
+
self._roots = forest._roots
|
|
945
|
+
self.children = forest.children
|
|
946
|
+
if hasattr(forest, 'post_process'):
|
|
947
|
+
self.post_process = forest.post_process
|
|
948
|
+
else:
|
|
949
|
+
if roots is not None:
|
|
950
|
+
self._roots = roots
|
|
951
|
+
if children is not None:
|
|
952
|
+
self.children = children
|
|
953
|
+
if post_process is not None:
|
|
954
|
+
self.post_process = post_process
|
|
955
|
+
if map_function is not None:
|
|
956
|
+
self.map_function = map_function
|
|
957
|
+
if reduce_function is not None:
|
|
958
|
+
self.reduce_function = reduce_function
|
|
959
|
+
if reduce_init is not None:
|
|
960
|
+
self._reduce_init = reduce_init
|
|
961
|
+
self._profile = None
|
|
962
|
+
|
|
963
|
+
@lazy_attribute
|
|
964
|
+
def _forest(self):
|
|
965
|
+
r"""
|
|
966
|
+
Return the forest underlying the map-reduce computation.
|
|
967
|
+
|
|
968
|
+
EXAMPLES::
|
|
969
|
+
|
|
970
|
+
sage: from sage.parallel.map_reduce import RESetMPExample
|
|
971
|
+
sage: EX = RESetMPExample()
|
|
972
|
+
sage: f = EX._forest; f
|
|
973
|
+
An enumerated set with a forest structure
|
|
974
|
+
sage: f.an_element()
|
|
975
|
+
[]
|
|
976
|
+
"""
|
|
977
|
+
return RecursivelyEnumeratedSet(
|
|
978
|
+
self.roots(),
|
|
979
|
+
self.children,
|
|
980
|
+
post_process=self.post_process,
|
|
981
|
+
structure='forest',
|
|
982
|
+
enumeration='depth')
|
|
983
|
+
|
|
984
|
+
def roots(self):
|
|
985
|
+
r"""
|
|
986
|
+
Return the roots of ``self``.
|
|
987
|
+
|
|
988
|
+
OUTPUT: an iterable of nodes
|
|
989
|
+
|
|
990
|
+
.. NOTE:: This should be overloaded in applications.
|
|
991
|
+
|
|
992
|
+
EXAMPLES::
|
|
993
|
+
|
|
994
|
+
sage: from sage.parallel.map_reduce import RESetMapReduce
|
|
995
|
+
sage: S = RESetMapReduce(42)
|
|
996
|
+
sage: S.roots()
|
|
997
|
+
42
|
|
998
|
+
"""
|
|
999
|
+
return self._roots
|
|
1000
|
+
|
|
1001
|
+
def map_function(self, o):
|
|
1002
|
+
r"""
|
|
1003
|
+
Return the function mapped by ``self``.
|
|
1004
|
+
|
|
1005
|
+
INPUT:
|
|
1006
|
+
|
|
1007
|
+
- ``o`` -- a node
|
|
1008
|
+
|
|
1009
|
+
OUTPUT: by default ``1``
|
|
1010
|
+
|
|
1011
|
+
.. NOTE:: This should be overloaded in applications.
|
|
1012
|
+
|
|
1013
|
+
EXAMPLES::
|
|
1014
|
+
|
|
1015
|
+
sage: from sage.parallel.map_reduce import RESetMapReduce
|
|
1016
|
+
sage: S = RESetMapReduce()
|
|
1017
|
+
sage: S.map_function(7)
|
|
1018
|
+
1
|
|
1019
|
+
sage: S = RESetMapReduce(map_function = lambda x: 3*x + 5)
|
|
1020
|
+
sage: S.map_function(7)
|
|
1021
|
+
26
|
|
1022
|
+
"""
|
|
1023
|
+
return 1
|
|
1024
|
+
|
|
1025
|
+
def reduce_function(self, a, b):
|
|
1026
|
+
r"""
|
|
1027
|
+
Return the reducer function for ``self``.
|
|
1028
|
+
|
|
1029
|
+
INPUT:
|
|
1030
|
+
|
|
1031
|
+
- ``a``, ``b`` -- two values to be reduced
|
|
1032
|
+
|
|
1033
|
+
OUTPUT: by default the sum of ``a`` and ``b``
|
|
1034
|
+
|
|
1035
|
+
.. NOTE:: This should be overloaded in applications.
|
|
1036
|
+
|
|
1037
|
+
EXAMPLES::
|
|
1038
|
+
|
|
1039
|
+
sage: from sage.parallel.map_reduce import RESetMapReduce
|
|
1040
|
+
sage: S = RESetMapReduce()
|
|
1041
|
+
sage: S.reduce_function(4, 3)
|
|
1042
|
+
7
|
|
1043
|
+
sage: S = RESetMapReduce(reduce_function=lambda x,y: x*y)
|
|
1044
|
+
sage: S.reduce_function(4, 3)
|
|
1045
|
+
12
|
|
1046
|
+
"""
|
|
1047
|
+
return a + b
|
|
1048
|
+
|
|
1049
|
+
def post_process(self, a):
|
|
1050
|
+
r"""
|
|
1051
|
+
Return the image of ``a`` under the post-processing function for ``self``.
|
|
1052
|
+
|
|
1053
|
+
INPUT:
|
|
1054
|
+
|
|
1055
|
+
- ``a`` -- a node
|
|
1056
|
+
|
|
1057
|
+
With the default post-processing function, which is the identity function,
|
|
1058
|
+
this returns ``a`` itself.
|
|
1059
|
+
|
|
1060
|
+
.. NOTE:: This should be overloaded in applications.
|
|
1061
|
+
|
|
1062
|
+
EXAMPLES::
|
|
1063
|
+
|
|
1064
|
+
sage: from sage.parallel.map_reduce import RESetMapReduce
|
|
1065
|
+
sage: S = RESetMapReduce()
|
|
1066
|
+
sage: S.post_process(4)
|
|
1067
|
+
4
|
|
1068
|
+
sage: S = RESetMapReduce(post_process=lambda x: x*x)
|
|
1069
|
+
sage: S.post_process(4)
|
|
1070
|
+
16
|
|
1071
|
+
"""
|
|
1072
|
+
return a
|
|
1073
|
+
|
|
1074
|
+
_reduce_init = 0
|
|
1075
|
+
|
|
1076
|
+
def reduce_init(self):
|
|
1077
|
+
r"""
|
|
1078
|
+
Return the initial element for a reduction.
|
|
1079
|
+
|
|
1080
|
+
.. NOTE:: This should be overloaded in applications.
|
|
1081
|
+
|
|
1082
|
+
TESTS::
|
|
1083
|
+
|
|
1084
|
+
sage: from sage.parallel.map_reduce import RESetMapReduce
|
|
1085
|
+
sage: S = RESetMapReduce()
|
|
1086
|
+
sage: S.reduce_init()
|
|
1087
|
+
0
|
|
1088
|
+
sage: S = RESetMapReduce(reduce_init = 2)
|
|
1089
|
+
sage: S.reduce_init()
|
|
1090
|
+
2
|
|
1091
|
+
"""
|
|
1092
|
+
return copy.copy(self._reduce_init)
|
|
1093
|
+
|
|
1094
|
+
def setup_workers(self, max_proc=None, reduce_locally=True):
|
|
1095
|
+
r"""
|
|
1096
|
+
Setup the communication channels.
|
|
1097
|
+
|
|
1098
|
+
INPUT:
|
|
1099
|
+
|
|
1100
|
+
- ``max_proc`` -- integer; an upper bound on the number of
|
|
1101
|
+
worker processes
|
|
1102
|
+
|
|
1103
|
+
- ``reduce_locally`` -- whether the workers should reduce locally
|
|
1104
|
+
their work or sends results to the master as soon as possible.
|
|
1105
|
+
See :class:`RESetMapReduceWorker` for details.
|
|
1106
|
+
|
|
1107
|
+
TESTS::
|
|
1108
|
+
|
|
1109
|
+
sage: from sage.parallel.map_reduce import RESetMapReduce
|
|
1110
|
+
sage: S = RESetMapReduce()
|
|
1111
|
+
sage: S.setup_workers(2)
|
|
1112
|
+
sage: S._results
|
|
1113
|
+
<multiprocessing.queues.Queue object at 0x...>
|
|
1114
|
+
sage: len(S._workers)
|
|
1115
|
+
2
|
|
1116
|
+
"""
|
|
1117
|
+
self._nprocess = proc_number(max_proc)
|
|
1118
|
+
self._results = mp.Queue()
|
|
1119
|
+
self._active_tasks = ActiveTaskCounter(self._nprocess)
|
|
1120
|
+
self._done = mp.Lock()
|
|
1121
|
+
# We use lock=False here, as a compromise, to avoid deadlocking when a
|
|
1122
|
+
# subprocess holding a lock is terminated. (:issue:`33236`)
|
|
1123
|
+
self._aborted = mp.Value(ctypes.c_bool, False, lock=False)
|
|
1124
|
+
sys.stdout.flush()
|
|
1125
|
+
sys.stderr.flush()
|
|
1126
|
+
self._workers = [RESetMapReduceWorker(self, i, reduce_locally)
|
|
1127
|
+
for i in range(self._nprocess)]
|
|
1128
|
+
|
|
1129
|
+
def start_workers(self):
|
|
1130
|
+
r"""
|
|
1131
|
+
Launch the workers.
|
|
1132
|
+
|
|
1133
|
+
The workers should have been created using :meth:`setup_workers`.
|
|
1134
|
+
|
|
1135
|
+
TESTS::
|
|
1136
|
+
|
|
1137
|
+
sage: # long time
|
|
1138
|
+
sage: from sage.parallel.map_reduce import RESetMapReduce
|
|
1139
|
+
sage: def children(x):
|
|
1140
|
+
....: print(f"Starting: {x}", flush=True)
|
|
1141
|
+
....: return []
|
|
1142
|
+
sage: S = RESetMapReduce(roots=[1, 2], children=children)
|
|
1143
|
+
sage: S.setup_workers(2)
|
|
1144
|
+
sage: S.start_workers(); sleep(float(5))
|
|
1145
|
+
Starting: ...
|
|
1146
|
+
Starting: ...
|
|
1147
|
+
sage: S.finish()
|
|
1148
|
+
"""
|
|
1149
|
+
if self._nprocess == 0:
|
|
1150
|
+
raise ValueError("No process connected")
|
|
1151
|
+
logger.info("Starting work with %s processes", self._nprocess)
|
|
1152
|
+
logger.debug("Distributing tasks")
|
|
1153
|
+
for i, task in enumerate(self.roots()):
|
|
1154
|
+
self._workers[i % len(self._workers)]._todo.append(task)
|
|
1155
|
+
logger.debug("Starting processes")
|
|
1156
|
+
sys.stdout.flush()
|
|
1157
|
+
sys.stderr.flush()
|
|
1158
|
+
for w in self._workers:
|
|
1159
|
+
w.start()
|
|
1160
|
+
|
|
1161
|
+
def get_results(self, timeout=None):
|
|
1162
|
+
r"""
|
|
1163
|
+
Get the results from the queue.
|
|
1164
|
+
|
|
1165
|
+
OUTPUT:
|
|
1166
|
+
|
|
1167
|
+
The reduction of the results of all the workers, that is, the result of
|
|
1168
|
+
the map/reduce computation.
|
|
1169
|
+
|
|
1170
|
+
EXAMPLES::
|
|
1171
|
+
|
|
1172
|
+
sage: from sage.parallel.map_reduce import RESetMapReduce
|
|
1173
|
+
sage: S = RESetMapReduce()
|
|
1174
|
+
sage: S.setup_workers(2)
|
|
1175
|
+
sage: for v in [1, 2, None, 3, None]: S._results.put(v)
|
|
1176
|
+
sage: S.get_results()
|
|
1177
|
+
6
|
|
1178
|
+
|
|
1179
|
+
Cleanup::
|
|
1180
|
+
|
|
1181
|
+
sage: del S._results, S._active_tasks, S._done, S._workers
|
|
1182
|
+
"""
|
|
1183
|
+
res = self.reduce_init()
|
|
1184
|
+
active_proc = self._nprocess
|
|
1185
|
+
while active_proc > 0:
|
|
1186
|
+
try:
|
|
1187
|
+
logger.debug('Waiting on results; active_proc: {}, '
|
|
1188
|
+
'timeout: {}, aborted: {}'.format(
|
|
1189
|
+
active_proc, timeout, self._aborted.value))
|
|
1190
|
+
newres = self._results.get(timeout=timeout)
|
|
1191
|
+
except queue.Empty:
|
|
1192
|
+
logger.debug('Timed out waiting for results; aborting')
|
|
1193
|
+
# If we timed out here then the abort timer should have
|
|
1194
|
+
# already fired, but just in case it didn't (or is in
|
|
1195
|
+
# progress) wait for it to finish
|
|
1196
|
+
self._timer.join()
|
|
1197
|
+
return
|
|
1198
|
+
|
|
1199
|
+
if newres is not None:
|
|
1200
|
+
logger.debug("Got one result")
|
|
1201
|
+
res = self.reduce_function(res, newres)
|
|
1202
|
+
else:
|
|
1203
|
+
active_proc -= 1
|
|
1204
|
+
|
|
1205
|
+
return res
|
|
1206
|
+
|
|
1207
|
+
def finish(self):
|
|
1208
|
+
r"""
|
|
1209
|
+
Destroy the workers and all the communication objects.
|
|
1210
|
+
|
|
1211
|
+
Communication statistics are gathered before destroying the workers.
|
|
1212
|
+
|
|
1213
|
+
TESTS::
|
|
1214
|
+
|
|
1215
|
+
sage: from sage.parallel.map_reduce import RESetMPExample
|
|
1216
|
+
sage: S = RESetMPExample(maxl=5)
|
|
1217
|
+
sage: S.setup_workers(2) # indirect doctest
|
|
1218
|
+
sage: S._workers[0]._todo.append([])
|
|
1219
|
+
sage: for w in S._workers: w.start()
|
|
1220
|
+
sage: _ = S.get_results()
|
|
1221
|
+
sage: S._shutdown()
|
|
1222
|
+
sage: S.print_communication_statistics()
|
|
1223
|
+
Traceback (most recent call last):
|
|
1224
|
+
...
|
|
1225
|
+
AttributeError: 'RESetMPExample' object has no attribute '_stats'...
|
|
1226
|
+
|
|
1227
|
+
sage: S.finish()
|
|
1228
|
+
|
|
1229
|
+
sage: S.print_communication_statistics()
|
|
1230
|
+
#proc: ...
|
|
1231
|
+
...
|
|
1232
|
+
|
|
1233
|
+
sage: _ = S.run() # cleanup
|
|
1234
|
+
|
|
1235
|
+
.. SEEALSO:: :meth:`print_communication_statistics`
|
|
1236
|
+
"""
|
|
1237
|
+
if not self._aborted.value:
|
|
1238
|
+
logger.debug("Joining worker processes...")
|
|
1239
|
+
for worker in self._workers:
|
|
1240
|
+
logger.debug(f"Joining {worker.name}")
|
|
1241
|
+
worker.join()
|
|
1242
|
+
logger.debug("Joining done")
|
|
1243
|
+
else:
|
|
1244
|
+
logger.debug("Killing worker processes...")
|
|
1245
|
+
for worker in self._workers:
|
|
1246
|
+
logger.debug(f"Terminating {worker.name}")
|
|
1247
|
+
worker.terminate()
|
|
1248
|
+
logger.debug("Killing done")
|
|
1249
|
+
|
|
1250
|
+
del self._results, self._active_tasks, self._done
|
|
1251
|
+
self._get_stats()
|
|
1252
|
+
del self._workers
|
|
1253
|
+
|
|
1254
|
+
def abort(self):
|
|
1255
|
+
r"""
|
|
1256
|
+
Abort the current parallel computation.
|
|
1257
|
+
|
|
1258
|
+
EXAMPLES::
|
|
1259
|
+
|
|
1260
|
+
sage: from sage.parallel.map_reduce import RESetParallelIterator
|
|
1261
|
+
sage: S = RESetParallelIterator([[]],
|
|
1262
|
+
....: lambda l: [l + [0], l + [1]] if len(l) < 17 else [])
|
|
1263
|
+
sage: it = iter(S)
|
|
1264
|
+
sage: next(it) # random
|
|
1265
|
+
[]
|
|
1266
|
+
sage: S.abort()
|
|
1267
|
+
sage: hasattr(S, 'work_queue')
|
|
1268
|
+
False
|
|
1269
|
+
|
|
1270
|
+
Cleanup::
|
|
1271
|
+
|
|
1272
|
+
sage: S.finish()
|
|
1273
|
+
"""
|
|
1274
|
+
logger.info("Abort called")
|
|
1275
|
+
self._aborted.value = True
|
|
1276
|
+
self._active_tasks.abort()
|
|
1277
|
+
self._shutdown()
|
|
1278
|
+
|
|
1279
|
+
def _shutdown(self):
|
|
1280
|
+
r"""
|
|
1281
|
+
Shutdown the workers.
|
|
1282
|
+
|
|
1283
|
+
Sends a poison pill to all workers and their thief thread.
|
|
1284
|
+
|
|
1285
|
+
EXAMPLES::
|
|
1286
|
+
|
|
1287
|
+
sage: from sage.parallel.map_reduce import RESetParallelIterator
|
|
1288
|
+
sage: S = RESetParallelIterator( [[]],
|
|
1289
|
+
....: lambda l: [l+[0], l+[1]] if len(l) < 20 else [])
|
|
1290
|
+
sage: S.setup_workers(2)
|
|
1291
|
+
sage: for w in S._workers: w.start()
|
|
1292
|
+
sage: S._shutdown()
|
|
1293
|
+
|
|
1294
|
+
Cleanup::
|
|
1295
|
+
|
|
1296
|
+
sage: S.finish()
|
|
1297
|
+
"""
|
|
1298
|
+
if self._done.acquire(False):
|
|
1299
|
+
logger.debug("***************** FINISHED ******************")
|
|
1300
|
+
logger.debug("Sending poison pills")
|
|
1301
|
+
for worker in self._workers:
|
|
1302
|
+
worker._request.put(AbortError)
|
|
1303
|
+
for worker in self._workers:
|
|
1304
|
+
worker._write_task.send(AbortError)
|
|
1305
|
+
|
|
1306
|
+
def _signal_task_start(self):
|
|
1307
|
+
r"""
|
|
1308
|
+
Signal a starting task.
|
|
1309
|
+
|
|
1310
|
+
Used by the worker to signal that a new task is starting. As soon as
|
|
1311
|
+
there are no more active task, the work is done, in which case an
|
|
1312
|
+
:exc:`AbortError` is raised.
|
|
1313
|
+
|
|
1314
|
+
EXAMPLES::
|
|
1315
|
+
|
|
1316
|
+
sage: from sage.parallel.map_reduce import RESetParallelIterator
|
|
1317
|
+
sage: S = RESetParallelIterator( [[]],
|
|
1318
|
+
....: lambda l: [l+[0], l+[1]] if len(l) < 20 else [])
|
|
1319
|
+
sage: S.setup_workers(2)
|
|
1320
|
+
sage: S._active_tasks
|
|
1321
|
+
ActiveTaskCounter(value=2)
|
|
1322
|
+
|
|
1323
|
+
sage: S._signal_task_start()
|
|
1324
|
+
sage: S._active_tasks
|
|
1325
|
+
ActiveTaskCounter(value=3)
|
|
1326
|
+
|
|
1327
|
+
Signaling one time too many raises an :exc:`AbortError`::
|
|
1328
|
+
|
|
1329
|
+
sage: S._signal_task_done()
|
|
1330
|
+
sage: S._signal_task_done()
|
|
1331
|
+
sage: S._signal_task_done()
|
|
1332
|
+
Traceback (most recent call last):
|
|
1333
|
+
...
|
|
1334
|
+
AbortError
|
|
1335
|
+
"""
|
|
1336
|
+
if self._active_tasks.task_start() == 0:
|
|
1337
|
+
raise AbortError
|
|
1338
|
+
|
|
1339
|
+
def _signal_task_done(self):
|
|
1340
|
+
r"""
|
|
1341
|
+
Signal a task is done.
|
|
1342
|
+
|
|
1343
|
+
Used by the worker to signal that a task is done. As soon as
|
|
1344
|
+
there are no more active task, the work is done, in which case an
|
|
1345
|
+
:exc:`AbortError` is raised.
|
|
1346
|
+
|
|
1347
|
+
EXAMPLES::
|
|
1348
|
+
|
|
1349
|
+
sage: from sage.parallel.map_reduce import RESetParallelIterator
|
|
1350
|
+
sage: S = RESetParallelIterator(
|
|
1351
|
+
....: [[]],
|
|
1352
|
+
....: lambda l: [l + [0], l + [1]] if len(l) < 20 else [])
|
|
1353
|
+
sage: S.setup_workers(2)
|
|
1354
|
+
sage: S._active_tasks
|
|
1355
|
+
ActiveTaskCounter(value=2)
|
|
1356
|
+
|
|
1357
|
+
sage: S._signal_task_done()
|
|
1358
|
+
sage: S._active_tasks
|
|
1359
|
+
ActiveTaskCounter(value=1)
|
|
1360
|
+
|
|
1361
|
+
sage: S._signal_task_done()
|
|
1362
|
+
Traceback (most recent call last):
|
|
1363
|
+
...
|
|
1364
|
+
AbortError
|
|
1365
|
+
|
|
1366
|
+
Cleanup::
|
|
1367
|
+
|
|
1368
|
+
sage: del S._results, S._active_tasks, S._done, S._workers
|
|
1369
|
+
"""
|
|
1370
|
+
# We test if the semaphore counting the number of active tasks is
|
|
1371
|
+
# becoming negative. This should not happen in normal
|
|
1372
|
+
# computations. However, in case of abort, we artificially put the
|
|
1373
|
+
# semaphore to 0 to stop the computation so that it is needed.
|
|
1374
|
+
if self._active_tasks.task_done() <= 0:
|
|
1375
|
+
logger.debug("raising AbortError")
|
|
1376
|
+
self._shutdown()
|
|
1377
|
+
raise AbortError
|
|
1378
|
+
|
|
1379
|
+
def random_worker(self):
|
|
1380
|
+
r"""
|
|
1381
|
+
Return a random worker.
|
|
1382
|
+
|
|
1383
|
+
OUTPUT: a worker for ``self`` chosen at random
|
|
1384
|
+
|
|
1385
|
+
EXAMPLES::
|
|
1386
|
+
|
|
1387
|
+
sage: from sage.parallel.map_reduce import RESetMPExample, RESetMapReduceWorker
|
|
1388
|
+
sage: from threading import Thread
|
|
1389
|
+
sage: EX = RESetMPExample(maxl=6)
|
|
1390
|
+
sage: EX.setup_workers(2)
|
|
1391
|
+
sage: EX.random_worker()
|
|
1392
|
+
<RESetMapReduceWorker...RESetMapReduceWorker-... initial...>
|
|
1393
|
+
sage: EX.random_worker() in EX._workers
|
|
1394
|
+
True
|
|
1395
|
+
|
|
1396
|
+
Cleanup::
|
|
1397
|
+
|
|
1398
|
+
sage: del EX._results, EX._active_tasks, EX._done, EX._workers
|
|
1399
|
+
"""
|
|
1400
|
+
victim = random.randint(0, len(self._workers) - 1)
|
|
1401
|
+
return self._workers[victim]
|
|
1402
|
+
|
|
1403
|
+
def run(self,
|
|
1404
|
+
max_proc=None,
|
|
1405
|
+
reduce_locally=True,
|
|
1406
|
+
timeout=None,
|
|
1407
|
+
profile=None):
|
|
1408
|
+
r"""
|
|
1409
|
+
Run the computations.
|
|
1410
|
+
|
|
1411
|
+
INPUT:
|
|
1412
|
+
|
|
1413
|
+
- ``max_proc`` -- (integer, default: ``None``) if given, the
|
|
1414
|
+
maximum number of worker processors to use. The actual number
|
|
1415
|
+
is also bounded by the value of the environment variable
|
|
1416
|
+
``SAGE_NUM_THREADS`` (the number of cores by default).
|
|
1417
|
+
- ``reduce_locally`` -- see :class:`RESetMapReduceWorker` (default: ``True``)
|
|
1418
|
+
- ``timeout`` -- a timeout on the computation (default: ``None``)
|
|
1419
|
+
- ``profile`` -- directory/filename prefix for profiling, or ``None``
|
|
1420
|
+
for no profiling (default: ``None``)
|
|
1421
|
+
|
|
1422
|
+
OUTPUT:
|
|
1423
|
+
|
|
1424
|
+
The result of the map/reduce computation or an exception
|
|
1425
|
+
:exc:`AbortError` if the computation was interrupted or timeout.
|
|
1426
|
+
|
|
1427
|
+
EXAMPLES::
|
|
1428
|
+
|
|
1429
|
+
sage: from sage.parallel.map_reduce import RESetMPExample
|
|
1430
|
+
sage: EX = RESetMPExample(maxl = 8)
|
|
1431
|
+
sage: EX.run()
|
|
1432
|
+
40320*x^8 + 5040*x^7 + 720*x^6 + 120*x^5 + 24*x^4 + 6*x^3 + 2*x^2 + x + 1
|
|
1433
|
+
|
|
1434
|
+
Here is an example or how to deal with timeout::
|
|
1435
|
+
|
|
1436
|
+
sage: from sage.parallel.map_reduce import AbortError
|
|
1437
|
+
sage: EX = RESetMPExample(maxl = 100)
|
|
1438
|
+
sage: try:
|
|
1439
|
+
....: res = EX.run(timeout=float(0.01))
|
|
1440
|
+
....: except AbortError:
|
|
1441
|
+
....: print("Computation timeout")
|
|
1442
|
+
....: else:
|
|
1443
|
+
....: print("Computation normally finished")
|
|
1444
|
+
....: res
|
|
1445
|
+
Computation timeout
|
|
1446
|
+
|
|
1447
|
+
The following should not timeout even on a very slow machine::
|
|
1448
|
+
|
|
1449
|
+
sage: from sage.parallel.map_reduce import AbortError
|
|
1450
|
+
sage: EX = RESetMPExample(maxl = 8)
|
|
1451
|
+
sage: try:
|
|
1452
|
+
....: res = EX.run(timeout=60)
|
|
1453
|
+
....: except AbortError:
|
|
1454
|
+
....: print("Computation Timeout")
|
|
1455
|
+
....: else:
|
|
1456
|
+
....: print("Computation normally finished")
|
|
1457
|
+
....: res
|
|
1458
|
+
Computation normally finished
|
|
1459
|
+
40320*x^8 + 5040*x^7 + 720*x^6 + 120*x^5 + 24*x^4 + 6*x^3 + 2*x^2 + x + 1
|
|
1460
|
+
"""
|
|
1461
|
+
self._profile = profile
|
|
1462
|
+
self.setup_workers(max_proc, reduce_locally)
|
|
1463
|
+
self.start_workers()
|
|
1464
|
+
if timeout is not None:
|
|
1465
|
+
from threading import Timer
|
|
1466
|
+
self._timer = Timer(timeout, self.abort)
|
|
1467
|
+
self._timer.start()
|
|
1468
|
+
self.result = self.get_results(timeout=timeout)
|
|
1469
|
+
if timeout is not None:
|
|
1470
|
+
self._timer.cancel()
|
|
1471
|
+
logger.info("Returning")
|
|
1472
|
+
self.finish()
|
|
1473
|
+
if self._aborted.value:
|
|
1474
|
+
raise AbortError
|
|
1475
|
+
else:
|
|
1476
|
+
return self.result
|
|
1477
|
+
|
|
1478
|
+
def _get_stats(self):
|
|
1479
|
+
r"""
|
|
1480
|
+
Gather the communication statistics at the end of a run.
|
|
1481
|
+
|
|
1482
|
+
EXAMPLES::
|
|
1483
|
+
|
|
1484
|
+
sage: from sage.parallel.map_reduce import RESetMPExample
|
|
1485
|
+
sage: S = RESetMPExample(maxl=6)
|
|
1486
|
+
sage: S.run() # indirect doctest
|
|
1487
|
+
720*x^6 + 120*x^5 + 24*x^4 + 6*x^3 + 2*x^2 + x + 1
|
|
1488
|
+
"""
|
|
1489
|
+
res = [tuple(self._workers[i]._stats) for i in range(self._nprocess)]
|
|
1490
|
+
self._stats = res
|
|
1491
|
+
|
|
1492
|
+
def print_communication_statistics(self, blocksize=16):
|
|
1493
|
+
r"""
|
|
1494
|
+
Print the communication statistics in a nice way.
|
|
1495
|
+
|
|
1496
|
+
EXAMPLES::
|
|
1497
|
+
|
|
1498
|
+
sage: from sage.parallel.map_reduce import RESetMPExample
|
|
1499
|
+
sage: S = RESetMPExample(maxl=6)
|
|
1500
|
+
sage: S.run()
|
|
1501
|
+
720*x^6 + 120*x^5 + 24*x^4 + 6*x^3 + 2*x^2 + x + 1
|
|
1502
|
+
|
|
1503
|
+
sage: S.print_communication_statistics() # random
|
|
1504
|
+
#proc: 0 1 2 3 4 5 6 7
|
|
1505
|
+
reqs sent: 5 2 3 11 21 19 1 0
|
|
1506
|
+
reqs rcvs: 10 10 9 5 1 11 9 2
|
|
1507
|
+
- thefs: 1 0 0 0 0 0 0 0
|
|
1508
|
+
+ thefs: 0 0 1 0 0 0 0 0
|
|
1509
|
+
"""
|
|
1510
|
+
res = [""] # classic trick to have a local variable shared with the
|
|
1511
|
+
# local function (see e.g:
|
|
1512
|
+
# https://stackoverflow.com/questions/2609518/python-nested-function-scopes).
|
|
1513
|
+
|
|
1514
|
+
def pstat(name, start, end, istat):
|
|
1515
|
+
res[0] += ("\n" + name + " ".join(
|
|
1516
|
+
"%4i" % (self._stats[i][istat]) for i in range(start, end)))
|
|
1517
|
+
for start in range(0, self._nprocess, blocksize):
|
|
1518
|
+
end = min(start + blocksize, self._nprocess)
|
|
1519
|
+
res[0] = ("#proc: " +
|
|
1520
|
+
" ".join("%4i" % (i) for i in range(start, end)))
|
|
1521
|
+
pstat("reqs sent: ", start, end, 0)
|
|
1522
|
+
pstat("reqs rcvs: ", start, end, 1)
|
|
1523
|
+
pstat("- thefs: ", start, end, 2)
|
|
1524
|
+
pstat("+ thefs: ", start, end, 3)
|
|
1525
|
+
print(res[0])
|
|
1526
|
+
|
|
1527
|
+
def run_serial(self):
|
|
1528
|
+
r"""
|
|
1529
|
+
Run the computation serially (mostly for tests).
|
|
1530
|
+
|
|
1531
|
+
EXAMPLES::
|
|
1532
|
+
|
|
1533
|
+
sage: from sage.parallel.map_reduce import RESetMPExample
|
|
1534
|
+
sage: EX = RESetMPExample(maxl = 4)
|
|
1535
|
+
sage: EX.run_serial()
|
|
1536
|
+
24*x^4 + 6*x^3 + 2*x^2 + x + 1
|
|
1537
|
+
"""
|
|
1538
|
+
import functools
|
|
1539
|
+
return functools.reduce(self.reduce_function,
|
|
1540
|
+
(self.map_function(x) for x in self._forest),
|
|
1541
|
+
self.reduce_init())
|
|
1542
|
+
|
|
1543
|
+
|
|
1544
|
+
class RESetMapReduceWorker(mp.Process):
|
|
1545
|
+
"""
|
|
1546
|
+
Worker for generate-map-reduce.
|
|
1547
|
+
|
|
1548
|
+
This shouldn't be called directly, but instead created by
|
|
1549
|
+
:meth:`RESetMapReduce.setup_workers`.
|
|
1550
|
+
|
|
1551
|
+
INPUT:
|
|
1552
|
+
|
|
1553
|
+
- ``mapred`` -- the instance of :class:`RESetMapReduce` for which
|
|
1554
|
+
this process is working
|
|
1555
|
+
|
|
1556
|
+
- ``iproc`` -- the id of this worker
|
|
1557
|
+
|
|
1558
|
+
- ``reduce_locally`` -- when reducing the results. Three possible values
|
|
1559
|
+
are supported:
|
|
1560
|
+
|
|
1561
|
+
* ``True`` -- means the reducing work is done all locally, the result is
|
|
1562
|
+
only sent back at the end of the work. This ensure the lowest level of
|
|
1563
|
+
communication.
|
|
1564
|
+
|
|
1565
|
+
* ``False`` -- results are sent back after each finished branches, when
|
|
1566
|
+
the process is asking for more work.
|
|
1567
|
+
"""
|
|
1568
|
+
def __init__(self, mapred, iproc, reduce_locally):
|
|
1569
|
+
r"""
|
|
1570
|
+
TESTS::
|
|
1571
|
+
|
|
1572
|
+
sage: from sage.parallel.map_reduce import RESetMPExample, RESetMapReduceWorker
|
|
1573
|
+
sage: EX = RESetMPExample()
|
|
1574
|
+
sage: RESetMapReduceWorker(EX, 200, True)
|
|
1575
|
+
<RESetMapReduceWorker...RESetMapReduceWorker-... initial...>
|
|
1576
|
+
"""
|
|
1577
|
+
mp.Process.__init__(self)
|
|
1578
|
+
self._iproc = iproc
|
|
1579
|
+
self._todo = deque()
|
|
1580
|
+
self._request = mp.SimpleQueue() # Faster than Queue
|
|
1581
|
+
# currently this is not possible to have to simultaneous read or write
|
|
1582
|
+
# on the following Pipe. So there is no need to have a queue.
|
|
1583
|
+
self._read_task, self._write_task = mp.Pipe(duplex=False)
|
|
1584
|
+
self._mapred = mapred
|
|
1585
|
+
self._stats = mp.RawArray('i', 4)
|
|
1586
|
+
self._reduce_locally = reduce_locally
|
|
1587
|
+
|
|
1588
|
+
def _thief(self):
|
|
1589
|
+
r"""
|
|
1590
|
+
Return the thief thread of this worker process.
|
|
1591
|
+
"""
|
|
1592
|
+
logger.debug("Thief started")
|
|
1593
|
+
reqs = 0
|
|
1594
|
+
thefts = 0
|
|
1595
|
+
|
|
1596
|
+
try:
|
|
1597
|
+
for ireq in iter(self._request.get, AbortError):
|
|
1598
|
+
reqs += 1
|
|
1599
|
+
target = self._mapred._workers[ireq]
|
|
1600
|
+
logger.debug(f"Got a Steal request from {target.name}")
|
|
1601
|
+
self._mapred._signal_task_start()
|
|
1602
|
+
try:
|
|
1603
|
+
work = self._todo.popleft()
|
|
1604
|
+
except IndexError:
|
|
1605
|
+
target._write_task.send(None)
|
|
1606
|
+
logger.debug(f"Failed Steal {target.name}")
|
|
1607
|
+
self._mapred._signal_task_done()
|
|
1608
|
+
else:
|
|
1609
|
+
target._write_task.send(work)
|
|
1610
|
+
logger.debug(f"Successful Steal {target.name}")
|
|
1611
|
+
thefts += 1
|
|
1612
|
+
except AbortError:
|
|
1613
|
+
logger.debug("Thief aborted")
|
|
1614
|
+
else:
|
|
1615
|
+
logger.debug("Thief received poison pill")
|
|
1616
|
+
if self._mapred._aborted.value: # Computation was aborted
|
|
1617
|
+
self._todo.clear()
|
|
1618
|
+
else: # Check that there is no remaining work
|
|
1619
|
+
assert len(self._todo) == 0, "Bad stop the result may be wrong"
|
|
1620
|
+
|
|
1621
|
+
self._stats[1] = reqs
|
|
1622
|
+
self._stats[2] = thefts
|
|
1623
|
+
logger.debug("Thief Exiting")
|
|
1624
|
+
|
|
1625
|
+
def steal(self):
|
|
1626
|
+
r"""
|
|
1627
|
+
Steal some node from another worker.
|
|
1628
|
+
|
|
1629
|
+
OUTPUT: a node stolen from another worker chosen at random
|
|
1630
|
+
|
|
1631
|
+
EXAMPLES::
|
|
1632
|
+
|
|
1633
|
+
sage: from sage.parallel.map_reduce import RESetMPExample, RESetMapReduceWorker
|
|
1634
|
+
sage: from threading import Thread
|
|
1635
|
+
sage: EX = RESetMPExample(maxl=6)
|
|
1636
|
+
sage: EX.setup_workers(2)
|
|
1637
|
+
|
|
1638
|
+
sage: # known bug (Issue #27537)
|
|
1639
|
+
sage: w0, w1 = EX._workers
|
|
1640
|
+
sage: w0._todo.append(42)
|
|
1641
|
+
sage: thief0 = Thread(target = w0._thief, name='Thief')
|
|
1642
|
+
sage: thief0.start()
|
|
1643
|
+
sage: w1.steal()
|
|
1644
|
+
42
|
|
1645
|
+
sage: w0._todo
|
|
1646
|
+
deque([])
|
|
1647
|
+
"""
|
|
1648
|
+
self._mapred._signal_task_done()
|
|
1649
|
+
node = None
|
|
1650
|
+
while node is None:
|
|
1651
|
+
victim = self._mapred.random_worker()
|
|
1652
|
+
if victim is not self:
|
|
1653
|
+
logger.debug(f"Trying to steal from {victim.name}")
|
|
1654
|
+
victim._request.put(self._iproc)
|
|
1655
|
+
self._stats[0] += 1
|
|
1656
|
+
logger.debug(f"waiting for steal answer from {victim.name}")
|
|
1657
|
+
node = self._read_task.recv()
|
|
1658
|
+
# logger.debug("Request answer: %s" % (node,))
|
|
1659
|
+
if node is AbortError:
|
|
1660
|
+
raise AbortError
|
|
1661
|
+
# logger.debug("Received a stolen node: %s" % (node,))
|
|
1662
|
+
self._stats[3] += 1
|
|
1663
|
+
return node
|
|
1664
|
+
|
|
1665
|
+
def run(self):
|
|
1666
|
+
r"""
|
|
1667
|
+
The main function executed by the worker.
|
|
1668
|
+
|
|
1669
|
+
Calls :meth:`run_myself` after possibly setting up parallel profiling.
|
|
1670
|
+
|
|
1671
|
+
EXAMPLES::
|
|
1672
|
+
|
|
1673
|
+
sage: from sage.parallel.map_reduce import RESetMPExample, RESetMapReduceWorker
|
|
1674
|
+
sage: EX = RESetMPExample(maxl=6)
|
|
1675
|
+
sage: EX.setup_workers(1)
|
|
1676
|
+
|
|
1677
|
+
sage: w = EX._workers[0]
|
|
1678
|
+
sage: w._todo.append(EX.roots()[0])
|
|
1679
|
+
|
|
1680
|
+
sage: w.run()
|
|
1681
|
+
sage: sleep(int(1))
|
|
1682
|
+
sage: w._todo.append(None)
|
|
1683
|
+
|
|
1684
|
+
sage: EX.get_results()
|
|
1685
|
+
720*x^6 + 120*x^5 + 24*x^4 + 6*x^3 + 2*x^2 + x + 1
|
|
1686
|
+
|
|
1687
|
+
Cleanups::
|
|
1688
|
+
|
|
1689
|
+
sage: del EX._results, EX._active_tasks, EX._done, EX._workers
|
|
1690
|
+
"""
|
|
1691
|
+
profile = self._mapred._profile
|
|
1692
|
+
if profile is not None:
|
|
1693
|
+
import cProfile
|
|
1694
|
+
PROFILER = cProfile.Profile()
|
|
1695
|
+
PROFILER.runcall(self.run_myself)
|
|
1696
|
+
|
|
1697
|
+
output = profile + str(self._iproc)
|
|
1698
|
+
logger.warning(f"Profiling in {output} ...")
|
|
1699
|
+
PROFILER.dump_stats(output)
|
|
1700
|
+
else:
|
|
1701
|
+
self.run_myself()
|
|
1702
|
+
|
|
1703
|
+
def run_myself(self):
|
|
1704
|
+
r"""
|
|
1705
|
+
The main function executed by the worker.
|
|
1706
|
+
|
|
1707
|
+
EXAMPLES::
|
|
1708
|
+
|
|
1709
|
+
sage: from sage.parallel.map_reduce import RESetMPExample, RESetMapReduceWorker
|
|
1710
|
+
sage: EX = RESetMPExample(maxl=6)
|
|
1711
|
+
sage: EX.setup_workers(1)
|
|
1712
|
+
|
|
1713
|
+
sage: w = EX._workers[0]
|
|
1714
|
+
sage: w._todo.append(EX.roots()[0])
|
|
1715
|
+
sage: w.run_myself()
|
|
1716
|
+
|
|
1717
|
+
sage: sleep(int(1))
|
|
1718
|
+
sage: w._todo.append(None)
|
|
1719
|
+
|
|
1720
|
+
sage: EX.get_results()
|
|
1721
|
+
720*x^6 + 120*x^5 + 24*x^4 + 6*x^3 + 2*x^2 + x + 1
|
|
1722
|
+
|
|
1723
|
+
Cleanups::
|
|
1724
|
+
|
|
1725
|
+
sage: del EX._results, EX._active_tasks, EX._done, EX._workers
|
|
1726
|
+
"""
|
|
1727
|
+
logger.debug("Started")
|
|
1728
|
+
mapred = self._mapred
|
|
1729
|
+
reduce_init = mapred.reduce_init
|
|
1730
|
+
results = mapred._results
|
|
1731
|
+
|
|
1732
|
+
self._stats[0] = 0
|
|
1733
|
+
self._stats[3] = 0
|
|
1734
|
+
logger.debug("Launching thief")
|
|
1735
|
+
self._thief = Thread(target=self._thief, name='Thief')
|
|
1736
|
+
self._thief.start()
|
|
1737
|
+
self._res = reduce_init()
|
|
1738
|
+
|
|
1739
|
+
try:
|
|
1740
|
+
while True:
|
|
1741
|
+
try:
|
|
1742
|
+
node = self._todo.pop()
|
|
1743
|
+
except IndexError:
|
|
1744
|
+
node = self.steal()
|
|
1745
|
+
self.walk_branch_locally(node)
|
|
1746
|
+
if not self._reduce_locally:
|
|
1747
|
+
self.send_partial_result()
|
|
1748
|
+
except AbortError:
|
|
1749
|
+
logger.debug("Worker Done !")
|
|
1750
|
+
results.put(self._res)
|
|
1751
|
+
results.put(None)
|
|
1752
|
+
self._thief.join()
|
|
1753
|
+
del self._request
|
|
1754
|
+
self._read_task.close()
|
|
1755
|
+
self._write_task.close()
|
|
1756
|
+
del self._read_task, self._write_task
|
|
1757
|
+
del self._mapred
|
|
1758
|
+
del self._stats
|
|
1759
|
+
logger.debug("Exiting")
|
|
1760
|
+
|
|
1761
|
+
def send_partial_result(self):
|
|
1762
|
+
r"""
|
|
1763
|
+
Send results to the MapReduce process.
|
|
1764
|
+
|
|
1765
|
+
Send the result stored in ``self._res`` to the master and reinitialize it to
|
|
1766
|
+
``master.reduce_init``.
|
|
1767
|
+
|
|
1768
|
+
EXAMPLES::
|
|
1769
|
+
|
|
1770
|
+
sage: from sage.parallel.map_reduce import RESetMPExample, RESetMapReduceWorker
|
|
1771
|
+
sage: EX = RESetMPExample(maxl=4)
|
|
1772
|
+
sage: EX.setup_workers(1)
|
|
1773
|
+
sage: w = EX._workers[0]
|
|
1774
|
+
sage: w._res = 4
|
|
1775
|
+
sage: w.send_partial_result()
|
|
1776
|
+
sage: w._res
|
|
1777
|
+
0
|
|
1778
|
+
sage: EX._results.get()
|
|
1779
|
+
4
|
|
1780
|
+
"""
|
|
1781
|
+
self._mapred._results.put(self._res)
|
|
1782
|
+
self._res = self._mapred.reduce_init()
|
|
1783
|
+
|
|
1784
|
+
def walk_branch_locally(self, node):
|
|
1785
|
+
r"""
|
|
1786
|
+
Work locally.
|
|
1787
|
+
|
|
1788
|
+
Performs the map/reduce computation on the subtrees rooted at ``node``.
|
|
1789
|
+
|
|
1790
|
+
INPUT:
|
|
1791
|
+
|
|
1792
|
+
- ``node`` -- the root of the subtree explored
|
|
1793
|
+
|
|
1794
|
+
OUTPUT: nothing, the result are stored in ``self._res``
|
|
1795
|
+
|
|
1796
|
+
This is where the actual work is performed.
|
|
1797
|
+
|
|
1798
|
+
EXAMPLES::
|
|
1799
|
+
|
|
1800
|
+
sage: from sage.parallel.map_reduce import RESetMPExample, RESetMapReduceWorker
|
|
1801
|
+
sage: EX = RESetMPExample(maxl=4)
|
|
1802
|
+
sage: w = RESetMapReduceWorker(EX, 0, True)
|
|
1803
|
+
sage: def sync(): pass
|
|
1804
|
+
sage: w.synchronize = sync
|
|
1805
|
+
sage: w._res = 0
|
|
1806
|
+
|
|
1807
|
+
sage: w.walk_branch_locally([])
|
|
1808
|
+
sage: w._res
|
|
1809
|
+
x^4 + x^3 + x^2 + x + 1
|
|
1810
|
+
|
|
1811
|
+
sage: w.walk_branch_locally(w._todo.pop())
|
|
1812
|
+
sage: w._res
|
|
1813
|
+
2*x^4 + x^3 + x^2 + x + 1
|
|
1814
|
+
|
|
1815
|
+
sage: while True: w.walk_branch_locally(w._todo.pop())
|
|
1816
|
+
Traceback (most recent call last):
|
|
1817
|
+
...
|
|
1818
|
+
IndexError: pop from an empty deque
|
|
1819
|
+
sage: w._res
|
|
1820
|
+
24*x^4 + 6*x^3 + 2*x^2 + x + 1
|
|
1821
|
+
"""
|
|
1822
|
+
mapred = self._mapred
|
|
1823
|
+
children = mapred.children
|
|
1824
|
+
post_process = mapred.post_process
|
|
1825
|
+
fun = mapred.map_function
|
|
1826
|
+
reduc = mapred.reduce_function
|
|
1827
|
+
|
|
1828
|
+
# logger.debug("Working on %s..." % (node,))
|
|
1829
|
+
while True:
|
|
1830
|
+
res = post_process(node)
|
|
1831
|
+
if res is not None:
|
|
1832
|
+
self._res = reduc(self._res, fun(res))
|
|
1833
|
+
newnodes = iter(children(node))
|
|
1834
|
+
try:
|
|
1835
|
+
node = next(newnodes)
|
|
1836
|
+
except StopIteration:
|
|
1837
|
+
return
|
|
1838
|
+
self._todo.extend(newnodes)
|
|
1839
|
+
|
|
1840
|
+
|
|
1841
|
+
class RESetMPExample(RESetMapReduce):
|
|
1842
|
+
r"""
|
|
1843
|
+
An example of map reduce class.
|
|
1844
|
+
|
|
1845
|
+
INPUT:
|
|
1846
|
+
|
|
1847
|
+
- ``maxl`` -- the maximum size of permutations generated (default: `9`)
|
|
1848
|
+
|
|
1849
|
+
This computes the generating series of permutations counted by their size
|
|
1850
|
+
up to size ``maxl``.
|
|
1851
|
+
|
|
1852
|
+
EXAMPLES::
|
|
1853
|
+
|
|
1854
|
+
sage: from sage.parallel.map_reduce import RESetMPExample
|
|
1855
|
+
sage: EX = RESetMPExample()
|
|
1856
|
+
sage: EX.run()
|
|
1857
|
+
362880*x^9 + 40320*x^8 + 5040*x^7 + 720*x^6 + 120*x^5
|
|
1858
|
+
+ 24*x^4 + 6*x^3 + 2*x^2 + x + 1
|
|
1859
|
+
|
|
1860
|
+
.. SEEALSO:: This is an example of :class:`RESetMapReduce`
|
|
1861
|
+
"""
|
|
1862
|
+
def __init__(self, maxl=9):
|
|
1863
|
+
r"""
|
|
1864
|
+
TESTS::
|
|
1865
|
+
|
|
1866
|
+
sage: from sage.parallel.map_reduce import RESetMPExample
|
|
1867
|
+
sage: RESetMPExample()
|
|
1868
|
+
<sage.parallel.map_reduce.RESetMPExample object at 0x...>
|
|
1869
|
+
"""
|
|
1870
|
+
RESetMapReduce.__init__(self)
|
|
1871
|
+
from sage.rings.integer_ring import ZZ
|
|
1872
|
+
from sage.rings.polynomial.polynomial_ring import polygen
|
|
1873
|
+
self.x = polygen(ZZ, 'x')
|
|
1874
|
+
self.maxl = maxl
|
|
1875
|
+
|
|
1876
|
+
def roots(self):
|
|
1877
|
+
r"""
|
|
1878
|
+
Return the empty permutation.
|
|
1879
|
+
|
|
1880
|
+
EXAMPLES::
|
|
1881
|
+
|
|
1882
|
+
sage: from sage.parallel.map_reduce import RESetMPExample
|
|
1883
|
+
sage: RESetMPExample().roots()
|
|
1884
|
+
[[]]
|
|
1885
|
+
"""
|
|
1886
|
+
return [[]]
|
|
1887
|
+
|
|
1888
|
+
def children(self, l):
|
|
1889
|
+
r"""
|
|
1890
|
+
Return the children of the permutation `l`.
|
|
1891
|
+
|
|
1892
|
+
INPUT:
|
|
1893
|
+
|
|
1894
|
+
- ``l`` -- list containing a permutation
|
|
1895
|
+
|
|
1896
|
+
OUTPUT:
|
|
1897
|
+
|
|
1898
|
+
The lists with ``len(l)`` inserted at all possible positions into ``l``.
|
|
1899
|
+
|
|
1900
|
+
EXAMPLES::
|
|
1901
|
+
|
|
1902
|
+
sage: from sage.parallel.map_reduce import RESetMPExample
|
|
1903
|
+
sage: RESetMPExample().children([1,0])
|
|
1904
|
+
[[2, 1, 0], [1, 2, 0], [1, 0, 2]]
|
|
1905
|
+
"""
|
|
1906
|
+
return [l[:i] + [len(l)] + l[i:]
|
|
1907
|
+
for i in range(len(l) + 1)] if len(l) < self.maxl else []
|
|
1908
|
+
|
|
1909
|
+
def map_function(self, l):
|
|
1910
|
+
r"""
|
|
1911
|
+
The monomial associated to the permutation `l`.
|
|
1912
|
+
|
|
1913
|
+
INPUT:
|
|
1914
|
+
|
|
1915
|
+
- ``l`` -- list containing a permutation
|
|
1916
|
+
|
|
1917
|
+
OUTPUT:
|
|
1918
|
+
|
|
1919
|
+
The monomial ``x^len(l)``.
|
|
1920
|
+
|
|
1921
|
+
EXAMPLES::
|
|
1922
|
+
|
|
1923
|
+
sage: from sage.parallel.map_reduce import RESetMPExample
|
|
1924
|
+
sage: RESetMPExample().map_function([1,0])
|
|
1925
|
+
x^2
|
|
1926
|
+
"""
|
|
1927
|
+
return self.x**len(l)
|
|
1928
|
+
|
|
1929
|
+
|
|
1930
|
+
class RESetParallelIterator(RESetMapReduce):
|
|
1931
|
+
r"""
|
|
1932
|
+
A parallel iterator for recursively enumerated sets.
|
|
1933
|
+
|
|
1934
|
+
This demonstrates how to use :class:`RESetMapReduce` to get an iterator on
|
|
1935
|
+
a recursively enumerated set for which the computations are done in
|
|
1936
|
+
parallel.
|
|
1937
|
+
|
|
1938
|
+
EXAMPLES::
|
|
1939
|
+
|
|
1940
|
+
sage: from sage.parallel.map_reduce import RESetParallelIterator
|
|
1941
|
+
sage: S = RESetParallelIterator([[]],
|
|
1942
|
+
....: lambda l: [l + [0], l + [1]] if len(l) < 15 else [])
|
|
1943
|
+
sage: sum(1 for _ in S)
|
|
1944
|
+
65535
|
|
1945
|
+
"""
|
|
1946
|
+
def map_function(self, z):
|
|
1947
|
+
r"""
|
|
1948
|
+
Return a singleton tuple.
|
|
1949
|
+
|
|
1950
|
+
INPUT:
|
|
1951
|
+
|
|
1952
|
+
- ``z`` -- a node
|
|
1953
|
+
|
|
1954
|
+
OUTPUT:
|
|
1955
|
+
|
|
1956
|
+
The singleton ``(z, )``.
|
|
1957
|
+
|
|
1958
|
+
EXAMPLES::
|
|
1959
|
+
|
|
1960
|
+
sage: from sage.parallel.map_reduce import RESetParallelIterator
|
|
1961
|
+
sage: S = RESetParallelIterator( [[]],
|
|
1962
|
+
....: lambda l: [l + [0], l + [1]] if len(l) < 15 else [])
|
|
1963
|
+
sage: S.map_function([1, 0])
|
|
1964
|
+
([1, 0],)
|
|
1965
|
+
"""
|
|
1966
|
+
return (z,)
|
|
1967
|
+
|
|
1968
|
+
reduce_init = tuple
|
|
1969
|
+
|
|
1970
|
+
def __iter__(self):
|
|
1971
|
+
r"""
|
|
1972
|
+
EXAMPLES::
|
|
1973
|
+
|
|
1974
|
+
sage: from sage.parallel.map_reduce import RESetParallelIterator
|
|
1975
|
+
sage: S = RESetParallelIterator( [[]],
|
|
1976
|
+
....: lambda l: [l + [0], l + [1]] if len(l) < 15 else [])
|
|
1977
|
+
sage: it = iter(S)
|
|
1978
|
+
sage: next(it) # random
|
|
1979
|
+
[1, 1, 0]
|
|
1980
|
+
sage: next(it) # random
|
|
1981
|
+
[1, 1, 0, 1]
|
|
1982
|
+
sage: sum(1 for _ in it)
|
|
1983
|
+
65533
|
|
1984
|
+
"""
|
|
1985
|
+
self.setup_workers(reduce_locally=False)
|
|
1986
|
+
self.start_workers()
|
|
1987
|
+
active_proc = self._nprocess
|
|
1988
|
+
while True:
|
|
1989
|
+
newres = self._results.get()
|
|
1990
|
+
if newres is not None:
|
|
1991
|
+
logger.debug("Got some results")
|
|
1992
|
+
yield from newres
|
|
1993
|
+
else:
|
|
1994
|
+
active_proc -= 1
|
|
1995
|
+
if active_proc == 0:
|
|
1996
|
+
break
|
|
1997
|
+
self.finish()
|