passagemath-objects 10.6.41__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. passagemath_objects/__init__.py +3 -0
  2. passagemath_objects-10.6.41.dist-info/DELVEWHEEL +2 -0
  3. passagemath_objects-10.6.41.dist-info/METADATA +115 -0
  4. passagemath_objects-10.6.41.dist-info/RECORD +281 -0
  5. passagemath_objects-10.6.41.dist-info/WHEEL +5 -0
  6. passagemath_objects-10.6.41.dist-info/top_level.txt +3 -0
  7. passagemath_objects.libs/libgmp-10-79b4110c7ea2b760f16cfef97e8a8a34.dll +0 -0
  8. sage/all__sagemath_objects.py +46 -0
  9. sage/arith/all__sagemath_objects.py +5 -0
  10. sage/arith/long.pxd +411 -0
  11. sage/arith/numerical_approx.cp312-win_amd64.pyd +0 -0
  12. sage/arith/numerical_approx.pxd +35 -0
  13. sage/arith/numerical_approx.pyx +75 -0
  14. sage/arith/power.cp312-win_amd64.pyd +0 -0
  15. sage/arith/power.pxd +31 -0
  16. sage/arith/power.pyx +127 -0
  17. sage/categories/action.cp312-win_amd64.pyd +0 -0
  18. sage/categories/action.pxd +29 -0
  19. sage/categories/action.pyx +641 -0
  20. sage/categories/algebra_functor.py +745 -0
  21. sage/categories/all__sagemath_objects.py +33 -0
  22. sage/categories/basic.py +71 -0
  23. sage/categories/cartesian_product.py +295 -0
  24. sage/categories/category.py +3401 -0
  25. sage/categories/category_cy_helper.cp312-win_amd64.pyd +0 -0
  26. sage/categories/category_cy_helper.pxd +8 -0
  27. sage/categories/category_cy_helper.pyx +322 -0
  28. sage/categories/category_singleton.cp312-win_amd64.pyd +0 -0
  29. sage/categories/category_singleton.pxd +3 -0
  30. sage/categories/category_singleton.pyx +342 -0
  31. sage/categories/category_types.py +637 -0
  32. sage/categories/category_with_axiom.py +2885 -0
  33. sage/categories/covariant_functorial_construction.py +703 -0
  34. sage/categories/facade_sets.py +228 -0
  35. sage/categories/functor.cp312-win_amd64.pyd +0 -0
  36. sage/categories/functor.pxd +7 -0
  37. sage/categories/functor.pyx +691 -0
  38. sage/categories/homset.py +1338 -0
  39. sage/categories/homsets.py +364 -0
  40. sage/categories/isomorphic_objects.py +73 -0
  41. sage/categories/map.cp312-win_amd64.pyd +0 -0
  42. sage/categories/map.pxd +34 -0
  43. sage/categories/map.pyx +2112 -0
  44. sage/categories/morphism.cp312-win_amd64.pyd +0 -0
  45. sage/categories/morphism.pxd +14 -0
  46. sage/categories/morphism.pyx +895 -0
  47. sage/categories/objects.py +167 -0
  48. sage/categories/primer.py +1696 -0
  49. sage/categories/pushout.py +4834 -0
  50. sage/categories/quotients.py +64 -0
  51. sage/categories/realizations.py +200 -0
  52. sage/categories/sets_cat.py +3228 -0
  53. sage/categories/sets_with_partial_maps.py +52 -0
  54. sage/categories/subobjects.py +64 -0
  55. sage/categories/subquotients.py +21 -0
  56. sage/categories/with_realizations.py +311 -0
  57. sage/cpython/__init__.py +28 -0
  58. sage/cpython/_py2_random.py +619 -0
  59. sage/cpython/all.py +3 -0
  60. sage/cpython/atexit.cp312-win_amd64.pyd +0 -0
  61. sage/cpython/atexit.pyx +269 -0
  62. sage/cpython/builtin_types.cp312-win_amd64.pyd +0 -0
  63. sage/cpython/builtin_types.pyx +7 -0
  64. sage/cpython/cython_metaclass.cp312-win_amd64.pyd +0 -0
  65. sage/cpython/cython_metaclass.h +117 -0
  66. sage/cpython/cython_metaclass.pxd +3 -0
  67. sage/cpython/cython_metaclass.pyx +130 -0
  68. sage/cpython/debug.cp312-win_amd64.pyd +0 -0
  69. sage/cpython/debug.pyx +302 -0
  70. sage/cpython/dict_del_by_value.cp312-win_amd64.pyd +0 -0
  71. sage/cpython/dict_del_by_value.pxd +9 -0
  72. sage/cpython/dict_del_by_value.pyx +191 -0
  73. sage/cpython/dict_internal.h +245 -0
  74. sage/cpython/getattr.cp312-win_amd64.pyd +0 -0
  75. sage/cpython/getattr.pxd +9 -0
  76. sage/cpython/getattr.pyx +439 -0
  77. sage/cpython/pycore_long.h +97 -0
  78. sage/cpython/pycore_long.pxd +10 -0
  79. sage/cpython/python_debug.h +44 -0
  80. sage/cpython/python_debug.pxd +47 -0
  81. sage/cpython/pyx_visit.h +13 -0
  82. sage/cpython/string.cp312-win_amd64.pyd +0 -0
  83. sage/cpython/string.pxd +76 -0
  84. sage/cpython/string.pyx +34 -0
  85. sage/cpython/string_impl.h +60 -0
  86. sage/cpython/type.cp312-win_amd64.pyd +0 -0
  87. sage/cpython/type.pxd +2 -0
  88. sage/cpython/type.pyx +40 -0
  89. sage/cpython/wrapperdescr.pxd +67 -0
  90. sage/ext/all__sagemath_objects.py +3 -0
  91. sage/ext/ccobject.h +64 -0
  92. sage/ext/cplusplus.pxd +17 -0
  93. sage/ext/mod_int.h +30 -0
  94. sage/ext/mod_int.pxd +24 -0
  95. sage/ext/stdsage.pxd +39 -0
  96. sage/groups/all__sagemath_objects.py +1 -0
  97. sage/groups/group.cp312-win_amd64.pyd +0 -0
  98. sage/groups/group.pxd +14 -0
  99. sage/groups/group.pyx +322 -0
  100. sage/groups/old.cp312-win_amd64.pyd +0 -0
  101. sage/groups/old.pxd +14 -0
  102. sage/groups/old.pyx +219 -0
  103. sage/libs/all__sagemath_objects.py +3 -0
  104. sage/libs/gmp/__init__.py +1 -0
  105. sage/libs/gmp/all.pxd +6 -0
  106. sage/libs/gmp/binop.pxd +23 -0
  107. sage/libs/gmp/misc.pxd +8 -0
  108. sage/libs/gmp/mpf.pxd +88 -0
  109. sage/libs/gmp/mpn.pxd +57 -0
  110. sage/libs/gmp/mpq.pxd +57 -0
  111. sage/libs/gmp/mpz.pxd +202 -0
  112. sage/libs/gmp/pylong.cp312-win_amd64.pyd +0 -0
  113. sage/libs/gmp/pylong.pxd +12 -0
  114. sage/libs/gmp/pylong.pyx +150 -0
  115. sage/libs/gmp/random.pxd +25 -0
  116. sage/libs/gmp/randomize.pxd +59 -0
  117. sage/libs/gmp/types.pxd +53 -0
  118. sage/libs/gmpxx.pxd +19 -0
  119. sage/misc/abstract_method.py +276 -0
  120. sage/misc/all__sagemath_objects.py +43 -0
  121. sage/misc/bindable_class.py +253 -0
  122. sage/misc/c3_controlled.cp312-win_amd64.pyd +0 -0
  123. sage/misc/c3_controlled.pxd +2 -0
  124. sage/misc/c3_controlled.pyx +1402 -0
  125. sage/misc/cachefunc.cp312-win_amd64.pyd +0 -0
  126. sage/misc/cachefunc.pxd +43 -0
  127. sage/misc/cachefunc.pyx +3781 -0
  128. sage/misc/call.py +188 -0
  129. sage/misc/classcall_metaclass.cp312-win_amd64.pyd +0 -0
  130. sage/misc/classcall_metaclass.pxd +14 -0
  131. sage/misc/classcall_metaclass.pyx +599 -0
  132. sage/misc/constant_function.cp312-win_amd64.pyd +0 -0
  133. sage/misc/constant_function.pyx +130 -0
  134. sage/misc/decorators.py +747 -0
  135. sage/misc/fast_methods.cp312-win_amd64.pyd +0 -0
  136. sage/misc/fast_methods.pxd +20 -0
  137. sage/misc/fast_methods.pyx +351 -0
  138. sage/misc/flatten.py +90 -0
  139. sage/misc/fpickle.cp312-win_amd64.pyd +0 -0
  140. sage/misc/fpickle.pyx +177 -0
  141. sage/misc/function_mangling.cp312-win_amd64.pyd +0 -0
  142. sage/misc/function_mangling.pxd +11 -0
  143. sage/misc/function_mangling.pyx +308 -0
  144. sage/misc/inherit_comparison.cp312-win_amd64.pyd +0 -0
  145. sage/misc/inherit_comparison.pxd +5 -0
  146. sage/misc/inherit_comparison.pyx +105 -0
  147. sage/misc/instancedoc.cp312-win_amd64.pyd +0 -0
  148. sage/misc/instancedoc.pyx +331 -0
  149. sage/misc/lazy_attribute.cp312-win_amd64.pyd +0 -0
  150. sage/misc/lazy_attribute.pyx +607 -0
  151. sage/misc/lazy_format.py +135 -0
  152. sage/misc/lazy_import.cp312-win_amd64.pyd +0 -0
  153. sage/misc/lazy_import.pyx +1299 -0
  154. sage/misc/lazy_import_cache.py +36 -0
  155. sage/misc/lazy_list.cp312-win_amd64.pyd +0 -0
  156. sage/misc/lazy_list.pxd +19 -0
  157. sage/misc/lazy_list.pyx +1187 -0
  158. sage/misc/lazy_string.cp312-win_amd64.pyd +0 -0
  159. sage/misc/lazy_string.pxd +7 -0
  160. sage/misc/lazy_string.pyx +546 -0
  161. sage/misc/misc.py +1066 -0
  162. sage/misc/misc_c.cp312-win_amd64.pyd +0 -0
  163. sage/misc/misc_c.pxd +3 -0
  164. sage/misc/misc_c.pyx +766 -0
  165. sage/misc/namespace_package.py +37 -0
  166. sage/misc/nested_class.cp312-win_amd64.pyd +0 -0
  167. sage/misc/nested_class.pxd +3 -0
  168. sage/misc/nested_class.pyx +394 -0
  169. sage/misc/persist.cp312-win_amd64.pyd +0 -0
  170. sage/misc/persist.pyx +1251 -0
  171. sage/misc/prandom.py +418 -0
  172. sage/misc/randstate.cp312-win_amd64.pyd +0 -0
  173. sage/misc/randstate.pxd +30 -0
  174. sage/misc/randstate.pyx +1059 -0
  175. sage/misc/repr.py +203 -0
  176. sage/misc/reset.cp312-win_amd64.pyd +0 -0
  177. sage/misc/reset.pyx +196 -0
  178. sage/misc/sage_ostools.cp312-win_amd64.pyd +0 -0
  179. sage/misc/sage_ostools.pyx +323 -0
  180. sage/misc/sage_timeit.py +276 -0
  181. sage/misc/sage_timeit_class.cp312-win_amd64.pyd +0 -0
  182. sage/misc/sage_timeit_class.pyx +120 -0
  183. sage/misc/sage_unittest.py +637 -0
  184. sage/misc/sageinspect.py +2768 -0
  185. sage/misc/session.cp312-win_amd64.pyd +0 -0
  186. sage/misc/session.pyx +392 -0
  187. sage/misc/superseded.py +557 -0
  188. sage/misc/test_nested_class.py +228 -0
  189. sage/misc/timing.py +264 -0
  190. sage/misc/unknown.py +222 -0
  191. sage/misc/verbose.py +253 -0
  192. sage/misc/weak_dict.cp312-win_amd64.pyd +0 -0
  193. sage/misc/weak_dict.pxd +15 -0
  194. sage/misc/weak_dict.pyx +1231 -0
  195. sage/modules/all__sagemath_objects.py +1 -0
  196. sage/modules/module.cp312-win_amd64.pyd +0 -0
  197. sage/modules/module.pxd +5 -0
  198. sage/modules/module.pyx +329 -0
  199. sage/rings/all__sagemath_objects.py +3 -0
  200. sage/rings/integer_fake.h +22 -0
  201. sage/rings/integer_fake.pxd +55 -0
  202. sage/sets/all__sagemath_objects.py +3 -0
  203. sage/sets/pythonclass.cp312-win_amd64.pyd +0 -0
  204. sage/sets/pythonclass.pxd +9 -0
  205. sage/sets/pythonclass.pyx +247 -0
  206. sage/structure/__init__.py +13 -0
  207. sage/structure/all.py +30 -0
  208. sage/structure/category_object.cp312-win_amd64.pyd +0 -0
  209. sage/structure/category_object.pxd +28 -0
  210. sage/structure/category_object.pyx +1087 -0
  211. sage/structure/coerce.cp312-win_amd64.pyd +0 -0
  212. sage/structure/coerce.pxd +44 -0
  213. sage/structure/coerce.pyx +2107 -0
  214. sage/structure/coerce_actions.cp312-win_amd64.pyd +0 -0
  215. sage/structure/coerce_actions.pxd +27 -0
  216. sage/structure/coerce_actions.pyx +988 -0
  217. sage/structure/coerce_dict.cp312-win_amd64.pyd +0 -0
  218. sage/structure/coerce_dict.pxd +51 -0
  219. sage/structure/coerce_dict.pyx +1557 -0
  220. sage/structure/coerce_exceptions.py +23 -0
  221. sage/structure/coerce_maps.cp312-win_amd64.pyd +0 -0
  222. sage/structure/coerce_maps.pxd +28 -0
  223. sage/structure/coerce_maps.pyx +718 -0
  224. sage/structure/debug_options.cp312-win_amd64.pyd +0 -0
  225. sage/structure/debug_options.pxd +6 -0
  226. sage/structure/debug_options.pyx +54 -0
  227. sage/structure/dynamic_class.py +541 -0
  228. sage/structure/element.cp312-win_amd64.pyd +0 -0
  229. sage/structure/element.pxd +272 -0
  230. sage/structure/element.pyx +4772 -0
  231. sage/structure/element_wrapper.cp312-win_amd64.pyd +0 -0
  232. sage/structure/element_wrapper.pxd +12 -0
  233. sage/structure/element_wrapper.pyx +582 -0
  234. sage/structure/factorization.py +1422 -0
  235. sage/structure/factorization_integer.py +105 -0
  236. sage/structure/factory.cp312-win_amd64.pyd +0 -0
  237. sage/structure/factory.pyx +786 -0
  238. sage/structure/formal_sum.py +489 -0
  239. sage/structure/gens_py.py +73 -0
  240. sage/structure/global_options.py +1743 -0
  241. sage/structure/indexed_generators.py +863 -0
  242. sage/structure/list_clone.cp312-win_amd64.pyd +0 -0
  243. sage/structure/list_clone.pxd +65 -0
  244. sage/structure/list_clone.pyx +1867 -0
  245. sage/structure/list_clone_demo.cp312-win_amd64.pyd +0 -0
  246. sage/structure/list_clone_demo.pyx +248 -0
  247. sage/structure/list_clone_timings.py +179 -0
  248. sage/structure/list_clone_timings_cy.cp312-win_amd64.pyd +0 -0
  249. sage/structure/list_clone_timings_cy.pyx +86 -0
  250. sage/structure/mutability.cp312-win_amd64.pyd +0 -0
  251. sage/structure/mutability.pxd +21 -0
  252. sage/structure/mutability.pyx +348 -0
  253. sage/structure/nonexact.py +69 -0
  254. sage/structure/parent.cp312-win_amd64.pyd +0 -0
  255. sage/structure/parent.pxd +112 -0
  256. sage/structure/parent.pyx +3093 -0
  257. sage/structure/parent_base.cp312-win_amd64.pyd +0 -0
  258. sage/structure/parent_base.pxd +13 -0
  259. sage/structure/parent_base.pyx +44 -0
  260. sage/structure/parent_gens.cp312-win_amd64.pyd +0 -0
  261. sage/structure/parent_gens.pxd +22 -0
  262. sage/structure/parent_gens.pyx +377 -0
  263. sage/structure/parent_old.cp312-win_amd64.pyd +0 -0
  264. sage/structure/parent_old.pxd +25 -0
  265. sage/structure/parent_old.pyx +294 -0
  266. sage/structure/proof/__init__.py +1 -0
  267. sage/structure/proof/all.py +243 -0
  268. sage/structure/proof/proof.py +300 -0
  269. sage/structure/richcmp.cp312-win_amd64.pyd +0 -0
  270. sage/structure/richcmp.pxd +213 -0
  271. sage/structure/richcmp.pyx +495 -0
  272. sage/structure/sage_object.cp312-win_amd64.pyd +0 -0
  273. sage/structure/sage_object.pxd +3 -0
  274. sage/structure/sage_object.pyx +988 -0
  275. sage/structure/sage_object_test.py +19 -0
  276. sage/structure/sequence.py +937 -0
  277. sage/structure/set_factories.py +1178 -0
  278. sage/structure/set_factories_example.py +527 -0
  279. sage/structure/support_view.py +179 -0
  280. sage/structure/test_factory.py +56 -0
  281. sage/structure/unique_representation.py +1359 -0
