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.
Files changed (719) hide show
  1. passagemath_categories-10.6.32.dist-info/METADATA +156 -0
  2. passagemath_categories-10.6.32.dist-info/RECORD +719 -0
  3. passagemath_categories-10.6.32.dist-info/WHEEL +5 -0
  4. passagemath_categories-10.6.32.dist-info/top_level.txt +2 -0
  5. passagemath_categories.libs/libgcc_s-2d945d6c.so.1 +0 -0
  6. passagemath_categories.libs/libgmp-28992bcb.so.10.5.0 +0 -0
  7. passagemath_categories.libs/libstdc++-85f2cd6d.so.6.0.33 +0 -0
  8. sage/all__sagemath_categories.py +28 -0
  9. sage/arith/all.py +38 -0
  10. sage/arith/constants.pxd +27 -0
  11. sage/arith/functions.cpython-314t-aarch64-linux-musl.so +0 -0
  12. sage/arith/functions.pxd +4 -0
  13. sage/arith/functions.pyx +221 -0
  14. sage/arith/misc.py +6552 -0
  15. sage/arith/multi_modular.cpython-314t-aarch64-linux-musl.so +0 -0
  16. sage/arith/multi_modular.pxd +39 -0
  17. sage/arith/multi_modular.pyx +994 -0
  18. sage/arith/rational_reconstruction.cpython-314t-aarch64-linux-musl.so +0 -0
  19. sage/arith/rational_reconstruction.pxd +4 -0
  20. sage/arith/rational_reconstruction.pyx +115 -0
  21. sage/arith/srange.cpython-314t-aarch64-linux-musl.so +0 -0
  22. sage/arith/srange.pyx +571 -0
  23. sage/calculus/all__sagemath_categories.py +2 -0
  24. sage/calculus/functional.py +481 -0
  25. sage/calculus/functions.py +151 -0
  26. sage/categories/additive_groups.py +73 -0
  27. sage/categories/additive_magmas.py +1044 -0
  28. sage/categories/additive_monoids.py +114 -0
  29. sage/categories/additive_semigroups.py +184 -0
  30. sage/categories/affine_weyl_groups.py +238 -0
  31. sage/categories/algebra_ideals.py +95 -0
  32. sage/categories/algebra_modules.py +96 -0
  33. sage/categories/algebras.py +349 -0
  34. sage/categories/algebras_with_basis.py +377 -0
  35. sage/categories/all.py +160 -0
  36. sage/categories/aperiodic_semigroups.py +29 -0
  37. sage/categories/associative_algebras.py +47 -0
  38. sage/categories/bialgebras.py +101 -0
  39. sage/categories/bialgebras_with_basis.py +414 -0
  40. sage/categories/bimodules.py +206 -0
  41. sage/categories/chain_complexes.py +268 -0
  42. sage/categories/classical_crystals.py +480 -0
  43. sage/categories/coalgebras.py +405 -0
  44. sage/categories/coalgebras_with_basis.py +232 -0
  45. sage/categories/coercion_methods.cpython-314t-aarch64-linux-musl.so +0 -0
  46. sage/categories/coercion_methods.pyx +52 -0
  47. sage/categories/commutative_additive_groups.py +104 -0
  48. sage/categories/commutative_additive_monoids.py +45 -0
  49. sage/categories/commutative_additive_semigroups.py +48 -0
  50. sage/categories/commutative_algebra_ideals.py +87 -0
  51. sage/categories/commutative_algebras.py +94 -0
  52. sage/categories/commutative_ring_ideals.py +58 -0
  53. sage/categories/commutative_rings.py +736 -0
  54. sage/categories/complete_discrete_valuation.py +293 -0
  55. sage/categories/complex_reflection_groups.py +145 -0
  56. sage/categories/complex_reflection_or_generalized_coxeter_groups.py +1249 -0
  57. sage/categories/coxeter_group_algebras.py +186 -0
  58. sage/categories/coxeter_groups.py +3402 -0
  59. sage/categories/crystals.py +2628 -0
  60. sage/categories/cw_complexes.py +216 -0
  61. sage/categories/dedekind_domains.py +137 -0
  62. sage/categories/discrete_valuation.py +325 -0
  63. sage/categories/distributive_magmas_and_additive_magmas.py +100 -0
  64. sage/categories/division_rings.py +114 -0
  65. sage/categories/domains.py +95 -0
  66. sage/categories/drinfeld_modules.py +789 -0
  67. sage/categories/dual.py +42 -0
  68. sage/categories/enumerated_sets.py +1146 -0
  69. sage/categories/euclidean_domains.py +271 -0
  70. sage/categories/examples/algebras_with_basis.py +102 -0
  71. sage/categories/examples/all.py +1 -0
  72. sage/categories/examples/commutative_additive_monoids.py +130 -0
  73. sage/categories/examples/commutative_additive_semigroups.py +199 -0
  74. sage/categories/examples/coxeter_groups.py +8 -0
  75. sage/categories/examples/crystals.py +236 -0
  76. sage/categories/examples/cw_complexes.py +163 -0
  77. sage/categories/examples/facade_sets.py +187 -0
  78. sage/categories/examples/filtered_algebras_with_basis.py +204 -0
  79. sage/categories/examples/filtered_modules_with_basis.py +154 -0
  80. sage/categories/examples/finite_coxeter_groups.py +252 -0
  81. sage/categories/examples/finite_dimensional_algebras_with_basis.py +148 -0
  82. sage/categories/examples/finite_dimensional_lie_algebras_with_basis.py +495 -0
  83. sage/categories/examples/finite_enumerated_sets.py +208 -0
  84. sage/categories/examples/finite_monoids.py +150 -0
  85. sage/categories/examples/finite_semigroups.py +190 -0
  86. sage/categories/examples/finite_weyl_groups.py +191 -0
  87. sage/categories/examples/graded_connected_hopf_algebras_with_basis.py +152 -0
  88. sage/categories/examples/graded_modules_with_basis.py +168 -0
  89. sage/categories/examples/graphs.py +122 -0
  90. sage/categories/examples/hopf_algebras_with_basis.py +145 -0
  91. sage/categories/examples/infinite_enumerated_sets.py +190 -0
  92. sage/categories/examples/lie_algebras.py +352 -0
  93. sage/categories/examples/lie_algebras_with_basis.py +196 -0
  94. sage/categories/examples/magmas.py +162 -0
  95. sage/categories/examples/manifolds.py +94 -0
  96. sage/categories/examples/monoids.py +144 -0
  97. sage/categories/examples/posets.py +178 -0
  98. sage/categories/examples/semigroups.py +580 -0
  99. sage/categories/examples/semigroups_cython.cpython-314t-aarch64-linux-musl.so +0 -0
  100. sage/categories/examples/semigroups_cython.pyx +221 -0
  101. sage/categories/examples/semirings.py +249 -0
  102. sage/categories/examples/sets_cat.py +706 -0
  103. sage/categories/examples/sets_with_grading.py +101 -0
  104. sage/categories/examples/with_realizations.py +542 -0
  105. sage/categories/fields.py +991 -0
  106. sage/categories/filtered_algebras.py +63 -0
  107. sage/categories/filtered_algebras_with_basis.py +548 -0
  108. sage/categories/filtered_hopf_algebras_with_basis.py +138 -0
  109. sage/categories/filtered_modules.py +210 -0
  110. sage/categories/filtered_modules_with_basis.py +1209 -0
  111. sage/categories/finite_complex_reflection_groups.py +1506 -0
  112. sage/categories/finite_coxeter_groups.py +1138 -0
  113. sage/categories/finite_crystals.py +103 -0
  114. sage/categories/finite_dimensional_algebras_with_basis.py +1860 -0
  115. sage/categories/finite_dimensional_bialgebras_with_basis.py +33 -0
  116. sage/categories/finite_dimensional_coalgebras_with_basis.py +33 -0
  117. sage/categories/finite_dimensional_graded_lie_algebras_with_basis.py +231 -0
  118. sage/categories/finite_dimensional_hopf_algebras_with_basis.py +38 -0
  119. sage/categories/finite_dimensional_lie_algebras_with_basis.py +2774 -0
  120. sage/categories/finite_dimensional_modules_with_basis.py +1407 -0
  121. sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py +167 -0
  122. sage/categories/finite_dimensional_semisimple_algebras_with_basis.py +270 -0
  123. sage/categories/finite_enumerated_sets.py +769 -0
  124. sage/categories/finite_fields.py +252 -0
  125. sage/categories/finite_groups.py +256 -0
  126. sage/categories/finite_lattice_posets.py +242 -0
  127. sage/categories/finite_monoids.py +316 -0
  128. sage/categories/finite_permutation_groups.py +339 -0
  129. sage/categories/finite_posets.py +1994 -0
  130. sage/categories/finite_semigroups.py +136 -0
  131. sage/categories/finite_sets.py +93 -0
  132. sage/categories/finite_weyl_groups.py +39 -0
  133. sage/categories/finitely_generated_lambda_bracket_algebras.py +112 -0
  134. sage/categories/finitely_generated_lie_conformal_algebras.py +114 -0
  135. sage/categories/finitely_generated_magmas.py +57 -0
  136. sage/categories/finitely_generated_semigroups.py +214 -0
  137. sage/categories/function_fields.py +76 -0
  138. sage/categories/g_sets.py +77 -0
  139. sage/categories/gcd_domains.py +65 -0
  140. sage/categories/generalized_coxeter_groups.py +94 -0
  141. sage/categories/graded_algebras.py +85 -0
  142. sage/categories/graded_algebras_with_basis.py +258 -0
  143. sage/categories/graded_bialgebras.py +32 -0
  144. sage/categories/graded_bialgebras_with_basis.py +32 -0
  145. sage/categories/graded_coalgebras.py +65 -0
  146. sage/categories/graded_coalgebras_with_basis.py +51 -0
  147. sage/categories/graded_hopf_algebras.py +41 -0
  148. sage/categories/graded_hopf_algebras_with_basis.py +169 -0
  149. sage/categories/graded_lie_algebras.py +91 -0
  150. sage/categories/graded_lie_algebras_with_basis.py +44 -0
  151. sage/categories/graded_lie_conformal_algebras.py +74 -0
  152. sage/categories/graded_modules.py +133 -0
  153. sage/categories/graded_modules_with_basis.py +329 -0
  154. sage/categories/graphs.py +138 -0
  155. sage/categories/group_algebras.py +430 -0
  156. sage/categories/groupoid.py +94 -0
  157. sage/categories/groups.py +667 -0
  158. sage/categories/h_trivial_semigroups.py +64 -0
  159. sage/categories/hecke_modules.py +185 -0
  160. sage/categories/highest_weight_crystals.py +980 -0
  161. sage/categories/hopf_algebras.py +219 -0
  162. sage/categories/hopf_algebras_with_basis.py +309 -0
  163. sage/categories/infinite_enumerated_sets.py +115 -0
  164. sage/categories/integral_domains.py +203 -0
  165. sage/categories/j_trivial_semigroups.py +29 -0
  166. sage/categories/kac_moody_algebras.py +82 -0
  167. sage/categories/kahler_algebras.py +203 -0
  168. sage/categories/l_trivial_semigroups.py +63 -0
  169. sage/categories/lambda_bracket_algebras.py +280 -0
  170. sage/categories/lambda_bracket_algebras_with_basis.py +107 -0
  171. sage/categories/lattice_posets.py +89 -0
  172. sage/categories/left_modules.py +49 -0
  173. sage/categories/lie_algebras.py +1070 -0
  174. sage/categories/lie_algebras_with_basis.py +261 -0
  175. sage/categories/lie_conformal_algebras.py +350 -0
  176. sage/categories/lie_conformal_algebras_with_basis.py +147 -0
  177. sage/categories/lie_groups.py +73 -0
  178. sage/categories/loop_crystals.py +1290 -0
  179. sage/categories/magmas.py +1189 -0
  180. sage/categories/magmas_and_additive_magmas.py +149 -0
  181. sage/categories/magmatic_algebras.py +365 -0
  182. sage/categories/manifolds.py +352 -0
  183. sage/categories/matrix_algebras.py +40 -0
  184. sage/categories/metric_spaces.py +387 -0
  185. sage/categories/modular_abelian_varieties.py +78 -0
  186. sage/categories/modules.py +989 -0
  187. sage/categories/modules_with_basis.py +2794 -0
  188. sage/categories/monoid_algebras.py +38 -0
  189. sage/categories/monoids.py +739 -0
  190. sage/categories/noetherian_rings.py +87 -0
  191. sage/categories/number_fields.py +242 -0
  192. sage/categories/ore_modules.py +189 -0
  193. sage/categories/partially_ordered_monoids.py +49 -0
  194. sage/categories/permutation_groups.py +63 -0
  195. sage/categories/pointed_sets.py +42 -0
  196. sage/categories/polyhedra.py +74 -0
  197. sage/categories/poor_man_map.py +270 -0
  198. sage/categories/posets.py +722 -0
  199. sage/categories/principal_ideal_domains.py +270 -0
  200. sage/categories/quantum_group_representations.py +543 -0
  201. sage/categories/quotient_fields.py +728 -0
  202. sage/categories/r_trivial_semigroups.py +45 -0
  203. sage/categories/regular_crystals.py +898 -0
  204. sage/categories/regular_supercrystals.py +170 -0
  205. sage/categories/right_modules.py +49 -0
  206. sage/categories/ring_ideals.py +74 -0
  207. sage/categories/rings.py +1904 -0
  208. sage/categories/rngs.py +175 -0
  209. sage/categories/schemes.py +393 -0
  210. sage/categories/semigroups.py +1060 -0
  211. sage/categories/semirings.py +71 -0
  212. sage/categories/semisimple_algebras.py +114 -0
  213. sage/categories/sets_with_grading.py +235 -0
  214. sage/categories/shephard_groups.py +43 -0
  215. sage/categories/signed_tensor.py +120 -0
  216. sage/categories/simplicial_complexes.py +134 -0
  217. sage/categories/simplicial_sets.py +1206 -0
  218. sage/categories/super_algebras.py +149 -0
  219. sage/categories/super_algebras_with_basis.py +144 -0
  220. sage/categories/super_hopf_algebras_with_basis.py +126 -0
  221. sage/categories/super_lie_conformal_algebras.py +193 -0
  222. sage/categories/super_modules.py +229 -0
  223. sage/categories/super_modules_with_basis.py +193 -0
  224. sage/categories/supercommutative_algebras.py +99 -0
  225. sage/categories/supercrystals.py +406 -0
  226. sage/categories/tensor.py +110 -0
  227. sage/categories/topological_spaces.py +170 -0
  228. sage/categories/triangular_kac_moody_algebras.py +439 -0
  229. sage/categories/tutorial.py +58 -0
  230. sage/categories/unique_factorization_domains.py +318 -0
  231. sage/categories/unital_algebras.py +426 -0
  232. sage/categories/vector_bundles.py +159 -0
  233. sage/categories/vector_spaces.py +357 -0
  234. sage/categories/weyl_groups.py +853 -0
  235. sage/combinat/all__sagemath_categories.py +34 -0
  236. sage/combinat/backtrack.py +180 -0
  237. sage/combinat/combinat.py +2269 -0
  238. sage/combinat/combinat_cython.cpython-314t-aarch64-linux-musl.so +0 -0
  239. sage/combinat/combinat_cython.pxd +6 -0
  240. sage/combinat/combinat_cython.pyx +390 -0
  241. sage/combinat/combination.py +796 -0
  242. sage/combinat/combinatorial_map.py +416 -0
  243. sage/combinat/composition.py +2192 -0
  244. sage/combinat/dlx.py +510 -0
  245. sage/combinat/integer_lists/__init__.py +7 -0
  246. sage/combinat/integer_lists/base.cpython-314t-aarch64-linux-musl.so +0 -0
  247. sage/combinat/integer_lists/base.pxd +16 -0
  248. sage/combinat/integer_lists/base.pyx +713 -0
  249. sage/combinat/integer_lists/invlex.cpython-314t-aarch64-linux-musl.so +0 -0
  250. sage/combinat/integer_lists/invlex.pxd +4 -0
  251. sage/combinat/integer_lists/invlex.pyx +1650 -0
  252. sage/combinat/integer_lists/lists.py +328 -0
  253. sage/combinat/integer_lists/nn.py +48 -0
  254. sage/combinat/integer_vector.py +1818 -0
  255. sage/combinat/integer_vector_weighted.py +413 -0
  256. sage/combinat/matrices/all__sagemath_categories.py +5 -0
  257. sage/combinat/matrices/dancing_links.cpython-314t-aarch64-linux-musl.so +0 -0
  258. sage/combinat/matrices/dancing_links.pyx +1159 -0
  259. sage/combinat/matrices/dancing_links_c.h +380 -0
  260. sage/combinat/matrices/dlxcpp.py +136 -0
  261. sage/combinat/partition.py +10070 -0
  262. sage/combinat/partitions.cpython-314t-aarch64-linux-musl.so +0 -0
  263. sage/combinat/partitions.pyx +743 -0
  264. sage/combinat/permutation.py +10168 -0
  265. sage/combinat/permutation_cython.cpython-314t-aarch64-linux-musl.so +0 -0
  266. sage/combinat/permutation_cython.pxd +11 -0
  267. sage/combinat/permutation_cython.pyx +407 -0
  268. sage/combinat/q_analogues.py +1090 -0
  269. sage/combinat/ranker.py +268 -0
  270. sage/combinat/subset.py +1561 -0
  271. sage/combinat/subsets_hereditary.py +202 -0
  272. sage/combinat/subsets_pairwise.py +184 -0
  273. sage/combinat/tools.py +63 -0
  274. sage/combinat/tuple.py +348 -0
  275. sage/data_structures/all.py +2 -0
  276. sage/data_structures/all__sagemath_categories.py +2 -0
  277. sage/data_structures/binary_matrix.pxd +138 -0
  278. sage/data_structures/binary_search.cpython-314t-aarch64-linux-musl.so +0 -0
  279. sage/data_structures/binary_search.pxd +3 -0
  280. sage/data_structures/binary_search.pyx +66 -0
  281. sage/data_structures/bitset.cpython-314t-aarch64-linux-musl.so +0 -0
  282. sage/data_structures/bitset.pxd +40 -0
  283. sage/data_structures/bitset.pyx +2385 -0
  284. sage/data_structures/bitset_base.cpython-314t-aarch64-linux-musl.so +0 -0
  285. sage/data_structures/bitset_base.pxd +926 -0
  286. sage/data_structures/bitset_base.pyx +117 -0
  287. sage/data_structures/bitset_intrinsics.h +487 -0
  288. sage/data_structures/blas_dict.cpython-314t-aarch64-linux-musl.so +0 -0
  289. sage/data_structures/blas_dict.pxd +12 -0
  290. sage/data_structures/blas_dict.pyx +469 -0
  291. sage/data_structures/list_of_pairs.cpython-314t-aarch64-linux-musl.so +0 -0
  292. sage/data_structures/list_of_pairs.pxd +16 -0
  293. sage/data_structures/list_of_pairs.pyx +122 -0
  294. sage/data_structures/mutable_poset.py +3312 -0
  295. sage/data_structures/pairing_heap.cpython-314t-aarch64-linux-musl.so +0 -0
  296. sage/data_structures/pairing_heap.h +346 -0
  297. sage/data_structures/pairing_heap.pxd +88 -0
  298. sage/data_structures/pairing_heap.pyx +1464 -0
  299. sage/data_structures/sparse_bitset.pxd +62 -0
  300. sage/data_structures/stream.py +5070 -0
  301. sage/databases/all__sagemath_categories.py +7 -0
  302. sage/databases/sql_db.py +2236 -0
  303. sage/ext/all__sagemath_categories.py +3 -0
  304. sage/ext/fast_callable.cpython-314t-aarch64-linux-musl.so +0 -0
  305. sage/ext/fast_callable.pxd +4 -0
  306. sage/ext/fast_callable.pyx +2746 -0
  307. sage/ext/fast_eval.cpython-314t-aarch64-linux-musl.so +0 -0
  308. sage/ext/fast_eval.pxd +1 -0
  309. sage/ext/fast_eval.pyx +102 -0
  310. sage/ext/interpreters/__init__.py +1 -0
  311. sage/ext/interpreters/all__sagemath_categories.py +2 -0
  312. sage/ext/interpreters/wrapper_el.cpython-314t-aarch64-linux-musl.so +0 -0
  313. sage/ext/interpreters/wrapper_el.pxd +18 -0
  314. sage/ext/interpreters/wrapper_el.pyx +148 -0
  315. sage/ext/interpreters/wrapper_py.cpython-314t-aarch64-linux-musl.so +0 -0
  316. sage/ext/interpreters/wrapper_py.pxd +17 -0
  317. sage/ext/interpreters/wrapper_py.pyx +133 -0
  318. sage/functions/airy.py +937 -0
  319. sage/functions/all.py +97 -0
  320. sage/functions/bessel.py +2102 -0
  321. sage/functions/error.py +784 -0
  322. sage/functions/exp_integral.py +1529 -0
  323. sage/functions/gamma.py +1087 -0
  324. sage/functions/generalized.py +672 -0
  325. sage/functions/hyperbolic.py +747 -0
  326. sage/functions/hypergeometric.py +1156 -0
  327. sage/functions/jacobi.py +1705 -0
  328. sage/functions/log.py +1402 -0
  329. sage/functions/min_max.py +338 -0
  330. sage/functions/orthogonal_polys.py +3106 -0
  331. sage/functions/other.py +2303 -0
  332. sage/functions/piecewise.py +1505 -0
  333. sage/functions/prime_pi.cpython-314t-aarch64-linux-musl.so +0 -0
  334. sage/functions/prime_pi.pyx +262 -0
  335. sage/functions/special.py +1212 -0
  336. sage/functions/spike_function.py +278 -0
  337. sage/functions/transcendental.py +690 -0
  338. sage/functions/trig.py +1062 -0
  339. sage/functions/wigner.py +726 -0
  340. sage/geometry/abc.cpython-314t-aarch64-linux-musl.so +0 -0
  341. sage/geometry/abc.pyx +82 -0
  342. sage/geometry/all__sagemath_categories.py +1 -0
  343. sage/groups/all__sagemath_categories.py +11 -0
  344. sage/groups/generic.py +1733 -0
  345. sage/groups/groups_catalog.py +113 -0
  346. sage/groups/perm_gps/all__sagemath_categories.py +1 -0
  347. sage/groups/perm_gps/partn_ref/all.py +1 -0
  348. sage/groups/perm_gps/partn_ref/all__sagemath_categories.py +1 -0
  349. sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.cpython-314t-aarch64-linux-musl.so +0 -0
  350. sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.pxd +52 -0
  351. sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.pyx +906 -0
  352. sage/groups/perm_gps/partn_ref/canonical_augmentation.cpython-314t-aarch64-linux-musl.so +0 -0
  353. sage/groups/perm_gps/partn_ref/canonical_augmentation.pxd +85 -0
  354. sage/groups/perm_gps/partn_ref/canonical_augmentation.pyx +534 -0
  355. sage/groups/perm_gps/partn_ref/data_structures.cpython-314t-aarch64-linux-musl.so +0 -0
  356. sage/groups/perm_gps/partn_ref/data_structures.pxd +576 -0
  357. sage/groups/perm_gps/partn_ref/data_structures.pyx +1792 -0
  358. sage/groups/perm_gps/partn_ref/double_coset.cpython-314t-aarch64-linux-musl.so +0 -0
  359. sage/groups/perm_gps/partn_ref/double_coset.pxd +45 -0
  360. sage/groups/perm_gps/partn_ref/double_coset.pyx +739 -0
  361. sage/groups/perm_gps/partn_ref/refinement_lists.cpython-314t-aarch64-linux-musl.so +0 -0
  362. sage/groups/perm_gps/partn_ref/refinement_lists.pxd +18 -0
  363. sage/groups/perm_gps/partn_ref/refinement_lists.pyx +82 -0
  364. sage/groups/perm_gps/partn_ref/refinement_python.cpython-314t-aarch64-linux-musl.so +0 -0
  365. sage/groups/perm_gps/partn_ref/refinement_python.pxd +16 -0
  366. sage/groups/perm_gps/partn_ref/refinement_python.pyx +564 -0
  367. sage/groups/perm_gps/partn_ref/refinement_sets.cpython-314t-aarch64-linux-musl.so +0 -0
  368. sage/groups/perm_gps/partn_ref/refinement_sets.pxd +60 -0
  369. sage/groups/perm_gps/partn_ref/refinement_sets.pyx +858 -0
  370. sage/interfaces/abc.py +140 -0
  371. sage/interfaces/all.py +58 -0
  372. sage/interfaces/all__sagemath_categories.py +1 -0
  373. sage/interfaces/expect.py +1643 -0
  374. sage/interfaces/interface.py +1682 -0
  375. sage/interfaces/process.cpython-314t-aarch64-linux-musl.so +0 -0
  376. sage/interfaces/process.pxd +5 -0
  377. sage/interfaces/process.pyx +288 -0
  378. sage/interfaces/quit.py +167 -0
  379. sage/interfaces/sage0.py +604 -0
  380. sage/interfaces/sagespawn.cpython-314t-aarch64-linux-musl.so +0 -0
  381. sage/interfaces/sagespawn.pyx +308 -0
  382. sage/interfaces/tab_completion.py +101 -0
  383. sage/misc/all__sagemath_categories.py +78 -0
  384. sage/misc/allocator.cpython-314t-aarch64-linux-musl.so +0 -0
  385. sage/misc/allocator.pxd +6 -0
  386. sage/misc/allocator.pyx +47 -0
  387. sage/misc/binary_tree.cpython-314t-aarch64-linux-musl.so +0 -0
  388. sage/misc/binary_tree.pxd +29 -0
  389. sage/misc/binary_tree.pyx +537 -0
  390. sage/misc/callable_dict.cpython-314t-aarch64-linux-musl.so +0 -0
  391. sage/misc/callable_dict.pyx +89 -0
  392. sage/misc/citation.cpython-314t-aarch64-linux-musl.so +0 -0
  393. sage/misc/citation.pyx +159 -0
  394. sage/misc/converting_dict.py +293 -0
  395. sage/misc/defaults.py +129 -0
  396. sage/misc/derivative.cpython-314t-aarch64-linux-musl.so +0 -0
  397. sage/misc/derivative.pyx +223 -0
  398. sage/misc/functional.py +2005 -0
  399. sage/misc/html.py +589 -0
  400. sage/misc/latex.py +2673 -0
  401. sage/misc/latex_macros.py +236 -0
  402. sage/misc/latex_standalone.py +1833 -0
  403. sage/misc/map_threaded.py +38 -0
  404. sage/misc/mathml.py +76 -0
  405. sage/misc/method_decorator.py +88 -0
  406. sage/misc/mrange.py +755 -0
  407. sage/misc/multireplace.py +41 -0
  408. sage/misc/object_multiplexer.py +92 -0
  409. sage/misc/parser.cpython-314t-aarch64-linux-musl.so +0 -0
  410. sage/misc/parser.pyx +1107 -0
  411. sage/misc/random_testing.py +264 -0
  412. sage/misc/rest_index_of_methods.py +377 -0
  413. sage/misc/search.cpython-314t-aarch64-linux-musl.so +0 -0
  414. sage/misc/search.pxd +2 -0
  415. sage/misc/search.pyx +68 -0
  416. sage/misc/stopgap.cpython-314t-aarch64-linux-musl.so +0 -0
  417. sage/misc/stopgap.pyx +95 -0
  418. sage/misc/table.py +853 -0
  419. sage/monoids/all__sagemath_categories.py +1 -0
  420. sage/monoids/indexed_free_monoid.py +1071 -0
  421. sage/monoids/monoid.py +82 -0
  422. sage/numerical/all__sagemath_categories.py +1 -0
  423. sage/numerical/backends/all__sagemath_categories.py +1 -0
  424. sage/numerical/backends/generic_backend.cpython-314t-aarch64-linux-musl.so +0 -0
  425. sage/numerical/backends/generic_backend.pxd +61 -0
  426. sage/numerical/backends/generic_backend.pyx +1893 -0
  427. sage/numerical/backends/generic_sdp_backend.cpython-314t-aarch64-linux-musl.so +0 -0
  428. sage/numerical/backends/generic_sdp_backend.pxd +38 -0
  429. sage/numerical/backends/generic_sdp_backend.pyx +755 -0
  430. sage/parallel/all.py +6 -0
  431. sage/parallel/decorate.py +575 -0
  432. sage/parallel/map_reduce.py +1997 -0
  433. sage/parallel/multiprocessing_sage.py +76 -0
  434. sage/parallel/ncpus.py +35 -0
  435. sage/parallel/parallelism.py +364 -0
  436. sage/parallel/reference.py +47 -0
  437. sage/parallel/use_fork.py +333 -0
  438. sage/rings/abc.cpython-314t-aarch64-linux-musl.so +0 -0
  439. sage/rings/abc.pxd +31 -0
  440. sage/rings/abc.pyx +526 -0
  441. sage/rings/algebraic_closure_finite_field.py +1154 -0
  442. sage/rings/all__sagemath_categories.py +91 -0
  443. sage/rings/big_oh.py +227 -0
  444. sage/rings/continued_fraction.py +2754 -0
  445. sage/rings/continued_fraction_gosper.py +220 -0
  446. sage/rings/factorint.cpython-314t-aarch64-linux-musl.so +0 -0
  447. sage/rings/factorint.pyx +295 -0
  448. sage/rings/fast_arith.cpython-314t-aarch64-linux-musl.so +0 -0
  449. sage/rings/fast_arith.pxd +21 -0
  450. sage/rings/fast_arith.pyx +535 -0
  451. sage/rings/finite_rings/all__sagemath_categories.py +9 -0
  452. sage/rings/finite_rings/conway_polynomials.py +542 -0
  453. sage/rings/finite_rings/element_base.cpython-314t-aarch64-linux-musl.so +0 -0
  454. sage/rings/finite_rings/element_base.pxd +12 -0
  455. sage/rings/finite_rings/element_base.pyx +1176 -0
  456. sage/rings/finite_rings/finite_field_base.cpython-314t-aarch64-linux-musl.so +0 -0
  457. sage/rings/finite_rings/finite_field_base.pxd +7 -0
  458. sage/rings/finite_rings/finite_field_base.pyx +2171 -0
  459. sage/rings/finite_rings/finite_field_constructor.py +827 -0
  460. sage/rings/finite_rings/finite_field_prime_modn.py +372 -0
  461. sage/rings/finite_rings/galois_group.py +154 -0
  462. sage/rings/finite_rings/hom_finite_field.cpython-314t-aarch64-linux-musl.so +0 -0
  463. sage/rings/finite_rings/hom_finite_field.pxd +23 -0
  464. sage/rings/finite_rings/hom_finite_field.pyx +856 -0
  465. sage/rings/finite_rings/hom_prime_finite_field.cpython-314t-aarch64-linux-musl.so +0 -0
  466. sage/rings/finite_rings/hom_prime_finite_field.pxd +15 -0
  467. sage/rings/finite_rings/hom_prime_finite_field.pyx +164 -0
  468. sage/rings/finite_rings/homset.py +357 -0
  469. sage/rings/finite_rings/integer_mod.cpython-314t-aarch64-linux-musl.so +0 -0
  470. sage/rings/finite_rings/integer_mod.pxd +56 -0
  471. sage/rings/finite_rings/integer_mod.pyx +4586 -0
  472. sage/rings/finite_rings/integer_mod_limits.h +11 -0
  473. sage/rings/finite_rings/integer_mod_ring.py +2044 -0
  474. sage/rings/finite_rings/residue_field.cpython-314t-aarch64-linux-musl.so +0 -0
  475. sage/rings/finite_rings/residue_field.pxd +30 -0
  476. sage/rings/finite_rings/residue_field.pyx +1811 -0
  477. sage/rings/finite_rings/stdint.pxd +19 -0
  478. sage/rings/fraction_field.py +1452 -0
  479. sage/rings/fraction_field_element.cpython-314t-aarch64-linux-musl.so +0 -0
  480. sage/rings/fraction_field_element.pyx +1357 -0
  481. sage/rings/function_field/all.py +7 -0
  482. sage/rings/function_field/all__sagemath_categories.py +2 -0
  483. sage/rings/function_field/constructor.py +218 -0
  484. sage/rings/function_field/element.cpython-314t-aarch64-linux-musl.so +0 -0
  485. sage/rings/function_field/element.pxd +11 -0
  486. sage/rings/function_field/element.pyx +1008 -0
  487. sage/rings/function_field/element_rational.cpython-314t-aarch64-linux-musl.so +0 -0
  488. sage/rings/function_field/element_rational.pyx +513 -0
  489. sage/rings/function_field/extensions.py +230 -0
  490. sage/rings/function_field/function_field.py +1468 -0
  491. sage/rings/function_field/function_field_rational.py +1005 -0
  492. sage/rings/function_field/ideal.py +1155 -0
  493. sage/rings/function_field/ideal_rational.py +629 -0
  494. sage/rings/function_field/jacobian_base.py +826 -0
  495. sage/rings/function_field/jacobian_hess.py +1053 -0
  496. sage/rings/function_field/jacobian_khuri_makdisi.py +1027 -0
  497. sage/rings/function_field/maps.py +1039 -0
  498. sage/rings/function_field/order.py +281 -0
  499. sage/rings/function_field/order_basis.py +586 -0
  500. sage/rings/function_field/order_rational.py +576 -0
  501. sage/rings/function_field/place.py +426 -0
  502. sage/rings/function_field/place_rational.py +181 -0
  503. sage/rings/generic.py +320 -0
  504. sage/rings/homset.py +332 -0
  505. sage/rings/ideal.py +1885 -0
  506. sage/rings/ideal_monoid.py +215 -0
  507. sage/rings/infinity.py +1890 -0
  508. sage/rings/integer.cpython-314t-aarch64-linux-musl.so +0 -0
  509. sage/rings/integer.pxd +45 -0
  510. sage/rings/integer.pyx +7874 -0
  511. sage/rings/integer_ring.cpython-314t-aarch64-linux-musl.so +0 -0
  512. sage/rings/integer_ring.pxd +8 -0
  513. sage/rings/integer_ring.pyx +1693 -0
  514. sage/rings/laurent_series_ring.py +931 -0
  515. sage/rings/laurent_series_ring_element.cpython-314t-aarch64-linux-musl.so +0 -0
  516. sage/rings/laurent_series_ring_element.pxd +11 -0
  517. sage/rings/laurent_series_ring_element.pyx +1927 -0
  518. sage/rings/lazy_series.py +7815 -0
  519. sage/rings/lazy_series_ring.py +4356 -0
  520. sage/rings/localization.py +1043 -0
  521. sage/rings/morphism.cpython-314t-aarch64-linux-musl.so +0 -0
  522. sage/rings/morphism.pxd +39 -0
  523. sage/rings/morphism.pyx +3299 -0
  524. sage/rings/multi_power_series_ring.py +1145 -0
  525. sage/rings/multi_power_series_ring_element.py +2184 -0
  526. sage/rings/noncommutative_ideals.cpython-314t-aarch64-linux-musl.so +0 -0
  527. sage/rings/noncommutative_ideals.pyx +423 -0
  528. sage/rings/number_field/all__sagemath_categories.py +1 -0
  529. sage/rings/number_field/number_field_base.cpython-314t-aarch64-linux-musl.so +0 -0
  530. sage/rings/number_field/number_field_base.pxd +8 -0
  531. sage/rings/number_field/number_field_base.pyx +507 -0
  532. sage/rings/number_field/number_field_element_base.cpython-314t-aarch64-linux-musl.so +0 -0
  533. sage/rings/number_field/number_field_element_base.pxd +6 -0
  534. sage/rings/number_field/number_field_element_base.pyx +36 -0
  535. sage/rings/number_field/number_field_ideal.py +3550 -0
  536. sage/rings/padics/all__sagemath_categories.py +4 -0
  537. sage/rings/padics/local_generic.py +1670 -0
  538. sage/rings/padics/local_generic_element.cpython-314t-aarch64-linux-musl.so +0 -0
  539. sage/rings/padics/local_generic_element.pxd +5 -0
  540. sage/rings/padics/local_generic_element.pyx +1017 -0
  541. sage/rings/padics/misc.py +256 -0
  542. sage/rings/padics/padic_generic.py +1911 -0
  543. sage/rings/padics/pow_computer.cpython-314t-aarch64-linux-musl.so +0 -0
  544. sage/rings/padics/pow_computer.pxd +38 -0
  545. sage/rings/padics/pow_computer.pyx +671 -0
  546. sage/rings/padics/precision_error.py +24 -0
  547. sage/rings/polynomial/all__sagemath_categories.py +25 -0
  548. sage/rings/polynomial/commutative_polynomial.cpython-314t-aarch64-linux-musl.so +0 -0
  549. sage/rings/polynomial/commutative_polynomial.pxd +6 -0
  550. sage/rings/polynomial/commutative_polynomial.pyx +24 -0
  551. sage/rings/polynomial/cyclotomic.cpython-314t-aarch64-linux-musl.so +0 -0
  552. sage/rings/polynomial/cyclotomic.pyx +404 -0
  553. sage/rings/polynomial/flatten.py +711 -0
  554. sage/rings/polynomial/ideal.py +102 -0
  555. sage/rings/polynomial/infinite_polynomial_element.py +1768 -0
  556. sage/rings/polynomial/infinite_polynomial_ring.py +1653 -0
  557. sage/rings/polynomial/laurent_polynomial.cpython-314t-aarch64-linux-musl.so +0 -0
  558. sage/rings/polynomial/laurent_polynomial.pxd +18 -0
  559. sage/rings/polynomial/laurent_polynomial.pyx +2190 -0
  560. sage/rings/polynomial/laurent_polynomial_ideal.py +590 -0
  561. sage/rings/polynomial/laurent_polynomial_ring.py +832 -0
  562. sage/rings/polynomial/laurent_polynomial_ring_base.py +708 -0
  563. sage/rings/polynomial/multi_polynomial.cpython-314t-aarch64-linux-musl.so +0 -0
  564. sage/rings/polynomial/multi_polynomial.pxd +12 -0
  565. sage/rings/polynomial/multi_polynomial.pyx +3082 -0
  566. sage/rings/polynomial/multi_polynomial_element.py +2570 -0
  567. sage/rings/polynomial/multi_polynomial_ideal.py +5771 -0
  568. sage/rings/polynomial/multi_polynomial_ring.py +947 -0
  569. sage/rings/polynomial/multi_polynomial_ring_base.cpython-314t-aarch64-linux-musl.so +0 -0
  570. sage/rings/polynomial/multi_polynomial_ring_base.pxd +15 -0
  571. sage/rings/polynomial/multi_polynomial_ring_base.pyx +1855 -0
  572. sage/rings/polynomial/multi_polynomial_sequence.py +2204 -0
  573. sage/rings/polynomial/polydict.cpython-314t-aarch64-linux-musl.so +0 -0
  574. sage/rings/polynomial/polydict.pxd +45 -0
  575. sage/rings/polynomial/polydict.pyx +2701 -0
  576. sage/rings/polynomial/polynomial_compiled.cpython-314t-aarch64-linux-musl.so +0 -0
  577. sage/rings/polynomial/polynomial_compiled.pxd +59 -0
  578. sage/rings/polynomial/polynomial_compiled.pyx +509 -0
  579. sage/rings/polynomial/polynomial_element.cpython-314t-aarch64-linux-musl.so +0 -0
  580. sage/rings/polynomial/polynomial_element.pxd +64 -0
  581. sage/rings/polynomial/polynomial_element.pyx +13255 -0
  582. sage/rings/polynomial/polynomial_element_generic.py +1637 -0
  583. sage/rings/polynomial/polynomial_fateman.py +97 -0
  584. sage/rings/polynomial/polynomial_quotient_ring.py +2465 -0
  585. sage/rings/polynomial/polynomial_quotient_ring_element.py +779 -0
  586. sage/rings/polynomial/polynomial_ring.py +3784 -0
  587. sage/rings/polynomial/polynomial_ring_constructor.py +1051 -0
  588. sage/rings/polynomial/polynomial_ring_homomorphism.cpython-314t-aarch64-linux-musl.so +0 -0
  589. sage/rings/polynomial/polynomial_ring_homomorphism.pxd +5 -0
  590. sage/rings/polynomial/polynomial_ring_homomorphism.pyx +121 -0
  591. sage/rings/polynomial/polynomial_singular_interface.py +549 -0
  592. sage/rings/polynomial/symmetric_ideal.py +989 -0
  593. sage/rings/polynomial/symmetric_reduction.cpython-314t-aarch64-linux-musl.so +0 -0
  594. sage/rings/polynomial/symmetric_reduction.pxd +8 -0
  595. sage/rings/polynomial/symmetric_reduction.pyx +669 -0
  596. sage/rings/polynomial/term_order.py +2279 -0
  597. sage/rings/polynomial/toy_buchberger.py +449 -0
  598. sage/rings/polynomial/toy_d_basis.py +387 -0
  599. sage/rings/polynomial/toy_variety.py +362 -0
  600. sage/rings/power_series_mpoly.cpython-314t-aarch64-linux-musl.so +0 -0
  601. sage/rings/power_series_mpoly.pxd +9 -0
  602. sage/rings/power_series_mpoly.pyx +161 -0
  603. sage/rings/power_series_poly.cpython-314t-aarch64-linux-musl.so +0 -0
  604. sage/rings/power_series_poly.pxd +10 -0
  605. sage/rings/power_series_poly.pyx +1317 -0
  606. sage/rings/power_series_ring.py +1441 -0
  607. sage/rings/power_series_ring_element.cpython-314t-aarch64-linux-musl.so +0 -0
  608. sage/rings/power_series_ring_element.pxd +12 -0
  609. sage/rings/power_series_ring_element.pyx +3028 -0
  610. sage/rings/puiseux_series_ring.py +487 -0
  611. sage/rings/puiseux_series_ring_element.cpython-314t-aarch64-linux-musl.so +0 -0
  612. sage/rings/puiseux_series_ring_element.pxd +7 -0
  613. sage/rings/puiseux_series_ring_element.pyx +1055 -0
  614. sage/rings/qqbar_decorators.py +167 -0
  615. sage/rings/quotient_ring.py +1598 -0
  616. sage/rings/quotient_ring_element.py +979 -0
  617. sage/rings/rational.cpython-314t-aarch64-linux-musl.so +0 -0
  618. sage/rings/rational.pxd +20 -0
  619. sage/rings/rational.pyx +4284 -0
  620. sage/rings/rational_field.py +1730 -0
  621. sage/rings/real_double.cpython-314t-aarch64-linux-musl.so +0 -0
  622. sage/rings/real_double.pxd +16 -0
  623. sage/rings/real_double.pyx +2218 -0
  624. sage/rings/real_lazy.cpython-314t-aarch64-linux-musl.so +0 -0
  625. sage/rings/real_lazy.pxd +30 -0
  626. sage/rings/real_lazy.pyx +1773 -0
  627. sage/rings/ring.cpython-314t-aarch64-linux-musl.so +0 -0
  628. sage/rings/ring.pxd +30 -0
  629. sage/rings/ring.pyx +850 -0
  630. sage/rings/semirings/all.py +3 -0
  631. sage/rings/semirings/non_negative_integer_semiring.py +107 -0
  632. sage/rings/semirings/tropical_mpolynomial.py +972 -0
  633. sage/rings/semirings/tropical_polynomial.py +997 -0
  634. sage/rings/semirings/tropical_semiring.cpython-314t-aarch64-linux-musl.so +0 -0
  635. sage/rings/semirings/tropical_semiring.pyx +676 -0
  636. sage/rings/semirings/tropical_variety.py +1701 -0
  637. sage/rings/sum_of_squares.cpython-314t-aarch64-linux-musl.so +0 -0
  638. sage/rings/sum_of_squares.pxd +3 -0
  639. sage/rings/sum_of_squares.pyx +336 -0
  640. sage/rings/tests.py +504 -0
  641. sage/schemes/affine/affine_homset.py +508 -0
  642. sage/schemes/affine/affine_morphism.py +1574 -0
  643. sage/schemes/affine/affine_point.py +460 -0
  644. sage/schemes/affine/affine_rational_point.py +308 -0
  645. sage/schemes/affine/affine_space.py +1264 -0
  646. sage/schemes/affine/affine_subscheme.py +592 -0
  647. sage/schemes/affine/all.py +25 -0
  648. sage/schemes/all__sagemath_categories.py +5 -0
  649. sage/schemes/generic/algebraic_scheme.py +2092 -0
  650. sage/schemes/generic/all.py +5 -0
  651. sage/schemes/generic/ambient_space.py +400 -0
  652. sage/schemes/generic/divisor.py +465 -0
  653. sage/schemes/generic/divisor_group.py +313 -0
  654. sage/schemes/generic/glue.py +84 -0
  655. sage/schemes/generic/homset.py +820 -0
  656. sage/schemes/generic/hypersurface.py +234 -0
  657. sage/schemes/generic/morphism.py +2107 -0
  658. sage/schemes/generic/point.py +237 -0
  659. sage/schemes/generic/scheme.py +1190 -0
  660. sage/schemes/generic/spec.py +199 -0
  661. sage/schemes/product_projective/all.py +6 -0
  662. sage/schemes/product_projective/homset.py +236 -0
  663. sage/schemes/product_projective/morphism.py +517 -0
  664. sage/schemes/product_projective/point.py +568 -0
  665. sage/schemes/product_projective/rational_point.py +550 -0
  666. sage/schemes/product_projective/space.py +1301 -0
  667. sage/schemes/product_projective/subscheme.py +466 -0
  668. sage/schemes/projective/all.py +24 -0
  669. sage/schemes/projective/proj_bdd_height.py +453 -0
  670. sage/schemes/projective/projective_homset.py +718 -0
  671. sage/schemes/projective/projective_morphism.py +2792 -0
  672. sage/schemes/projective/projective_point.py +1484 -0
  673. sage/schemes/projective/projective_rational_point.py +569 -0
  674. sage/schemes/projective/projective_space.py +2571 -0
  675. sage/schemes/projective/projective_subscheme.py +1574 -0
  676. sage/sets/all.py +17 -0
  677. sage/sets/cartesian_product.py +376 -0
  678. sage/sets/condition_set.py +525 -0
  679. sage/sets/disjoint_set.cpython-314t-aarch64-linux-musl.so +0 -0
  680. sage/sets/disjoint_set.pxd +36 -0
  681. sage/sets/disjoint_set.pyx +998 -0
  682. sage/sets/disjoint_union_enumerated_sets.py +625 -0
  683. sage/sets/family.cpython-314t-aarch64-linux-musl.so +0 -0
  684. sage/sets/family.pxd +12 -0
  685. sage/sets/family.pyx +1556 -0
  686. sage/sets/finite_enumerated_set.py +406 -0
  687. sage/sets/finite_set_map_cy.cpython-314t-aarch64-linux-musl.so +0 -0
  688. sage/sets/finite_set_map_cy.pxd +34 -0
  689. sage/sets/finite_set_map_cy.pyx +708 -0
  690. sage/sets/finite_set_maps.py +591 -0
  691. sage/sets/image_set.py +448 -0
  692. sage/sets/integer_range.py +829 -0
  693. sage/sets/non_negative_integers.py +241 -0
  694. sage/sets/positive_integers.py +93 -0
  695. sage/sets/primes.py +188 -0
  696. sage/sets/real_set.py +2760 -0
  697. sage/sets/recursively_enumerated_set.cpython-314t-aarch64-linux-musl.so +0 -0
  698. sage/sets/recursively_enumerated_set.pxd +31 -0
  699. sage/sets/recursively_enumerated_set.pyx +2082 -0
  700. sage/sets/set.py +2083 -0
  701. sage/sets/set_from_iterator.py +1021 -0
  702. sage/sets/totally_ordered_finite_set.py +329 -0
  703. sage/symbolic/all__sagemath_categories.py +1 -0
  704. sage/symbolic/function.cpython-314t-aarch64-linux-musl.so +0 -0
  705. sage/symbolic/function.pxd +29 -0
  706. sage/symbolic/function.pyx +1488 -0
  707. sage/symbolic/symbols.py +56 -0
  708. sage/tests/all__sagemath_categories.py +1 -0
  709. sage/tests/cython.cpython-314t-aarch64-linux-musl.so +0 -0
  710. sage/tests/cython.pyx +37 -0
  711. sage/tests/stl_vector.cpython-314t-aarch64-linux-musl.so +0 -0
  712. sage/tests/stl_vector.pyx +171 -0
  713. sage/typeset/all.py +6 -0
  714. sage/typeset/ascii_art.py +295 -0
  715. sage/typeset/character_art.py +789 -0
  716. sage/typeset/character_art_factory.py +572 -0
  717. sage/typeset/symbols.py +334 -0
  718. sage/typeset/unicode_art.py +183 -0
  719. 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()