patches
view automatic_monoid-nt.patch @ 4057:89eb3ce51ecf
merge
| author | Thierry Monteil <sage at lma.homelinux.org> |
|---|---|
| date | Mon Sep 06 20:16:04 2010 +0200 (18 hours ago) |
| parents | 2e89de32ff14 |
| children |
line source
1 AutomaticMonoid: a class for finite monoids implemented as automatons
3 - unique representation for the elements (with copy/hash/...)
5 diff --git a/sage/combinat/all.py b/sage/combinat/all.py
6 --- a/sage/combinat/all.py
7 +++ b/sage/combinat/all.py
8 @@ -98,6 +98,9 @@ from backtrack import TransitiveIdeal, T
10 import copyonwrite
12 +#########################################
13 +from automatic_monoid import AutomaticMonoid
14 +
15 from integer_vector import IntegerVectors
16 from integer_vector_weighted import WeightedIntegerVectors
18 diff --git a/sage/combinat/automatic_monoid.py b/sage/combinat/automatic_monoid.py
19 new file mode 100644
20 --- /dev/null
21 +++ b/sage/combinat/automatic_monoid.py
22 @@ -0,0 +1,279 @@
23 +from sage.structure.parent import Parent
24 +from sage.structure.unique_representation import UniqueRepresentation
25 +from sage.structure.sage_object import SageObject
26 +from sage.categories.all import FiniteMonoids
27 +from sage.misc.all import cached_function, cached_method
28 +from sage.combinat import ranker
29 +from sage.combinat.all import TransitiveIdealGraded, CombinatorialClass
30 +from copy import copy
31 +import operator
32 +
33 +class AutomaticMonoid(UniqueRepresentation, Parent):
34 + r"""
35 +
36 + Construct (lazily) a monoid from a set of concrete generators
37 + living in an ambient monoid.
38 +
39 + EXAMPLES::
40 +
41 + sage: R = IntegerModRing(12)
42 + sage: M = AutomaticMonoid(Family({1: R(3), 2: R(5)}), one = R.one())
43 + sage: M.one()
44 + []
45 + sage: M.one() in M
46 + True
47 +
48 + Elements are represented by default by their reduced word, or by
49 + the corresponding element in the ambient monoid if the reduced
50 + word is not yet known.
51 +
52 + ::
53 +
54 + sage: g = M.generators; g
55 + Finite family {1: 3, 2: 5}
56 + sage: g[1]*g[2]
57 + 3
58 +
59 + Calling cardinality, or list, or iterating through the monoid will
60 + trigger its full construction and, as a side effect, compute all
61 + the reduced words. The order of the elements, and the induced
62 + choice of reduced word is currently length-lexicographic
63 + (i.e. the chosen reduced word is of minimal length, and then
64 + minimal lexicographically w.r.t. the order of the indices of the
65 + generators).
66 +
67 + sage: M.cardinality()
68 + 4
69 + sage: M.list()
70 + [[], [1], [2], [1, 1]]
71 + sage: g = M.generators
72 +
73 + sage: g[1]*g[2]
74 + [1]
75 +
76 + sage: g[1].transition(1)
77 + [1, 1]
78 + sage: g[1] * g[1]
79 + [1, 1]
80 + sage: g[1] * g[1] * g[1]
81 + [1]
82 + sage: g[1].transition(2)
83 + [1]
84 + sage: g[1] * g[2]
85 + [1]
86 + sage: M.list()
87 + [[], [1], [2], [1, 1]]
88 + sage: [ x.lift() for x in M.list() ]
89 + [1, 3, 5, 9]
90 + sage: M.idempotents()
91 + [[], [1, 1]]
92 + sage: G = M.cayley_graph(side = "twosided"); G
93 + Looped multi-digraph on 4 vertices
94 + sage: G.edges()
95 + [([], [1], (1, 'left')), ([], [1], (1, 'right')), ([], [2], (2, 'left')), ([], [2], (2, 'right')), ([1], [1], (2, 'left')), ([1], [1], (2, 'right')), ([1], [1, 1], (1, 'left')), ([1], [1, 1], (1, 'right')), ([2], [], (2, 'left')), ([2], [], (2, 'right')), ([2], [1], (1, 'left')), ([2], [1], (1, 'right')), ([1, 1], [1], (1, 'left')), ([1, 1], [1], (1, 'right')), ([1, 1], [1, 1], (2, 'left')), ([1, 1], [1, 1], (2, 'right'))]
96 +
97 + sage: map(sorted, M.j_classes())
98 + [[[1], [1, 1]], [[], [2]]]
99 + sage: M.j_classes_of_idempotents()
100 + [[[1, 1]], [[]]]
101 + sage: M.j_transversal_of_idempotents()
102 + [[1, 1], []]
103 +
104 + sage: map(attrcall('pseudo_order'), M.list())
105 + [[1, 0], [3, 1], [2, 0], [2, 1]]
106 +
107 + TESTS::
108 +
109 + sage: (g[1]).__hash__() == (g[1]*g[1]*g[1]).__hash__()
110 + True
111 + sage: g[1] == g[1]*g[1]*g[1]
112 + True
113 + sage: copy(g[1]) is g[1]
114 + True
115 + sage: from copy import deepcopy
116 + sage: deepcopy(g[1]) is g[1]
117 + True
118 + sage: M.__class__.mro() # todo: not implemented (wait until CombinatorialClass is a category)
119 + [<class 'sage.combinat.automatic_monoid.AutomaticMonoid_with_categories'>, <class 'sage.categories.category.uniq'>, <type 'sage.structure.parent.Parent'>, <type 'sage.structure.category_object.CategoryObject'>, <type 'sage.structure.sage_object.SageObject'>, <class 'sage.categories.finite_monoids.FiniteMonoids.parent_class'>, <class 'sage.categories.category.Monoids.parent_class'>, <class 'sage.categories.category.Semigroups.parent_class'>, <class 'sage.categories.category_types.Sets.parent_class'>, <type 'object'>]
120 +
121 + sage: TestSuite(M).run()
122 +
123 + CAVEATS:
124 +
125 + - AutomaticMonoid is designed primarily for finite monoids. This
126 + property is not checked automatically (this would be too costly
127 + if not impossible). Some of the features should still work with
128 + infinite monoids. In that case, the category Monoids() should
129 + be passed as extra argument, instead of the default
130 + FiniteMonoids().
131 +
132 + sage: AutomaticMonoid(Family({1:2}), category = Monoids())
133 + The (automatic) monoid with generators Finite family {1: 2}
134 +
135 + BUGS:
136 +
137 + - Running a command like [ f(p) for p in M ] where f involves
138 + computing products of elements and M.list() has not yet been
139 + computed caused an infinite loop at least in one occasion looking like::
140 +
141 + sage: all(operator.sub(*p.pseudo_order())==1 for p in M)
142 + False
143 +
144 + TODO:
145 +
146 + - The constructor should accept a list, and make it into a finite
147 + combinatorial class before caching
148 + """
149 +
150 + def __init__(self, generators, one = None, mul = operator.mul, category = FiniteMonoids()):
151 + """
152 + TESTS::
153 +
154 + sage: R = IntegerModRing(12)
155 + sage: M = AutomaticMonoid(Family(()), one = R.one())
156 + sage: M.ambient == R
157 + True
158 + sage: M = AutomaticMonoid(Family(()))
159 + Traceback (most recent call last):
160 + ...
161 + ValueError: AutomaticMonoid requires at least one generator or `one` to determine the ambient space
162 +
163 + """
164 + Parent.__init__(self, category = category)
165 + self.generators = generators # todo: rename to self._generators?
166 + if self.generators.cardinality() > 0:
167 + self.ambient = self.generators.first().parent()
168 + elif one is not None:
169 + self.ambient = one.parent()
170 + else:
171 + raise ValueError("AutomaticMonoid requires at least one generator or `one` to determine the ambient space")
172 +
173 + if one is None:
174 + one = self.generators.first().parent().one()
175 + self._one = self.retract(one)
176 + self.mul = mul
177 + self.generators_in_ambient = generators
178 + self.generators = generators.map(self.retract)
179 +
180 + self.elements = [self.one()]
181 + self.elements_set = set(self.elements)
182 + self.one()._reduced_word = []
183 + self.done = 0
184 +
185 + def one(self):
186 + return self._one
187 +
188 + def _repr_(self):
189 + return "The (automatic) monoid with generators %s"%self.generators
190 +
191 + def an_element(self):
192 + return self.generators.first()
193 +
194 + def some_elements(self):
195 + return self.generators
196 +
197 + @cached_method
198 + def retract(self, ambient_element):
199 + return self.element_class(self, ambient_element)
200 +
201 + def lift(self, x):
202 + assert(x in self)
203 + return x.lift()
204 +
205 + def semigroup_generators(self):
206 + return self.generators
207 +
208 + def __iter__(self):
209 + for x in self.elements:
210 + yield x
211 + while self.done < len(self.elements):
212 + x = self.elements[self.done]
213 + for i in self.generators.keys():
214 + y = x.transition(i)
215 + if y in self.elements_set:
216 + continue
217 + self.elements.append(y)
218 + self.elements_set.add(y)
219 + y._reduced_word = x.reduced_word()+[i]
220 + yield y
221 + self.done+=1
222 + return
223 +
224 + def cardinality(self):
225 + if self.done == len(self.elements):
226 + return self.done
227 + else:
228 + return self._cardinality_from_iterator()
229 +
230 + def __contains__(self, x):
231 + return x.parent() is self
232 +
233 + def product(self, x, y):
234 + assert(x in self)
235 + assert(y in self)
236 + red = y.reduced_word()
237 + if red is None:
238 + return self.retract(self.mul(x.lift(), y.lift()))
239 + else:
240 + for i in red:
241 + x = x.transition(i)
242 + return x
243 +
244 + class Element(SageObject):
245 + def __init__(self, parent, ambient_element):
246 + self._parent = parent
247 + self.ambient_element = ambient_element
248 + self._reduced_word = None
249 +
250 + # should we inherit from UniqueRepresentation instead?
251 + def __eq__ (self, other): return self is other
252 + def __hash__ (self): return id(self)
253 + def __copy__ (self): return self
254 + def __deepcopy__(self, memo): return self
255 +
256 + def __reduce__(self):
257 + """
258 + sage: R = IntegerModRing(12)
259 + sage: M = AutomaticMonoid(Family({1: R(3), 2: R(5)}), one = R.one())
260 + sage: bla = M.list()[2]
261 + sage: loads(dumps(bla)) is bla # indirect doctest
262 + True
263 + """
264 + return (unreduce_retract, (self._parent, self.ambient_element,))
265 +
266 + def parent(self):
267 + return self._parent
268 +
269 + def lift(self):
270 + return self.ambient_element
271 +
272 + def reduced_word(self):
273 + """
274 + Returns the length-lexicographic shortest reduced word for
275 + self, or None if it has not yet been computed
276 +
277 + TODO: should it instead trigger the computation?
278 + """
279 + return self._reduced_word
280 +
281 + @cached_method
282 + def transition(self, i):
283 + """
284 + The multiplication on the right by a generator.
285 + Namely, this returns x * self.generators[i]
286 + """
287 + parent = self.parent()
288 + assert(i in parent.generators.keys())
289 + # Do it in to steps, as a temporary workaround for #5843
290 + res = parent.mul(self.lift(), parent.generators_in_ambient[i])
291 + return parent.retract(res)
292 +
293 + def _repr_(self):
294 + rep = self.reduced_word()
295 + if rep is None:
296 + rep = self.lift()
297 + return "%s"%rep
298 +
299 +
300 +def unreduce_retract(parent, ambient_element):
301 + return parent.retract(ambient_element)