@@ -0,0 +1,1402 @@
1
+ # sage_setup: distribution = sagemath-objects
2
+ """
3
+ The C3 algorithm, under control of a total order
4
+
5
+ Abstract
6
+ ========
7
+
8
+ Python handles multiple inheritance by computing, for each class,
9
+ a linear extension of the poset of all its super classes (the Method
10
+ Resolution Order, MRO). The MRO is calculated recursively from local
11
+ information (the *ordered* list of the direct super classes), with
12
+ the so-called ``C3`` algorithm. This algorithm can fail if the local
13
+ information is not consistent; worst, there exist hierarchies of
14
+ classes with provably no consistent local information.
15
+
16
+ For large hierarchy of classes, like those derived from categories in
17
+ Sage, maintaining consistent local information by hand does not scale
18
+ and leads to unpredictable ``C3`` failures (the dreaded "could not
19
+ find a consistent method resolution order"); a maintenance nightmare.
20
+
21
+ This module implements a final solution to this problem. Namely, it
22
+ allows for building automatically the local information from the bare
23
+ class hierarchy in such a way that guarantees that the ``C3``
24
+ algorithm will never fail.
25
+
26
+ Err, but you said that this was provably impossible? Well, not if one
27
+ relaxes a bit the hypotheses; but that's not something one would want
28
+ to do by hand :-)
29
+
30
+ The problem
31
+ ===========
32
+
33
+ Consider the following hierarchy of classes::
34
+
35
+ sage: class A1(): pass
36
+ sage: class A2():
37
+ ....: def foo(self): return 2
38
+ sage: class A3(): pass
39
+ sage: class A4():
40
+ ....: def foo(self): return 4
41
+ sage: class A5(A2, A1):
42
+ ....: def foo(self): return 5
43
+ sage: class A6(A4, A3): pass
44
+ sage: class A7(A6, A5): pass
45
+
46
+ If ``a`` is an instance of ``A7``, then Python needs to choose which
47
+ implementation to use upon calling ``a.foo()``: that of ``A4`` or
48
+ ``A5``, but obviously not that of ``A2``. In Python, like in many
49
+ other dynamic object oriented languages, this is achieved by
50
+ calculating once for all a specific linear extension of the hierarchy
51
+ of the super classes of each class, called its Method Resolution Order
52
+ (MRO)::
53
+
54
+ sage: [cls.__name__ for cls in A7.mro()]
55
+ ['A7', 'A6', 'A4', 'A3', 'A5', 'A2', 'A1', 'object']
56
+
57
+ Thus, in our example, the implementation in ``A4`` is chosen::
58
+
59
+ sage: a = A7()
60
+ sage: a.foo()
61
+ 4
62
+
63
+ Specifically, the MRO is calculated using the so-called ``C3``
64
+ algorithm which guarantees that the MRO respects not only inheritance,
65
+ but also the order in which the bases (direct super classes) are given
66
+ for each class.
67
+
68
+ However, for large hierarchies of classes with lots of multiple
69
+ inheritance, like those derived from categories in Sage, this
70
+ algorithm easily fails if the order of the bases is not chosen
71
+ consistently (here for ``A2`` w.r.t. ``A1``)::
72
+
73
+ sage: class B6(A1,A2): pass
74
+ sage: class B7(B6,A5): pass
75
+ Traceback (most recent call last):
76
+ ...
77
+ TypeError: Cannot create a consistent method resolution
78
+ order (MRO) for bases A1, A2
79
+
80
+ There actually exist hierarchies of classes for which ``C3`` fails
81
+ whatever order of the bases is chosen; the smallest such example,
82
+ admittedly artificial, has ten classes (see below). Still, this
83
+ highlights that this problem has to be tackled in a systematic way.
84
+
85
+ Fortunately, one can trick ``C3``, without changing the inheritance
86
+ semantic, by adding some super classes of ``A`` to the bases of
87
+ ``A``. In the following example, we completely force a given MRO by
88
+ specifying *all* the super classes of ``A`` as bases::
89
+
90
+ sage: class A7(A6, A5, A4, A3, A2, A1): pass
91
+ sage: [cls.__name__ for cls in A7.mro()]
92
+ ['A7', 'A6', 'A5', 'A4', 'A3', 'A2', 'A1', 'object']
93
+
94
+ Luckily this can be optimized; here it is sufficient to add a single
95
+ base to enforce the same MRO::
96
+
97
+ sage: class A7(A6, A5, A4): pass
98
+ sage: [cls.__name__ for cls in A7.mro()]
99
+ ['A7', 'A6', 'A5', 'A4', 'A3', 'A2', 'A1', 'object']
100
+
101
+ A strategy to solve the problem
102
+ ===============================
103
+
104
+ We should recall at this point a design decision that we took for the
105
+ hierarchy of classes derived from categories: *the semantic shall only
106
+ depend on the inheritance order*, not on the specific MRO, and in
107
+ particular not on the order of the bases (see
108
+ :ref:`On the order of super categories <category-primer-category-order>`).
109
+
110
+ If a choice needs to be made (for example for efficiency reasons),
111
+ then this should be done explicitly, on a method-by-method basis. In
112
+ practice this design goal is not yet met.
113
+
114
+ .. NOTE::
115
+
116
+ When managing large hierarchies of classes in other contexts this
117
+ may be too strong a design decision.
118
+
119
+ The strategy we use for hierarchies of classes derived from categories
120
+ is then:
121
+
122
+ 1. To choose a global total order on the whole hierarchy of classes.
123
+ 2. To control ``C3`` to get it to return MROs that follow this total order.
124
+
125
+ A basic approach for point 1., that will work for any hierarchy of
126
+ classes, is to enumerate the classes while they are constructed
127
+ (making sure that the bases of each class are enumerated before that
128
+ class), and to order the classes according to that enumeration. A more
129
+ conceptual ordering may be desirable, in particular to get
130
+ deterministic and reproducible results. In the context of Sage, this
131
+ is mostly relevant for those doctests displaying all the categories or
132
+ classes that an object inherits from.
133
+
134
+ Getting fine control on C3
135
+ ==========================
136
+
137
+ This module is about point 2.
138
+
139
+ The natural approach would be to change the algorithm used by Python
140
+ to compute the MRO. However, changing Python's default algorithm just
141
+ for our needs is obviously not an option, and there is currently no
142
+ hook to customize specific classes to use a different algorithm.
143
+ Pushing the addition of such a hook into stock Python would take too
144
+ much time and effort.
145
+
146
+ Another approach would be to use the "adding bases" trick
147
+ straightforwardly, putting the list of *all* the super classes of a
148
+ class as its bases. However, this would have several drawbacks:
149
+
150
+ - It is not so elegant, in particular because it duplicates
151
+ information: we already know through ``A5`` that ``A7`` is a
152
+ subclass of ``A1``. This duplication could be acceptable in our
153
+ context because the hierarchy of classes is generated automatically
154
+ from a conceptual hierarchy (the categories) which serves as single
155
+ point of truth for calculating the bases of each class.
156
+
157
+ - It increases the complexity of the calculation of the MRO with
158
+ ``C3``. For example, for a linear hierarchy of classes, the
159
+ complexity goes from `O(n^2)` to `O(n^3)` which is not acceptable.
160
+
161
+ - It increases the complexity of inspecting the classes. For example,
162
+ the current implementation of the ``dir`` command in Python has no
163
+ cache, and its complexity is linear in the number of maximal paths
164
+ in the class hierarchy graph as defined by the bases. For a linear
165
+ hierarchy, this is of complexity `O(p_n)` where `p_n` is the number
166
+ of integer partitions of `n`, which is exponential. And indeed,
167
+ running ``dir`` for a typical class like
168
+ ``GradedHopfAlgebrasWithBasis(QQ).parent_class`` with ``37`` super
169
+ classes took `18` seconds with this approach.
170
+
171
+ Granted: this mostly affects the ``dir`` command and could be blamed
172
+ on its current implementation. With appropriate caching, it could be
173
+ reimplemented to have a complexity roughly linear in the number of
174
+ classes in the hierarchy. But this won't happen any time soon in a
175
+ stock Python.
176
+
177
+ This module refines this approach to make it acceptable, if not
178
+ seamless. Given a hierarchy and a total order on this hierarchy, it
179
+ calculates for each element of the hierarchy the smallest list of
180
+ additional bases that forces ``C3`` to return the desired MRO. This is
181
+ achieved by implementing an instrumented variant of the ``C3``
182
+ algorithm (which we call *instrumented* ``C3``) that detects when
183
+ ``C3`` is about to take a wrong decision and adds one base to force
184
+ the right decision. Then, running the standard ``C3`` algorithm with
185
+ the updated list of bases (which we call *controlled* ``C3``) yields
186
+ the desired MRO.
187
+
188
+ EXAMPLES:
189
+
190
+ As an experimentation and testing tool, we use a class
191
+ :class:`HierarchyElement` whose instances can be constructed from a
192
+ hierarchy described by a poset, a digraph, or more generally a
193
+ successor relation. By default, the desired MRO is sorted
194
+ decreasingly. Another total order can be specified using a sorting
195
+ key.
196
+
197
+ We consider the smallest poset describing a class hierarchy admitting
198
+ no MRO whatsoever::
199
+
200
+ sage: P = Poset({10: [9,8,7], 9: [6,1], 8: [5,2], 7: [4,3], # needs sage.graphs
201
+ ....: 6: [3,2], 5: [3,1], 4: [2,1]},
202
+ ....: linear_extension=True, facade=True)
203
+
204
+ And build a :class:`HierarchyElement` from it::
205
+
206
+ sage: from sage.misc.c3_controlled import HierarchyElement
207
+ sage: x = HierarchyElement(10, P) # needs sage.graphs
208
+
209
+ Here are its bases::
210
+
211
+ sage: HierarchyElement(10, P)._bases # needs sage.graphs
212
+ [9, 8, 7]
213
+
214
+ Using the standard ``C3`` algorithm fails::
215
+
216
+ sage: x.mro_standard # needs sage.graphs
217
+ Traceback (most recent call last):
218
+ ...
219
+ ValueError: Cannot merge the items 3, 3, 2.
220
+
221
+ We also get a failure when we relabel `P` according to another linear
222
+ extension. For easy relabelling, we first need to set an appropriate
223
+ default linear extension for `P`::
224
+
225
+ sage: linear_extension = list(reversed(IntegerRange(1, 11)))
226
+ sage: P = P.with_linear_extension(linear_extension) # needs sage.graphs
227
+ sage: list(P) # needs sage.graphs
228
+ [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
229
+
230
+ Now we play with a specific linear extension of `P`::
231
+
232
+ sage: # needs sage.graphs
233
+ sage: Q = P.linear_extension([10, 9, 8, 7, 6, 5, 4, 1, 2, 3]).to_poset()
234
+ sage: Q.cover_relations()
235
+ [[10, 9], [10, 8], [10, 7], [9, 6], [9, 3], [8, 5], [8, 2], [7, 4],
236
+ [7, 1], [6, 2], [6, 1], [5, 3], [5, 1], [4, 3], [4, 2]]
237
+ sage: x = HierarchyElement(10, Q)
238
+ sage: x.mro_standard
239
+ Traceback (most recent call last):
240
+ ...
241
+ ValueError: Cannot merge the items 2, 3, 3.
242
+
243
+ On the other hand, both the instrumented ``C3`` algorithm, and the
244
+ controlled ``C3`` algorithm give the desired MRO::
245
+
246
+ sage: x.mro # needs sage.graphs
247
+ [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
248
+ sage: x.mro_controlled # needs sage.graphs
249
+ [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
250
+
251
+ The above checks, and more, can be run with::
252
+
253
+ sage: x._test_mro() # needs sage.graphs
254
+
255
+ In practice, the control was achieved by adding the following bases::
256
+
257
+ sage: x._bases # needs sage.graphs
258
+ [9, 8, 7]
259
+ sage: x._bases_controlled # needs sage.graphs
260
+ [9, 8, 7, 6, 5]
261
+
262
+ Altogether, four bases were added for control::
263
+
264
+ sage: sum(len(HierarchyElement(q, Q)._bases) for q in Q) # needs sage.graphs
265
+ 15
266
+ sage: sum(len(HierarchyElement(q, Q)._bases_controlled) for q in Q) # needs sage.graphs
267
+ 19
268
+
269
+ This information can also be recovered with::
270
+
271
+ sage: x.all_bases_len() # needs sage.graphs
272
+ 15
273
+ sage: x.all_bases_controlled_len() # needs sage.graphs
274
+ 19
275
+
276
+ We now check that the ``C3`` algorithm fails for all linear extensions
277
+ `l` of this poset, whereas both the instrumented and controlled ``C3``
278
+ algorithms succeed; along the way, we collect some statistics::
279
+
280
+ sage: L = P.linear_extensions() # needs sage.graphs
281
+ sage: stats = []
282
+ sage: for l in L: # needs sage.graphs sage.modules
283
+ ....: x = HierarchyElement(10, l.to_poset())
284
+ ....: try: # Check that x.mro_standard always fails with a ValueError
285
+ ....: x.mro_standard
286
+ ....: except ValueError:
287
+ ....: pass
288
+ ....: else:
289
+ ....: assert False
290
+ ....: assert x.mro == list(P)
291
+ ....: assert x.mro_controlled == list(P)
292
+ ....: assert x.all_bases_len() == 15
293
+ ....: stats.append(x.all_bases_controlled_len()-x.all_bases_len())
294
+
295
+ Depending on the linear extension `l` it was necessary to add between
296
+ one and five bases for control; for example, `216` linear extensions
297
+ required the addition of four bases::
298
+
299
+ sage: sorted(Word(stats).evaluation_sparse()) # needs sage.combinat sage.graphs sage.modules
300
+ [(1, 36), (2, 108), (3, 180), (4, 216), (5, 180)]
301
+
302
+ We now consider a hierarchy of categories::
303
+
304
+ sage: from operator import attrgetter
305
+ sage: x = HierarchyElement(Groups(), attrcall("super_categories"), attrgetter("_cmp_key"))
306
+ sage: x.mro
307
+ [Category of groups, Category of monoids, Category of semigroups,
308
+ Category of inverse unital magmas, Category of unital magmas, Category of magmas,
309
+ Category of sets, Category of sets with partial maps, Category of objects]
310
+ sage: x.mro_standard
311
+ [Category of groups, Category of monoids, Category of semigroups,
312
+ Category of inverse unital magmas, Category of unital magmas, Category of magmas,
313
+ Category of sets, Category of sets with partial maps, Category of objects]
314
+
315
+ For a typical category, few bases, if any, need to be added to force
316
+ ``C3`` to give the desired order::
317
+
318
+ sage: C = FiniteFields()
319
+ sage: x = HierarchyElement(C, attrcall("super_categories"), attrgetter("_cmp_key"))
320
+ sage: x.mro == x.mro_standard
321
+ False
322
+ sage: x.all_bases_len()
323
+ 72
324
+ sage: x.all_bases_controlled_len()
325
+ 76
326
+
327
+ sage: C = GradedHopfAlgebrasWithBasis(QQ)
328
+ sage: x = HierarchyElement(C, attrcall("super_categories"), attrgetter("_cmp_key"))
329
+ sage: x._test_mro()
330
+ sage: x.mro == x.mro_standard
331
+ False
332
+ sage: x.all_bases_len()
333
+ 114
334
+ sage: x.all_bases_controlled_len()
335
+ 117
336
+
337
+ The following can be used to search through the Sage named categories
338
+ for any that requires the addition of some bases. The output may
339
+ change a bit when the category hierarchy is changed. As long as the
340
+ list below does not change radically, it's fine to just update this
341
+ doctest::
342
+
343
+ sage: from sage.categories.category import category_sample
344
+ sage: sorted([C for C in category_sample() # needs sage.combinat sage.graphs sage.modules sage.rings.number_field
345
+ ....: if len(C._super_categories_for_classes) != len(C.super_categories())],
346
+ ....: key=str)
347
+ [Category of affine Weyl groups,
348
+ Category of fields,
349
+ Category of finite Weyl groups,
350
+ Category of finite dimensional Hopf algebras with basis over Rational Field,
351
+ Category of finite dimensional algebras with basis over Rational Field,
352
+ Category of finite enumerated permutation groups,
353
+ Category of number fields]
354
+
355
+ AUTHOR:
356
+
357
+ - Nicolas M. Thiery (2012-09): initial version.
358
+ """
359
+ # ****************************************************************************
360
+ # Copyright (C) 2012-2013 Nicolas M. Thiery <nthiery at users.sf.net>
361
+ #
362
+ # Distributed under the terms of the GNU General Public License (GPL)
363
+ # https://www.gnu.org/licenses/
364
+ # *****************************************************************************
365
+
366
+ from sage.misc.classcall_metaclass import ClasscallMetaclass, typecall
367
+ from sage.misc.cachefunc import cached_function, cached_method
368
+ from sage.misc.lazy_attribute import lazy_attribute
369
+ from sage.structure.dynamic_class import dynamic_class
370
+
371
+ ##############################################################################
372
+ # Implementation of the total order between categories
373
+ ##############################################################################
374
+
375
+ cdef tuple atoms = ("FacadeSets",
376
+ "FiniteSets", "Sets.Infinite", "EnumeratedSets", "SetsWithGrading",
377
+ "Posets", "LatticePosets", "Crystals", "AdditiveMagmas",
378
+ "FiniteDimensionalModules", "GradedModules", "ModulesWithBasis",
379
+ "Magmas", "Semigroups", "Monoids", "PermutationGroups",
380
+ "MagmasAndAdditiveMagmas", "Rngs", "Domains", "HopfAlgebras")
381
+
382
+
383
+ cdef dict flags = {atom: 1 << i for i, atom in enumerate(atoms)}
384
+
385
+ cdef class CmpKey:
386
+ r"""
387
+ This class implements the lazy attribute ``Category._cmp_key``.
388
+
389
+ The comparison key ``A._cmp_key`` of a category is used to define
390
+ an (almost) total order on non-join categories by setting, for two
391
+ categories `A` and `B`, `A<B` if ``A._cmp_key > B._cmp_key``. This
392
+ order in turn is used to give a normal form to join's, and help
393
+ toward having a consistent method resolution order for
394
+ parent/element classes.
395
+
396
+ The comparison key should satisfy the following properties:
397
+
398
+ - If `A` is a subcategory of `B`, then `A < B` (so that
399
+ ``A._cmp_key > B._cmp_key``). In particular,
400
+ :class:`Objects() <Objects>` is the largest category.
401
+
402
+ - If `A != B` and taking the join of `A` and `B` makes sense
403
+ (e.g. taking the join of ``Algebras(GF(5))`` and
404
+ ``Algebras(QQ)`` does not make sense), then `A<B` or `B<A`.
405
+
406
+ The rationale for the inversion above between `A<B` and
407
+ ``A._cmp_key > B._cmp_key`` is that we want the order to
408
+ be compatible with inclusion of categories, yet it's easier in
409
+ practice to create keys that get bigger and bigger while we go
410
+ down the category hierarchy.
411
+
412
+ This implementation applies to join-irreducible categories
413
+ (i.e. categories that are not join categories). It returns a
414
+ pair of integers ``(flags, i)``, where ``flags`` is to be
415
+ interpreted as a bit vector. The first bit is set if ``self``
416
+ is a facade set. The second bit is set if ``self`` is finite.
417
+ And so on. The choice of the flags is adhoc and was primarily
418
+ crafted so that the order between categories would not change
419
+ too much upon integration of :issue:`13589` and would be
420
+ reasonably session independent. The number ``i`` is there
421
+ to resolve ambiguities; it is session dependent, and is
422
+ assigned increasingly when new categories are created.
423
+
424
+ .. NOTE::
425
+
426
+ This is currently not implemented using a
427
+ :class:`lazy_attribute` for speed reasons only (the code is in
428
+ Cython and takes advantage of the fact that Category objects
429
+ always have a ``__dict__`` dictionary)
430
+
431
+ .. TODO::
432
+
433
+ - Handle nicely (covariant) functorial constructions and axioms
434
+
435
+ EXAMPLES::
436
+
437
+ sage: Objects()._cmp_key
438
+ (0, 0)
439
+ sage: SetsWithPartialMaps()._cmp_key
440
+ (0, 1)
441
+ sage: Sets()._cmp_key
442
+ (0, 2)
443
+ sage: Sets().Facade()._cmp_key
444
+ (1, ...)
445
+ sage: Sets().Finite()._cmp_key
446
+ (2, ...)
447
+ sage: Sets().Infinite()._cmp_key
448
+ (4, ...)
449
+ sage: EnumeratedSets()._cmp_key
450
+ (8, ...)
451
+ sage: FiniteEnumeratedSets()._cmp_key
452
+ (10, ...)
453
+ sage: SetsWithGrading()._cmp_key
454
+ (16, ...)
455
+ sage: Posets()._cmp_key
456
+ (32, ...)
457
+ sage: LatticePosets()._cmp_key
458
+ (96, ...)
459
+ sage: Crystals()._cmp_key
460
+ (136, ...)
461
+ sage: AdditiveMagmas()._cmp_key
462
+ (256, ...)
463
+ sage: Magmas()._cmp_key
464
+ (4096, ...)
465
+ sage: CommutativeAdditiveSemigroups()._cmp_key
466
+ (256, ...)
467
+ sage: Rings()._cmp_key
468
+ (225536, ...)
469
+ sage: Algebras(QQ)._cmp_key
470
+ (225536, ...)
471
+ sage: AlgebrasWithBasis(QQ)._cmp_key
472
+ (227584, ...)
473
+ sage: GradedAlgebras(QQ)._cmp_key
474
+ (226560, ...)
475
+ sage: GradedAlgebrasWithBasis(QQ)._cmp_key
476
+ (228608, ...)
477
+
478
+ For backward compatibility we currently want the following comparisons::
479
+
480
+ sage: EnumeratedSets()._cmp_key > Sets().Facade()._cmp_key
481
+ True
482
+ sage: AdditiveMagmas()._cmp_key > EnumeratedSets()._cmp_key
483
+ True
484
+
485
+ sage: Category.join([EnumeratedSets(), Sets().Facade()]).parent_class._an_element_.__module__
486
+ 'sage.categories.enumerated_sets'
487
+
488
+ sage: CommutativeAdditiveSemigroups()._cmp_key < Magmas()._cmp_key
489
+ True
490
+ sage: VectorSpaces(QQ)._cmp_key < Rings()._cmp_key
491
+ True
492
+ sage: VectorSpaces(QQ)._cmp_key < Magmas()._cmp_key
493
+ True
494
+ """
495
+ cdef int count
496
+
497
+ def __init__(self):
498
+ """
499
+ Set the internal category counter to zero.
500
+
501
+ EXAMPLES::
502
+
503
+ sage: Objects()._cmp_key # indirect doctest
504
+ (0, 0)
505
+ """
506
+ self.count = -1
507
+
508
+ def __get__(self, object inst, object cls):
509
+ """
510
+ Bind the comparison key to the given instance.
511
+
512
+ EXAMPLES::
513
+
514
+ sage: C = Algebras(FractionField(QQ['x']))
515
+ sage: C._cmp_key
516
+ (225536, ...)
517
+ sage: '_cmp_key' in C.__dict__ # indirect doctest
518
+ True
519
+ """
520
+ # assert not isinstance(inst, JoinCategory)
521
+ # Note that cls is a DynamicClassMetaclass, hence not a type
522
+ cdef str classname = cls.__base__.__name__
523
+ cdef int flag = flags.get(classname, 0)
524
+ cdef object cat
525
+ for cat in inst._super_categories:
526
+ flag = flag | <int>(<tuple>(cat._cmp_key)[0])
527
+ self.count += 1
528
+ inst._cmp_key = (flag, self.count)
529
+ return flag, self.count
530
+
531
+ _cmp_key = CmpKey()
532
+
533
+
534
+ cdef class CmpKeyNamed:
535
+ """
536
+ This class implements the lazy attribute ``CategoryWithParameters._cmp_key``.
537
+
538
+ .. SEEALSO::
539
+
540
+ - :class:`CmpKey`
541
+ - :class:`lazy_attribute`
542
+ - :class:`sage.categories.category.CategoryWithParameters`.
543
+
544
+ .. NOTE::
545
+
546
+ - The value of the attribute depends only on the parameters of
547
+ this category.
548
+
549
+ - This is currently not implemented using a
550
+ :class:`lazy_attribute` for speed reasons only.
551
+
552
+ EXAMPLES::
553
+
554
+ sage: Algebras(GF(3))._cmp_key == Algebras(GF(5))._cmp_key # indirect doctest
555
+ True
556
+ sage: Algebras(ZZ)._cmp_key != Algebras(GF(5))._cmp_key
557
+ True
558
+ """
559
+ def __get__(self, object inst, object cls):
560
+ """
561
+ EXAMPLES::
562
+
563
+ sage: Algebras(GF(3))._cmp_key == Algebras(GF(5))._cmp_key # indirect doctest
564
+ True
565
+ sage: Algebras(ZZ)._cmp_key != Algebras(GF(5))._cmp_key
566
+ True
567
+ """
568
+ cdef dict D = cls._make_named_class_cache
569
+ cdef str name = "_cmp_key"
570
+ cdef tuple key = (cls.__base__, name, inst._make_named_class_key(name))
571
+ try:
572
+ result = D[key]
573
+ inst._cmp_key = result
574
+ return result
575
+ except KeyError:
576
+ pass
577
+ result = _cmp_key.__get__(inst, cls)
578
+ D[key] = result
579
+ return result
580
+
581
+ _cmp_key_named = CmpKeyNamed()
582
+
583
+
584
+ ##############################################################################
585
+
586
+ def C3_merge(list lists):
587
+ r"""
588
+ Return the input lists merged using the ``C3`` algorithm.
589
+
590
+ EXAMPLES::
591
+
592
+ sage: from sage.misc.c3_controlled import C3_merge
593
+ sage: C3_merge([[3,2],[4,3,1]])
594
+ [4, 3, 2, 1]
595
+ sage: C3_merge([[3,2],[4,1]])
596
+ [3, 2, 4, 1]
597
+
598
+ This function is only used for testing and experimenting purposes,
599
+ but exercised quite some by the other doctests in this file.
600
+
601
+ It is an extract of :func:`sage.misc.c3.C3_algorithm`; the latter
602
+ could be possibly rewritten to use this one to avoid duplication.
603
+ """
604
+ cdef list out = []
605
+ # Data structure / invariants:
606
+ # We will be working with the MROs of the super objects
607
+ # together with the list of bases of ``self``.
608
+ # Each list is split between its head (in ``heads``) and tail (in
609
+ # ``tails'') . Each tail is stored reversed, so that we can use a
610
+ # cheap pop() in lieue of pop(0). A duplicate of the tail is
611
+ # stored as a set in ``tailsets`` for cheap membership testing.
612
+ # Since we actually want comparison by identity, not equality,
613
+ # what we store is the set of memory locations of objects
614
+ cdef object O, X
615
+ cdef list tail, l
616
+ cdef set tailset
617
+
618
+ cdef list tails = [l[::-1] for l in lists if l]
619
+ cdef list heads = [tail.pop() for tail in tails]
620
+ cdef list tailsets = [set(O for O in tail) for tail in tails] # <size_t><void *>
621
+
622
+ cdef int i, j, nbheads
623
+ nbheads = len(heads)
624
+ cdef bint next_item_found
625
+
626
+ while nbheads:
627
+ for i in range(nbheads):
628
+ O = heads[i]
629
+ # Does O appear in none of the tails? ``all(O not in tail for tail in tailsets)``
630
+ next_item_found = True
631
+ for j in range(nbheads):
632
+ if j == i:
633
+ continue
634
+ tailset = tailsets[j]
635
+ if O in tailset: # <size_t><void *>O
636
+ next_item_found = False
637
+ break
638
+ if next_item_found:
639
+ out.append(O)
640
+ # Clear O from other heads, removing the line altogether
641
+ # if the tail is already empty.
642
+ # j goes down so that ``del heads[j]`` does not screw up the numbering
643
+ for j in range(nbheads-1, -1, -1):
644
+ if heads[j] == O: # is O
645
+ tail = tails[j]
646
+ if tail:
647
+ X = tail.pop()
648
+ heads[j] = X
649
+ tailset = tailsets[j]
650
+ tailset.remove(X) # <size_t><void *>X)
651
+ else:
652
+ del heads[j]
653
+ del tails[j]
654
+ del tailsets[j]
655
+ nbheads -= 1
656
+ break
657
+ if not next_item_found:
658
+ # No head is available
659
+ raise ValueError("Cannot merge the items %s." % ', '.join(repr(head) for head in heads))
660
+ return out
661
+
662
+
663
+ cpdef identity(x):
664
+ r"""
665
+ EXAMPLES::
666
+
667
+ sage: from sage.misc.c3_controlled import identity
668
+ sage: identity(10)
669
+ 10
670
+ """
671
+ return x
672
+
673
+ cpdef tuple C3_sorted_merge(list lists, key=identity):
674
+ r"""
675
+ Return the sorted input lists merged using the ``C3`` algorithm, with a twist.
676
+
677
+ INPUT:
678
+
679
+ - ``lists`` -- a non empty list (or iterable) of lists (or
680
+ iterables), each sorted strictly decreasingly according
681
+ to ``key``
682
+ - ``key`` -- a function
683
+
684
+ OUTPUT: a pair ``(result, suggestion)``
685
+
686
+ ``result`` is the sorted list obtained by merging the lists in
687
+ ``lists`` while removing duplicates, and ``suggestion`` is a list
688
+ such that applying ``C3`` on ``lists`` with its last list replaced
689
+ by ``suggestion`` would return ``result``.
690
+
691
+ EXAMPLES:
692
+
693
+ With the following input, :func:`C3_merge` returns right away a
694
+ sorted list::
695
+
696
+ sage: from sage.misc.c3_controlled import C3_merge
697
+ sage: C3_merge([[2],[1]])
698
+ [2, 1]
699
+
700
+ In that case, :func:`C3_sorted_merge` returns the same result,
701
+ with the last line unchanged::
702
+
703
+ sage: from sage.misc.c3_controlled import C3_sorted_merge
704
+ sage: C3_sorted_merge([[2],[1]])
705
+ ([2, 1], [1])
706
+
707
+ On the other hand, with the following input, :func:`C3_merge`
708
+ returns a non sorted list::
709
+
710
+ sage: C3_merge([[1],[2]])
711
+ [1, 2]
712
+
713
+ Then, :func:`C3_sorted_merge` returns a sorted list, and suggests
714
+ to replace the last line by ``[2,1]``::
715
+
716
+ sage: C3_sorted_merge([[1],[2]])
717
+ ([2, 1], [2, 1])
718
+
719
+ And indeed :func:`C3_merge` now returns the desired result::
720
+
721
+ sage: C3_merge([[1],[2,1]])
722
+ [2, 1]
723
+
724
+ From now on, we use this little wrapper that checks that
725
+ :func:`C3_merge`, with the suggestion of :func:`C3_sorted_merge`,
726
+ returns a sorted list::
727
+
728
+ sage: def C3_sorted_merge_check(lists):
729
+ ....: result, suggestion = C3_sorted_merge(lists)
730
+ ....: assert result == C3_merge(lists[:-1] + [suggestion])
731
+ ....: return result, suggestion
732
+
733
+ Base cases::
734
+
735
+ sage: C3_sorted_merge_check([])
736
+ Traceback (most recent call last):
737
+ ...
738
+ ValueError: The input should be a non empty list of lists (or iterables)
739
+ sage: C3_sorted_merge_check([[]])
740
+ ([], [])
741
+ sage: C3_sorted_merge_check([[1]])
742
+ ([1], [1])
743
+ sage: C3_sorted_merge_check([[3,2,1]])
744
+ ([3, 2, 1], [3, 2, 1])
745
+ sage: C3_sorted_merge_check([[1],[1]])
746
+ ([1], [1])
747
+ sage: C3_sorted_merge_check([[3,2,1],[3,2,1]])
748
+ ([3, 2, 1], [3, 2, 1])
749
+
750
+ Exercise different states for the last line::
751
+
752
+ sage: C3_sorted_merge_check([[1],[2],[]])
753
+ ([2, 1], [2, 1])
754
+ sage: C3_sorted_merge_check([[1],[2], [1]])
755
+ ([2, 1], [2, 1])
756
+
757
+ Explore (all?) the different execution branches::
758
+
759
+ sage: C3_sorted_merge_check([[3,1],[4,2]])
760
+ ([4, 3, 2, 1], [4, 3, 2, 1])
761
+ sage: C3_sorted_merge_check([[4,1],[3,2]])
762
+ ([4, 3, 2, 1], [3, 2, 1])
763
+ sage: C3_sorted_merge_check([[3,2],[4,1]])
764
+ ([4, 3, 2, 1], [4, 3, 1])
765
+ sage: C3_sorted_merge_check([[1],[4,3,2]])
766
+ ([4, 3, 2, 1], [4, 3, 2, 1])
767
+ sage: C3_sorted_merge_check([[1],[3,2], []])
768
+ ([3, 2, 1], [2, 1])
769
+ sage: C3_sorted_merge_check([[1],[4,3,2], []])
770
+ ([4, 3, 2, 1], [2, 1])
771
+ sage: C3_sorted_merge_check([[1],[4,3,2], [2]])
772
+ ([4, 3, 2, 1], [2, 1])
773
+ sage: C3_sorted_merge_check([[2],[1],[4],[3]])
774
+ ([4, 3, 2, 1], [3, 2, 1])
775
+ sage: C3_sorted_merge_check([[2],[1],[4],[]])
776
+ ([4, 2, 1], [4, 2, 1])
777
+ sage: C3_sorted_merge_check([[2],[1],[3],[4]])
778
+ ([4, 3, 2, 1], [4, 3, 2, 1])
779
+ sage: C3_sorted_merge_check([[2],[1],[3,2,1],[3]])
780
+ ([3, 2, 1], [3])
781
+ sage: C3_sorted_merge_check([[2],[1],[2,1],[3]])
782
+ ([3, 2, 1], [3, 2])
783
+
784
+ Exercises adding one item when the last list has a single element;
785
+ the second example comes from an actual poset::
786
+
787
+ sage: C3_sorted_merge_check([[5,4,2],[4,3],[5,4,1]])
788
+ ([5, 4, 3, 2, 1], [5, 4, 3, 2, 1])
789
+ sage: C3_sorted_merge_check([[6,4,2],[5,3],[6,5,1]])
790
+ ([6, 5, 4, 3, 2, 1], [6, 5, 4, 3, 2, 1])
791
+ """
792
+ lists = list(lists)
793
+ if not lists:
794
+ raise ValueError("The input should be a non empty list of lists (or iterables)")
795
+ # for l in lists:
796
+ # assert sorted(l, key = key, reverse=True) == l,\
797
+ # "Each input list should be sorted %s"%l
798
+
799
+ cdef set suggestion = set(lists[-1])
800
+ cdef bint last_list_non_empty = bool(lists[-1])
801
+ cdef list out = []
802
+ # Data structure / invariants:
803
+ # - Each list remains sorted and duplicate free.
804
+ # - Each list only evolves by popping its largest element
805
+ # Exception: elements can be inserted back into the last list.
806
+ # - Whenever a list becomes empty, it's removed from the data structure.
807
+ # The order between the (remaining non empty) lists remains unchanged.
808
+ # - nbheads contains the number of lists appearing in the data structure.
809
+ # - The flag ``last_list_non_empty`` states whether the last
810
+ # list is currently non empty; if yes, by the above, this list is stored last.
811
+ # - Each list is split between its head (in ``heads``) and tail (in ``tails'').
812
+ # - Each tail is stored reversed, so that we can use a cheap ``pop()``
813
+ # in lieue of ``pop(0)``.
814
+ # - A duplicate of this tail is stored as a set (of keys) in
815
+ # ``tailsets``, for cheap membership testing.
816
+
817
+ cdef int i, j, max_i
818
+ cdef bint cont
819
+ cdef list tail, l
820
+ cdef set tailset
821
+
822
+ cdef list tails = [l[::-1] for l in lists if l]
823
+ cdef list heads = [tail.pop() for tail in tails]
824
+ cdef set tmp_set
825
+ cdef list tailsets = [] # remove closure [set(key(O) for O in tail) for tail in tails]
826
+ for tail in tails:
827
+ tmp_set = set()
828
+ for O in tail:
829
+ tmp_set.add(key(O))
830
+ tailsets.append(tmp_set)
831
+ # for i in range(len(tails)):
832
+ # assert len(tails[i]) == len(tailsets[i]), \
833
+ # "All objects should be distinct and have distinct sorting key!"+'\n'.join(" - %s: %s"%(key(O), O) for O in sorted(tails[i], key=key))
834
+
835
+ cdef int nbheads = len(heads)
836
+ cdef dict holder = {}
837
+
838
+ # def print_state():
839
+ # print("-- %s -- %s ------"%(out,suggestion))
840
+ # for i in range(nbheads):
841
+ # print([heads[i]] + list(reversed(tails[i])))
842
+
843
+ # def check_state():
844
+ # for i in range(nbheads):
845
+ # l = tails[i]
846
+ # if heads[i] is not None:
847
+ # l = l+[heads[i]]
848
+ # assert sorted(l, key=key) == l
849
+ # assert len(set(l)) == len(l)
850
+ # assert len(tails[i]) == len(set(tails[i])), \
851
+ # "C3's input list should have no repeats %s"%tails[i]
852
+ # assert set(key(O) for O in tails[i]) == tailsets[i], \
853
+ # "inconsistent tails[i] and tailsets[i]: %s %s"%(tails[i], tailsets[i])
854
+ # assert len(tails[i]) == len(tailsets[i]), \
855
+ # "keys should be distinct"%(tails[i])
856
+
857
+ while nbheads:
858
+ # print_state()
859
+ # check_state()
860
+ # Find the position of the largest head which will become the next item
861
+ max_i = 0
862
+ max_key = key(heads[0])
863
+ for i in range(1, nbheads):
864
+ O = heads[i]
865
+ O_key = key(O)
866
+ if O_key > max_key:
867
+ max_i = i
868
+ max_key = O_key
869
+ max_value = heads[max_i]
870
+
871
+ # Find all the bad choices
872
+ max_bad = None
873
+ for i in range(max_i):
874
+ O = heads[i]
875
+ # Does O appear in none of the tails?
876
+ O_key = key(O)
877
+ # replace the closure
878
+ # if any(O_key in tailsets[j] for j in range(nbheads) if j != i): continue
879
+ cont = False
880
+ for j in range(i):
881
+ if O_key in tailsets[j]:
882
+ cont = True
883
+ break
884
+ if cont:
885
+ continue
886
+ for j from i<j<nbheads:
887
+ if O_key in tailsets[j]:
888
+ cont = True
889
+ break
890
+ if cont:
891
+ continue
892
+
893
+ # The plain C3 algorithm would have chosen O as next item!
894
+ if max_bad is None or O_key > key(max_bad):
895
+ max_bad = O
896
+
897
+ # We prevent this choice by inserting O in the tail of the
898
+ # suggestions. At this stage, we only insert it into the
899
+ # last list. Later, we will make sure that it is actually
900
+ # in the tail of the last list.
901
+ if not last_list_non_empty:
902
+ # Reinstate the last list for the suggestion
903
+ # if it had disappeared before
904
+ heads.append(O)
905
+ tails.append([])
906
+ tailsets.append(set())
907
+ nbheads += 1
908
+ last_list_non_empty = True
909
+ elif O_key > key(heads[-1]):
910
+ tails[-1].append(heads[-1])
911
+ tailsets[-1].add(key(heads[-1]))
912
+ heads[-1] = O
913
+ elif O != heads[-1]:
914
+ assert O_key not in tailsets[-1], "C3 should not have chosen this O"
915
+ # Use a heap or something for fast sorted insertion?
916
+ # Since Python uses TimSort, that's probably not so bad.
917
+ tails[-1].append(O)
918
+ tails[-1].sort(key=key)
919
+ tailsets[-1].add(O_key)
920
+ suggestion.add(O)
921
+ # check_state()
922
+
923
+ # Insert max_value in the last list, if needed to hold off the bad items
924
+ if max_bad is not None:
925
+ last_head = heads[-1]
926
+ if last_head is None or key(max_bad) >= key(last_head):
927
+ if last_head is not None and last_head != max_bad:
928
+ tails[-1].append(last_head)
929
+ tailsets[-1].add(key(last_head))
930
+ # check_state()
931
+ heads[-1] = max_value
932
+ holder[max_bad] = max_value
933
+ # check_state()
934
+
935
+ out.append(max_value)
936
+ # Clear O from other heads, removing the line altogether
937
+ # if the tail is already empty.
938
+ # j goes down so that ``del heads[j]`` does not screw up the numbering
939
+ for j in range(nbheads-1, -1, -1):
940
+ if heads[j] == max_value:
941
+ tail = tails[j]
942
+ if tail:
943
+ X = tail.pop()
944
+ heads[j] = X
945
+ tailset = tailsets[j]
946
+ tailset.remove(key(X))
947
+ else:
948
+ del heads[j]
949
+ del tails[j]
950
+ del tailsets[j]
951
+ nbheads -= 1
952
+ if last_list_non_empty and j == nbheads:
953
+ last_list_non_empty = False
954
+ # check_state()
955
+ suggestion.update(holder.values())
956
+ cdef list suggestion_list = sorted(suggestion, key=key, reverse=True)
957
+ # assert C3_merge(lists[:-1]+[suggestion_list]) == out
958
+ return (out, suggestion_list)
959
+
960
+
961
+ class HierarchyElement(object, metaclass=ClasscallMetaclass):
962
+ """
963
+ A class for elements in a hierarchy.
964
+
965
+ This class is for testing and experimenting with various variants
966
+ of the ``C3`` algorithm to compute a linear extension of the
967
+ elements above an element in a hierarchy. Given the topic at hand,
968
+ we use the following naming conventions. For `x` an element of the
969
+ hierarchy, we call the elements just above `x` its *bases*, and
970
+ the linear extension of all elements above `x` its *MRO*.
971
+
972
+ By convention, the bases are given as lists of instances of
973
+ :class:`HierarchyElement`, and MROs are given a list of the
974
+ corresponding values.
975
+
976
+ INPUT:
977
+
978
+ - ``value`` -- an object
979
+ - ``succ`` -- a successor function, poset or digraph from which
980
+ one can recover the successors of ``value``
981
+ - ``key`` -- a function taking values as input (default: the
982
+ identity) this function is used to compute comparison keys for
983
+ sorting elements of the hierarchy.
984
+
985
+ .. NOTE::
986
+
987
+ Constructing a :class:`HierarchyElement` immediately constructs the
988
+ whole hierarchy above it.
989
+
990
+ EXAMPLES:
991
+
992
+ See the introduction of this module :mod:`sage.misc.c3_controlled`
993
+ for many examples. Here we consider a large example, originally
994
+ taken from the hierarchy of categories above
995
+ :class:`HopfAlgebrasWithBasis`::
996
+
997
+ sage: from sage.misc.c3_controlled import HierarchyElement
998
+ sage: G = DiGraph({ # needs sage.graphs
999
+ ....: 44 : [43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1000
+ ....: 43 : [42, 41, 40, 36, 35, 39, 38, 37, 33, 32, 31, 30, 29, 28, 27, 26, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1001
+ ....: 42 : [36, 35, 37, 30, 29, 28, 27, 26, 15, 14, 12, 11, 9, 8, 5, 3, 2, 1, 0],
1002
+ ....: 41 : [40, 36, 35, 33, 32, 31, 30, 29, 28, 27, 26, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1003
+ ....: 40 : [36, 35, 32, 31, 30, 29, 28, 27, 26, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1004
+ ....: 39 : [38, 37, 33, 32, 31, 30, 29, 28, 27, 26, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1005
+ ....: 38 : [37, 33, 32, 31, 30, 29, 28, 27, 26, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1006
+ ....: 37 : [30, 29, 28, 27, 26, 15, 14, 12, 11, 9, 8, 5, 3, 2, 1, 0],
1007
+ ....: 36 : [35, 30, 29, 28, 27, 26, 15, 14, 12, 11, 9, 8, 5, 3, 2, 1, 0],
1008
+ ....: 35 : [29, 28, 27, 26, 15, 14, 12, 11, 9, 8, 5, 3, 2, 1, 0],
1009
+ ....: 34 : [33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1010
+ ....: 33 : [32, 31, 30, 29, 28, 27, 26, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1011
+ ....: 32 : [31, 30, 29, 28, 27, 26, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1012
+ ....: 31 : [30, 29, 28, 27, 26, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1013
+ ....: 30 : [29, 28, 27, 26, 15, 14, 12, 11, 9, 8, 5, 3, 2, 1, 0],
1014
+ ....: 29 : [28, 27, 26, 15, 14, 12, 11, 9, 8, 5, 3, 2, 1, 0],
1015
+ ....: 28 : [27, 26, 15, 14, 12, 11, 9, 8, 5, 3, 2, 1, 0],
1016
+ ....: 27 : [15, 14, 12, 11, 9, 8, 5, 3, 2, 1, 0],
1017
+ ....: 26 : [15, 14, 12, 11, 9, 8, 5, 3, 2, 1, 0],
1018
+ ....: 25 : [24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1019
+ ....: 24 : [4, 2, 1, 0],
1020
+ ....: 23 : [22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1021
+ ....: 22 : [21, 20, 18, 17, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1022
+ ....: 21 : [20, 17, 4, 2, 1, 0],
1023
+ ....: 20 : [4, 2, 1, 0],
1024
+ ....: 19 : [18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1025
+ ....: 18 : [17, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1026
+ ....: 17 : [4, 2, 1, 0],
1027
+ ....: 16 : [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1028
+ ....: 15 : [14, 12, 11, 9, 8, 5, 3, 2, 1, 0],
1029
+ ....: 14 : [11, 3, 2, 1, 0],
1030
+ ....: 13 : [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1031
+ ....: 12 : [11, 9, 8, 5, 3, 2, 1, 0],
1032
+ ....: 11 : [3, 2, 1, 0],
1033
+ ....: 10 : [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1034
+ ....: 9 : [8, 5, 3, 2, 1, 0],
1035
+ ....: 8 : [3, 2, 1, 0],
1036
+ ....: 7 : [6, 5, 4, 3, 2, 1, 0],
1037
+ ....: 6 : [4, 3, 2, 1, 0],
1038
+ ....: 5 : [3, 2, 1, 0],
1039
+ ....: 4 : [2, 1, 0],
1040
+ ....: 3 : [2, 1, 0],
1041
+ ....: 2 : [1, 0],
1042
+ ....: 1 : [0],
1043
+ ....: 0 : [],
1044
+ ....: })
1045
+
1046
+ sage: # needs sage.combinat sage.graphs
1047
+ sage: x = HierarchyElement(44, G)
1048
+ sage: x.mro
1049
+ [44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
1050
+ sage: x.cls
1051
+ <class '44.cls'>
1052
+ sage: x.cls.mro()
1053
+ [<class '44.cls'>, <class '43.cls'>, <class '42.cls'>, <class '41.cls'>, <class '40.cls'>, <class '39.cls'>, <class '38.cls'>, <class '37.cls'>, <class '36.cls'>, <class '35.cls'>, <class '34.cls'>, <class '33.cls'>, <class '32.cls'>, <class '31.cls'>, <class '30.cls'>, <class '29.cls'>, <class '28.cls'>, <class '27.cls'>, <class '26.cls'>, <class '25.cls'>, <class '24.cls'>, <class '23.cls'>, <class '22.cls'>, <class '21.cls'>, <class '20.cls'>, <class '19.cls'>, <class '18.cls'>, <class '17.cls'>, <class '16.cls'>, <class '15.cls'>, <class '14.cls'>, <class '13.cls'>, <class '12.cls'>, <class '11.cls'>, <class '10.cls'>, <class '9.cls'>, <class '8.cls'>, <class '7.cls'>, <class '6.cls'>, <class '5.cls'>, <class '4.cls'>, <class '3.cls'>, <class '2.cls'>, <class '1.cls'>, <class '0.cls'>, <... 'object'>]
1054
+ """
1055
+ @staticmethod
1056
+ def __classcall__(cls, value, succ, key=None):
1057
+ """
1058
+ EXAMPLES::
1059
+
1060
+ sage: # needs sage.combinat sage.graphs
1061
+ sage: from sage.misc.c3_controlled import HierarchyElement
1062
+ sage: P = Poset((divisors(30), lambda x, y: y.divides(x)), facade=True)
1063
+ sage: x = HierarchyElement(10, P)
1064
+ sage: x
1065
+ 10
1066
+ sage: x.bases
1067
+ [5, 2]
1068
+ sage: x.mro
1069
+ [10, 5, 2, 1]
1070
+ """
1071
+ from sage.categories.sets_cat import Sets
1072
+
1073
+ try:
1074
+ from sage.combinat.posets.poset_examples import Posets
1075
+ except ImportError:
1076
+ pass
1077
+ else:
1078
+ if succ in Posets():
1079
+ assert succ in Sets().Facade()
1080
+ succ = succ.upper_covers
1081
+
1082
+ try:
1083
+ from sage.graphs.digraph import DiGraph
1084
+ except ImportError:
1085
+ pass
1086
+ else:
1087
+ if isinstance(succ, DiGraph):
1088
+ succ = succ.copy()
1089
+ succ._immutable = True
1090
+ succ = succ.neighbors_out
1091
+
1092
+ if key is None:
1093
+ key = identity
1094
+
1095
+ @cached_function
1096
+ def f(x):
1097
+ return typecall(cls, x, [f(y) for y in succ(x)], key, f)
1098
+
1099
+ return f(value)
1100
+
1101
+ def __init__(self, value, bases, key, from_value):
1102
+ """
1103
+ EXAMPLES::
1104
+
1105
+ sage: # needs sage.graphs
1106
+ sage: from sage.misc.c3_controlled import HierarchyElement
1107
+ sage: P = Poset((divisors(30), lambda x, y: y.divides(x)), facade=True)
1108
+ sage: x = HierarchyElement(10, P)
1109
+ sage: x
1110
+ 10
1111
+ sage: x.value
1112
+ 10
1113
+ sage: x._bases
1114
+ [5, 2]
1115
+ sage: x._key
1116
+ <built-in function identity>
1117
+ sage: x._key(10)
1118
+ 10
1119
+
1120
+ The ``_from_value`` attribute is a function that can be used
1121
+ to reconstruct an element of the hierarchy from its value::
1122
+
1123
+ sage: x._from_value # needs sage.graphs
1124
+ Cached version of <...__classcall__...>
1125
+ sage: x._from_value(x.value) is x # needs sage.graphs
1126
+ True
1127
+ """
1128
+ self.value = value
1129
+ self._bases = sorted(bases, key=lambda x: key(x.value), reverse=True)
1130
+ self._key = key
1131
+ self._from_value = from_value
1132
+
1133
+ def __repr__(self):
1134
+ """
1135
+ Return the representation of ``self`` which is that of its value.
1136
+
1137
+ EXAMPLES::
1138
+
1139
+ sage: from sage.misc.c3_controlled import HierarchyElement
1140
+ sage: P = Poset((divisors(30), lambda x, y: y.divides(x)), facade=True) # needs sage.graphs
1141
+ sage: x = HierarchyElement(10, P) # needs sage.graphs
1142
+ sage: x # needs sage.graphs
1143
+ 10
1144
+ """
1145
+ return repr(self.value)
1146
+
1147
+ @lazy_attribute
1148
+ def bases(self):
1149
+ """
1150
+ The bases of ``self``.
1151
+
1152
+ The bases are given as a list of instances of
1153
+ :class:`HierarchyElement`, sorted decreasingly according to
1154
+ the ``key`` function.
1155
+
1156
+ EXAMPLES::
1157
+
1158
+ sage: # needs sage.combinat sage.graphs
1159
+ sage: from sage.misc.c3_controlled import HierarchyElement
1160
+ sage: P = Poset((divisors(30), lambda x, y: y.divides(x)), facade=True)
1161
+ sage: x = HierarchyElement(10, P)
1162
+ sage: x.bases
1163
+ [5, 2]
1164
+ sage: type(x.bases[0])
1165
+ <class 'sage.misc.c3_controlled.HierarchyElement'>
1166
+ sage: x.mro
1167
+ [10, 5, 2, 1]
1168
+ sage: x._bases_controlled
1169
+ [5, 2]
1170
+ """
1171
+ return self._bases
1172
+
1173
+ @lazy_attribute
1174
+ def mro(self):
1175
+ """
1176
+ The MRO for this object, calculated with :meth:`C3_sorted_merge`.
1177
+
1178
+ EXAMPLES::
1179
+
1180
+ sage: # needs sage.graphs
1181
+ sage: from sage.misc.c3_controlled import HierarchyElement, C3_sorted_merge, identity
1182
+ sage: P = Poset({7: [5, 6], 5: [1, 2], 6: [3, 4]}, facade=True)
1183
+ sage: x = HierarchyElement(5, P)
1184
+ sage: x.mro
1185
+ [5, 2, 1]
1186
+ sage: x = HierarchyElement(6, P)
1187
+ sage: x.mro
1188
+ [6, 4, 3]
1189
+ sage: x = HierarchyElement(7, P)
1190
+ sage: x.mro
1191
+ [7, 6, 5, 4, 3, 2, 1]
1192
+
1193
+ sage: C3_sorted_merge([[6, 4, 3], [5, 2, 1], [6, 5]], identity) # needs sage.graphs
1194
+ ([6, 5, 4, 3, 2, 1], [6, 5, 4])
1195
+
1196
+ TESTS::
1197
+
1198
+ sage: assert all(isinstance(v, Integer) for v in x.mro) # needs sage.graphs
1199
+ """
1200
+ bases = self._bases
1201
+ result, suggestion = C3_sorted_merge([base.mro for base in bases]+[[base.value for base in bases]], key=self._key)
1202
+ result = [self.value] + result
1203
+ self._bases_controlled = suggestion
1204
+ return result
1205
+
1206
+ @lazy_attribute
1207
+ def _bases_controlled(self):
1208
+ """
1209
+ A list of bases controlled by :meth:`C3_sorted_merge`.
1210
+
1211
+ This triggers the calculation of the MRO using
1212
+ :meth:`C3_sorted_merge`, which sets this attribute as a side
1213
+ effect.
1214
+
1215
+ EXAMPLES::
1216
+
1217
+ sage: # needs sage.graphs
1218
+ sage: from sage.misc.c3_controlled import HierarchyElement
1219
+ sage: P = Poset({7: [5, 6], 5: [1, 2], 6: [3, 4]}, facade=True)
1220
+ sage: x = HierarchyElement(7, P)
1221
+ sage: x._bases
1222
+ [6, 5]
1223
+ sage: x._bases_controlled
1224
+ [6, 5, 4]
1225
+ """
1226
+ self.mro
1227
+ return self._bases_controlled
1228
+
1229
+ @lazy_attribute
1230
+ def mro_standard(self):
1231
+ """
1232
+ The MRO for this object, calculated with :meth:`C3_merge`.
1233
+
1234
+ EXAMPLES::
1235
+
1236
+ sage: from sage.misc.c3_controlled import HierarchyElement, C3_merge
1237
+
1238
+ sage: # needs sage.graphs
1239
+ sage: P = Poset({7: [5, 6], 5: [1, 2], 6: [3, 4]}, facade=True)
1240
+ sage: x = HierarchyElement(5, P)
1241
+ sage: x.mro_standard
1242
+ [5, 2, 1]
1243
+ sage: x = HierarchyElement(6, P)
1244
+ sage: x.mro_standard
1245
+ [6, 4, 3]
1246
+ sage: x = HierarchyElement(7, P)
1247
+ sage: x.mro_standard
1248
+ [7, 6, 4, 3, 5, 2, 1]
1249
+
1250
+ sage: C3_merge([[6, 4, 3], [5, 2, 1], [6, 5]])
1251
+ [6, 4, 3, 5, 2, 1]
1252
+
1253
+ TESTS::
1254
+
1255
+ sage: assert all(isinstance(v, Integer) for v in x.mro_standard) # needs sage.graphs
1256
+ """
1257
+ bases = self._bases
1258
+ return [self.value] + C3_merge([base.mro_standard for base in bases]+[[base.value for base in bases]])
1259
+
1260
+ @lazy_attribute
1261
+ def mro_controlled(self):
1262
+ """
1263
+ The MRO for this object, calculated with :meth:`C3_merge`, under
1264
+ control of :func:`C3_sorted_merge`
1265
+
1266
+ EXAMPLES::
1267
+
1268
+ sage: from sage.misc.c3_controlled import HierarchyElement, C3_merge
1269
+
1270
+ sage: # needs sage.graphs
1271
+ sage: P = Poset({7: [5, 6], 5: [1, 2], 6: [3, 4]}, facade=True)
1272
+ sage: x = HierarchyElement(5, P)
1273
+ sage: x.mro_controlled
1274
+ [5, 2, 1]
1275
+ sage: x = HierarchyElement(6, P)
1276
+ sage: x.mro_controlled
1277
+ [6, 4, 3]
1278
+ sage: x = HierarchyElement(7, P)
1279
+ sage: x.mro_controlled
1280
+ [7, 6, 5, 4, 3, 2, 1]
1281
+ sage: x._bases
1282
+ [6, 5]
1283
+ sage: x._bases_controlled
1284
+ [6, 5, 4]
1285
+
1286
+ sage: C3_merge([[6, 4, 3], [5, 2, 1], [6, 5]])
1287
+ [6, 4, 3, 5, 2, 1]
1288
+ sage: C3_merge([[6, 4, 3], [5, 2, 1], [6, 5, 4]])
1289
+ [6, 5, 4, 3, 2, 1]
1290
+
1291
+ TESTS::
1292
+
1293
+ sage: assert all(isinstance(v, Integer) for v in x.mro_controlled) # needs sage.graphs
1294
+ """
1295
+ return [self.value] + C3_merge([base.mro_controlled for base in self._bases]+[self._bases_controlled])
1296
+
1297
+ @cached_method
1298
+ def _test_mro(self):
1299
+ r"""
1300
+ Run consistency tests.
1301
+
1302
+ This checks in particular that the instrumented ``C3`` and
1303
+ controlled ``C3`` algorithms give, as desired, the
1304
+ decreasingly sorted list of the objects above in the
1305
+ hierarchy. For the controlled ``C3`` algorithm, this includes
1306
+ both Sage's implementation, and Python's implementation (by
1307
+ constructing an appropriate hierarchy of classes).
1308
+
1309
+ It is cached because it is run recursively on the elements
1310
+ above ``self``.
1311
+
1312
+ EXAMPLES::
1313
+
1314
+ sage: from sage.misc.c3_controlled import HierarchyElement
1315
+ sage: P = Poset({7: [5, 6], 5: [1, 2], 6: [3, 4]}, facade=True) # needs sage.graphs
1316
+ sage: x = HierarchyElement(7, P) # needs sage.graphs
1317
+ sage: x._test_mro() # needs sage.graphs
1318
+ """
1319
+ for b in self._bases:
1320
+ b._test_mro()
1321
+ try:
1322
+ assert self.mro_standard[0] == self.value
1323
+ except ValueError:
1324
+ # standard C3 failed to compute a mro; that's ok
1325
+ pass
1326
+ assert self.mro[0] == self.value
1327
+ assert self.mro_controlled[0] == self.value
1328
+ assert sorted([x.value for x in self.all_bases()], key=self._key, reverse = True) == self.mro
1329
+ assert self.mro == self.mro_controlled
1330
+ assert self.cls.mro() == [self._from_value(b).cls for b in self.mro]+[object]
1331
+
1332
+ @lazy_attribute
1333
+ def cls(self):
1334
+ """
1335
+ Return a Python class with inheritance graph parallel to the hierarchy above ``self``.
1336
+
1337
+ EXAMPLES::
1338
+
1339
+ sage: # needs sage.graphs
1340
+ sage: from sage.misc.c3_controlled import HierarchyElement
1341
+ sage: P = Poset((divisors(30), lambda x, y: y.divides(x)), facade=True)
1342
+ sage: x = HierarchyElement(1, P)
1343
+ sage: x.cls
1344
+ <class '1.cls'>
1345
+ sage: x.cls.mro()
1346
+ [<class '1.cls'>, <... 'object'>]
1347
+ sage: x = HierarchyElement(30, P)
1348
+ sage: x.cls
1349
+ <class '30.cls'>
1350
+ sage: x.cls.mro()
1351
+ [<class '30.cls'>, <class '15.cls'>, <class '10.cls'>, <class '6.cls'>, <class '5.cls'>, <class '3.cls'>, <class '2.cls'>, <class '1.cls'>, <... 'object'>]
1352
+ """
1353
+ super_classes = tuple(self._from_value(base).cls for base in self._bases_controlled)
1354
+ if not super_classes:
1355
+ super_classes = (object,)
1356
+ return dynamic_class("%s.cls" % self, super_classes)
1357
+
1358
+ @cached_method
1359
+ def all_bases(self):
1360
+ """
1361
+ Return the set of all instances of :class:`HierarchyElement` above
1362
+ ``self``, ``self`` included.
1363
+
1364
+ EXAMPLES::
1365
+
1366
+ sage: # needs sage.graphs
1367
+ sage: from sage.misc.c3_controlled import HierarchyElement
1368
+ sage: P = Poset((divisors(30), lambda x, y: y.divides(x)), facade=True)
1369
+ sage: HierarchyElement(1, P).all_bases()
1370
+ {1}
1371
+ sage: HierarchyElement(10, P).all_bases() # random output
1372
+ {10, 5, 2, 1}
1373
+ sage: sorted([x.value for x in HierarchyElement(10, P).all_bases()])
1374
+ [1, 2, 5, 10]
1375
+ """
1376
+ return {self} | {x for base in self._bases for x in base.all_bases()}
1377
+
1378
+ def all_bases_len(self):
1379
+ """
1380
+ Return the cumulated size of the bases of the elements above ``self`` in the hierarchy.
1381
+
1382
+ EXAMPLES::
1383
+
1384
+ sage: from sage.misc.c3_controlled import HierarchyElement
1385
+ sage: P = Poset((divisors(30), lambda x, y: y.divides(x)), facade=True) # needs sage.graphs
1386
+ sage: HierarchyElement(30, P).all_bases_len() # needs sage.graphs
1387
+ 12
1388
+ """
1389
+ return sum(len(x._bases) for x in self.all_bases())
1390
+
1391
+ def all_bases_controlled_len(self):
1392
+ """
1393
+ Return the cumulated size of the controlled bases of the elements above ``self`` in the hierarchy.
1394
+
1395
+ EXAMPLES::
1396
+
1397
+ sage: from sage.misc.c3_controlled import HierarchyElement
1398
+ sage: P = Poset((divisors(30), lambda x, y: y.divides(x)), facade=True) # needs sage.graphs
1399
+ sage: HierarchyElement(30, P).all_bases_controlled_len() # needs sage.graphs
1400
+ 13
1401
+ """
1402
+ return sum(len(x._bases_controlled) for x in self.all_bases())