Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/misc/c3.pyx
8814 views
1
"""
2
The C3 algorithm
3
4
The C3 algorithm is used as method resolution order for new style
5
classes in Python. The implementation here is used to order the list
6
of super categories of a category.
7
8
AUTHOR:
9
10
- Simon King (2011-11): initial version.
11
"""
12
#*****************************************************************************
13
# Copyright (C) 2011 Simon King <[email protected]>
14
#
15
# Distributed under the terms of the GNU General Public License (GPL)
16
# http://www.gnu.org/licenses/
17
#******************************************************************************
18
19
include "sage/ext/python.pxi"
20
21
cpdef list C3_algorithm(object start, str bases, str attribute, bint proper):
22
"""
23
An implementation of the C3 algorithm.
24
25
C3 is the algorithm used by Python to construct the method
26
resolution order for new style classes involving multiple
27
inheritance.
28
29
After :trac:`11943` this implementation was used to compute the
30
list of super categories of a category; see
31
:meth:`~sage.categories.category.Category.all_super_categories`.
32
The purpose is to ensure that list of super categories matches
33
with the method resolution order of the parent or element classes
34
of a category.
35
36
Since :trac:`13589`, this implementation is superseded by that in
37
:mod:`sage.misc.c3_controlled`, that puts the ``C3`` algorithm
38
under control of some total order on categories. This guarantees
39
that ``C3`` always finds a consistent Method Resolution Order. For
40
background, see :mod:`sage.misc.c3_controlled`.
41
42
INPUT:
43
44
- ``start`` -- an object; the returned list is built upon data
45
provided by certain attributes of ``start``.
46
- ``bases`` -- a string; the name of an attribute of ``start``
47
providing a list of objects.
48
- ``attribute`` -- a string; the name of an attribute of the
49
objects provided in ``getattr(start,bases)``. That attribute is
50
supposed to provide a list.
51
52
ASSUMPTIONS:
53
54
Our implementation of the algorithm only works on lists of
55
objects that compare equal if and only if they are identical.
56
57
OUTPUT:
58
59
A list, the result of the C3 algorithm applied to the list
60
``[getattr(X,attribute) for X in getattr(start,bases)]``.
61
62
EXAMPLES:
63
64
We create a class for elements in a hierarchy that uses the ``C3``
65
algorithm to compute, for each element, a linear extension of the
66
elements above it::
67
68
.. TODO:: Move back the __init__ at the beginning
69
70
sage: from sage.misc.c3 import C3_algorithm
71
sage: class HierarchyElement(UniqueRepresentation):
72
....: @lazy_attribute
73
....: def _all_bases(self):
74
....: return C3_algorithm(self, '_bases', '_all_bases', False)
75
....: def __repr__(self):
76
....: return self._name
77
....: def __init__(self, name, bases):
78
....: self._name = name
79
....: self._bases = list(bases)
80
81
We construct a little hierarchy::
82
83
sage: T = HierarchyElement("T", ())
84
sage: X = HierarchyElement("X", (T,))
85
sage: Y = HierarchyElement("Y", (T,))
86
sage: A = HierarchyElement("A", (X, Y))
87
sage: B = HierarchyElement("B", (Y, X))
88
sage: Foo = HierarchyElement("Foo", (A, B))
89
90
And inspect the linear extensions associated to each element::
91
92
sage: T._all_bases
93
[T]
94
sage: X._all_bases
95
[X, T]
96
sage: Y._all_bases
97
[Y, T]
98
sage: A._all_bases
99
[A, X, Y, T]
100
sage: B._all_bases
101
[B, Y, X, T]
102
103
So far so good. However::
104
105
sage: Foo._all_bases
106
Traceback (most recent call last):
107
...
108
ValueError: Can not merge the items X, Y.
109
110
The ``C3`` algorithm is not able to create a consistent linear
111
extension. Indeed, its specifications impose that, if ``X`` and
112
``Y`` appear in a certain order in the linear extension for an
113
element of the hierarchy, then they should appear in the same
114
order for any lower element. This is clearly not possibly for
115
``Foo``, since ``A`` and ``B`` impose incompatible orders. If the
116
above was a hierarchy of classes, Python would complain that it
117
cannot calculate a consistent Method Resolution Order.
118
119
TESTS:
120
121
Regression test for bug #1 of :trac:`13501`::
122
123
sage: class C(object): pass
124
sage: class F(object): pass
125
sage: class G(object): pass
126
sage: class B(C,F): pass
127
sage: class D(F,G): pass
128
sage: class E(F): pass
129
sage: class A(B,D,E): pass
130
sage: [cls.__name__ for cls in A.mro()]
131
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'object']
132
133
sage: C = HierarchyElement("C", ())
134
sage: F = HierarchyElement("F", ())
135
sage: G = HierarchyElement("G", ())
136
sage: B = HierarchyElement("B", (C, F))
137
sage: D = HierarchyElement("D", (F, G))
138
sage: E = HierarchyElement("E", (F,))
139
sage: A = HierarchyElement("A", (B, D, E))
140
sage: A._all_bases
141
[A, B, C, D, E, F, G]
142
143
Regression test for bug #2 of :trac:`13501`. The following should
144
fail since ``A`` asks for ``B`` to come before ``C``, where as
145
``B`` is a super class of ``C``::
146
147
sage: class B(object): pass
148
sage: class C(B): pass
149
sage: class A(B, C): pass
150
Traceback (most recent call last):
151
...
152
TypeError: Error when calling the metaclass bases
153
Cannot create a consistent method resolution
154
order (MRO) for bases ...
155
156
sage: B = HierarchyElement("B", ())
157
sage: C = HierarchyElement("C", (B,))
158
sage: A = HierarchyElement("A", (B,C))
159
sage: A._all_bases
160
Traceback (most recent call last):
161
...
162
ValueError: Can not merge the items B, C, B.
163
164
Since :trac:`11943`, the following consistency tests are part
165
of the test suites of categories (except for hom categories)::
166
167
sage: C = Category.join([HopfAlgebrasWithBasis(QQ), FiniteEnumeratedSets()])
168
sage: C.parent_class.mro() == [x.parent_class for x in C.all_super_categories()]+[object]
169
True
170
sage: C.element_class.mro() == [x.element_class for x in C.all_super_categories()]+[object]
171
True
172
"""
173
cdef list out
174
if proper:
175
out = []
176
else:
177
out = [start]
178
cdef list args = getattr(start,bases)
179
if not args:
180
return out
181
# Data structure / invariants:
182
# We will be working with the MRO's of the super objects
183
# together with the list of bases of ``self``.
184
# Each list is split between its head (in ``heads``) and tail (in
185
# ``tails'') . Each tail is stored reversed, so that we can use a
186
# cheap pop() in lieue of pop(0). A duplicate of the tail is
187
# stored as a set in ``tailsets`` for cheap membership testing.
188
# Since we actually want comparison by identity, not equality,
189
# what we store is the set of memory locations of objects
190
cdef object O, X
191
cdef list tails = [getattr(obj, attribute) for obj in args]
192
tails.append(args)
193
tails = [list(reversed(tail)) for tail in tails]
194
cdef list heads = [tail.pop() for tail in tails]
195
cdef list tailsets = [set([<size_t><void *>O for O in tail]) for tail in tails]
196
197
cdef int i, j, nbheads
198
nbheads = len(heads)
199
cdef bint next_item_found
200
cdef list tail_list
201
202
while nbheads:
203
for i from 0 <= i < nbheads:
204
O = heads[i]
205
# Does O appear in none of the tails? ``all(O not in tail for tail in tailsets)``
206
next_item_found = True
207
for j from 0 <= j < nbheads:
208
if j == i:
209
continue
210
if <size_t><void *>O in <set>tailsets[j]:
211
next_item_found = False
212
break
213
if next_item_found:
214
out.append(O)
215
# Clear O from other heads, removing the line altogether
216
# if the tail is already empty.
217
# j goes down so that ``del heads[j]`` does not screw up the numbering
218
for j from nbheads > j >= 0:
219
if heads[j] is O:
220
tail_list = tails[j]
221
if tail_list:
222
X = tail_list.pop()
223
heads[j] = X
224
<set>tailsets[j].remove(<size_t><void *>X)
225
else:
226
del heads[j]
227
del tails[j]
228
del tailsets[j]
229
nbheads -= 1
230
break
231
if not next_item_found:
232
# No head is available
233
raise ValueError, "Can not merge the items %s."%', '.join([repr(head) for head in heads])
234
return out
235
236