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