rlsm 0.2.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +0 -0
- data/Manifest.txt +3 -28
- data/README.txt +0 -0
- data/Rakefile +0 -0
- data/bin/smon +1 -3
- data/lib/data/monoids.db +0 -0
- data/lib/dfa.rb +656 -0
- data/lib/monoid.rb +778 -0
- data/lib/re.rb +492 -0
- data/lib/rlsm.rb +57 -36
- metadata +5 -30
- data/lib/rlsm/dfa.rb +0 -705
- data/lib/rlsm/exceptions.rb +0 -39
- data/lib/rlsm/mgen.rb +0 -138
- data/lib/rlsm/monkey_patching.rb +0 -126
- data/lib/rlsm/monoid.rb +0 -552
- data/lib/rlsm/monoid_db.rb +0 -123
- data/lib/rlsm/regexp.rb +0 -229
- data/lib/rlsm/regexp_nodes/concat.rb +0 -112
- data/lib/rlsm/regexp_nodes/primexp.rb +0 -49
- data/lib/rlsm/regexp_nodes/renodes.rb +0 -95
- data/lib/rlsm/regexp_nodes/star.rb +0 -50
- data/lib/rlsm/regexp_nodes/union.rb +0 -85
- data/lib/smon/commands/db_find.rb +0 -37
- data/lib/smon/commands/db_stat.rb +0 -20
- data/lib/smon/commands/exit.rb +0 -9
- data/lib/smon/commands/help.rb +0 -31
- data/lib/smon/commands/intro.rb +0 -32
- data/lib/smon/commands/monoid.rb +0 -27
- data/lib/smon/commands/quit.rb +0 -10
- data/lib/smon/commands/regexp.rb +0 -20
- data/lib/smon/commands/reload.rb +0 -22
- data/lib/smon/commands/show.rb +0 -21
- data/lib/smon/presenter.rb +0 -18
- data/lib/smon/presenter/txt_presenter.rb +0 -157
- data/lib/smon/smon.rb +0 -79
- data/test/dfa_spec.rb +0 -99
- data/test/monoid_spec.rb +0 -270
- data/test/regexp_spec.rb +0 -25
data/lib/monoid.rb
ADDED
@@ -0,0 +1,778 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'rlsm'))
|
2
|
+
require 'dfa'
|
3
|
+
|
4
|
+
=begin rdoc
|
5
|
+
=The RLSM::Monoid class
|
6
|
+
===Definition of a monoid
|
7
|
+
A _monoid_ is a tuple <tt>(M,B)</tt> where
|
8
|
+
* +M+ is a set of _elements_
|
9
|
+
* <tt>B:M x M -> M</tt> is a _binary_ _operation_
|
10
|
+
with the following properties
|
11
|
+
* the binary operation is associative ( <tt>B(a,B(b,c)) = B(B(a,b),c)</tt> for all <tt>a,b,c</tt> in +M+)
|
12
|
+
* It exists an element +e+ in +M+ with <tt>B(a,e) = B(e,a) = a</tt> for all +a+ in +M+ (the neutral element).
|
13
|
+
|
14
|
+
In theory the set +M+ may be of infinite size, but for obvious reasons we consider here only the finite case. The size of +M+ is called the _order_ of the monoid.
|
15
|
+
|
16
|
+
Also we denote the product of two elements by
|
17
|
+
|
18
|
+
B(x,y) =: xy
|
19
|
+
|
20
|
+
|
21
|
+
===A word on the binary operation
|
22
|
+
Suppose that <tt>M = {e,b,c}</tt>. We can describe the binary operation as a table, for example
|
23
|
+
|
24
|
+
| e | a | b | <-- this row gives only a correspondence between
|
25
|
+
---+---+---+---+ columns and monoid elements (analog the first column,
|
26
|
+
e | e | a | b | which gives a correspondence between rows and elements).
|
27
|
+
---+---+---+---+
|
28
|
+
a | a | a | b |
|
29
|
+
---+---+---+---+
|
30
|
+
b | b | a | b |
|
31
|
+
---+---+---+---+
|
32
|
+
|
33
|
+
For two elements <tt>x,y</tt>, the product +xy+ can be looked up in the table as follows: Say <tt>x = a</tt> and <tt>y = b</tt> the product +ab+ is then the entry in row +a+ and column +b+ (in this case it is +b+).
|
34
|
+
|
35
|
+
This gives us an easy way to express a binary operation in code. It is simply a 2D-array. We see immediatly two restrictions:
|
36
|
+
* Ignoring the descreptive first row and first column, the array must be quadratic
|
37
|
+
* Each entry must be a monoid element
|
38
|
+
|
39
|
+
If we now agree upon the convention that the first row and column belongs to the neutral element, we can discard the row and column description because of the fact, that in this case the first row and column are identical with the descriptions (compare above example).
|
40
|
+
|
41
|
+
===Basic properties of monoids and monoid elements
|
42
|
+
Given two monoids <tt>(M,B), (N,C)</tt> we say the monoids are _isomorph_ _to_ each other iff there exists a bijective map <tt>I : M -> N</tt> such that the equality
|
43
|
+
I(B(x,y)) = C(I(x),I(y))
|
44
|
+
holds for all <tt>x,y</tt> in +M+. The map +I+ is then called an _isomorphism_.
|
45
|
+
|
46
|
+
The monoids are _anti_-_isomorph_ _to_ each other iff there exists a bijective map <tt>A : M -> N </tt> such that the equality
|
47
|
+
A(B(x,y)) = C(A(y),A(x))
|
48
|
+
holds for all <tt>x,y</tt> in +M+. The map +A+ is then called an _anti_-_isomorphism_.
|
49
|
+
|
50
|
+
We say a monoid <tt>(M,B)</tt> is _commutative_ iff the equality
|
51
|
+
B(x,y) = B(y,x)
|
52
|
+
holds for all <tt>x,y</tt> in +M+.
|
53
|
+
|
54
|
+
An element +l+ of +M+ is called a _left_-_zero_ iff for all +x+ in +M+
|
55
|
+
lx = l
|
56
|
+
holds. An element +r+ of +M+ is called a _right_-_zero_ iff for all +x+ in +M+
|
57
|
+
xr = r
|
58
|
+
holds. An element +z+ of +M+ is called a _zero_ _element_ iff for all +x+ in +M+
|
59
|
+
zx = xz = z
|
60
|
+
holds. If it exists, the zero element is unique.
|
61
|
+
|
62
|
+
An element +a+ of +M+ is called a _idempotent_ iff <tt>xx = x</tt>. A monoid +M+ is called _idempotent_ if all elements are idempotent.
|
63
|
+
|
64
|
+
It is easy to see that a monoid is a group (i.e. for all elements +x+ there exists an element +y+ such that <tt>xy = yx = 1</tt> where +1+ is the neutral element) if the neutral element is the only idempotent element.
|
65
|
+
|
66
|
+
===Submonoids and generators
|
67
|
+
Given two monoids <tt>(M,B), (N,C)</tt> we say +M+ is a _submonoid_ of +N+ iff there exists an injective map <tt>S : M -> N</tt> such that the equality
|
68
|
+
S(B(x,y)) = C(S(x),S(y))
|
69
|
+
holds for all <tt>x,y</tt> in +M+ and +M+ is a subset of +N+.
|
70
|
+
|
71
|
+
A submonoid +N+ of +M+ is called a _proper_ _submonoid_ if <tt>N != M</tt> and +N+ has more than one element.
|
72
|
+
|
73
|
+
Let <tt>(M,B)</tt> with <tt>M = {m1,m2,m3,...}</tt> be a monoid and <tt>N = {n1,n2,...}</tt> be a subset of +M+. Then the set
|
74
|
+
<N> := { x | x is the product of arbitary powers of elements in N }
|
75
|
+
is a submonoid of +M+ with +B+ restricted to <tt>NxN</tt>. It is called the submonoid _generated_ _by_ +N+ and +N+ is called the _generator_.
|
76
|
+
|
77
|
+
A _generating_ _subset_ of a Monoid +M+ is a subset +N+ of +M+ such that <tt><N>=M</tt>.
|
78
|
+
|
79
|
+
===Ideals of a monoid and Green Relations
|
80
|
+
Let +M+ be a monoid and +a+ in +M+. We define
|
81
|
+
Ma := {xa | x in M}
|
82
|
+
aM := {ax | x in M}
|
83
|
+
MaM := {xay | x,y in M}
|
84
|
+
and call +Ma+ the _left_ _ideal_ of +a+, +aM+ the _right_ _ideal_ of +a+ and +MaM+ the (_two_-_sided_) _ideal_ of +a+.
|
85
|
+
|
86
|
+
We can now define some equivalent relations on +M+:
|
87
|
+
a =L= b :<=> Ma = Mb
|
88
|
+
a =R= b :<=> aM = bM
|
89
|
+
a =J= b :<=> MaM = MbM
|
90
|
+
a =H= b :<=> a =L= b and a =R= b
|
91
|
+
a =D= b :<=> it exists a c in +M+ such that a =L= c and c =R= b
|
92
|
+
These relations are called the _Green_-_relations_ of the monoid +M+.
|
93
|
+
|
94
|
+
The relations =J= and =D= are the same for finite monoids, so we consider here only the relation =D=.
|
95
|
+
|
96
|
+
The equivalence classes of these relations are called _L_-_class_, _R_-_class_,_J_-_class_,_H_-_class_ and _D_-_class_.
|
97
|
+
|
98
|
+
A monoid is called _L_-_trivial_ iff all L-classes contains only one element. Analog for _R_,_J_,_H_,_D_-_trivial_.
|
99
|
+
|
100
|
+
===Disjunctive subsets and syntactic monoids.
|
101
|
+
A subset +D+ of a monoid +M+ is called a _disjunctive_ _subset_ iff for all <tt>a,b</tt> in +M+ with <tt>a != b</tt> a 'context' <tt>x,y</tt> in +M+ exists such that
|
102
|
+
(xay in N and xby not in N) or vice versa
|
103
|
+
|
104
|
+
A monoid is called a _syntactic_ _monoid_ iff it has a disjunctive subset.
|
105
|
+
|
106
|
+
These definitions are motivated by the formal language theory in theoretical computer science. There one can define the _syntactic_ _monoid_ of a language as the factor monoid given by a congruence relation which depends on the language. It is shown that a monoid is syntactic in this sense iff it has a disjunctive subset.
|
107
|
+
|
108
|
+
Also it is shown that the syntactic monoid of a language is finite iff the language is regular.
|
109
|
+
=end
|
110
|
+
|
111
|
+
class RLSM::Monoid
|
112
|
+
=begin rdoc
|
113
|
+
The new method takes two parameters: the binary_operation and an (more or less) optional options hash.
|
114
|
+
|
115
|
+
The +binary_operation+ parameter should be either an array or a string.
|
116
|
+
If it is an array it must satisfy the following conditions:
|
117
|
+
|
118
|
+
* It is a two dimensional array and the rows are also arrays.
|
119
|
+
* It is quadratic.
|
120
|
+
* Each entry is an element of the monoid
|
121
|
+
* The first row and column belongs to the neutral element
|
122
|
+
|
123
|
+
If +binary_operation+ is a string, it must be of the form
|
124
|
+
|
125
|
+
1abc aabc babc cabc
|
126
|
+
|
127
|
+
Such a string will be transformed in a 2D-array in the following way:
|
128
|
+
1. Each row is seperated by a space
|
129
|
+
2. In a row elements are seperated by ',' (comma) or each element consists of exactly one character (as in the above example)
|
130
|
+
|
131
|
+
The above example will be transformed to
|
132
|
+
[['1','a','b','c'],['a','a','b','c'],['b','a','b','c'],['c','a','b','c']]
|
133
|
+
After the transformation, the same rules as for an array parameter applies.
|
134
|
+
|
135
|
+
Remark: The elements will always converted to a string, even if given an array with only numeric values. So multiplication will always be performed on strings.
|
136
|
+
|
137
|
+
The optional options hash knows the following keys.
|
138
|
+
[<tt>:elements</tt>] Takes an array as value and calls elements= with this array after the construction is complete.
|
139
|
+
[<tt>:normalize</tt>] If given non-nil and non-false value, the normalize method will be called after construction.
|
140
|
+
[<tt>:rename</tt>]If given non-nil and non-false value, the rename_elements method will be called after construction.
|
141
|
+
|
142
|
+
Other keys will be ignored and the order in which the methods will be called is
|
143
|
+
normalize rename elements
|
144
|
+
=end
|
145
|
+
def initialize(binary_operation, options = {})
|
146
|
+
@binary_operation = get_binary_operation_from binary_operation
|
147
|
+
@elements = @binary_operation.first.uniq unless @binary_operation.empty?
|
148
|
+
@order = @binary_operation.size
|
149
|
+
|
150
|
+
validate
|
151
|
+
|
152
|
+
normalize if options[:normalize]
|
153
|
+
rename_elements if options[:rename]
|
154
|
+
self.elements = options[:elements] if options[:elements]
|
155
|
+
end
|
156
|
+
|
157
|
+
attr_reader :binary_operation, :elements, :order
|
158
|
+
|
159
|
+
=begin rdoc
|
160
|
+
Returns the product of the given elements. Raises a MonoidException if one of the arguments isn't a monoid element.
|
161
|
+
=end
|
162
|
+
def [](*args)
|
163
|
+
args.flatten!
|
164
|
+
check_args(args)
|
165
|
+
|
166
|
+
if args.size == 2
|
167
|
+
x,y = args[0], args[1]
|
168
|
+
return @binary_operation[@elements.index(x)][@elements.index(y)]
|
169
|
+
else
|
170
|
+
args[0,2] = self[args[0,2]]
|
171
|
+
return self[*args]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
=begin rdoc
|
176
|
+
Checks if this monoid is isomorph to +other+, if so returns true.
|
177
|
+
=end
|
178
|
+
def isomorph_to?(other)
|
179
|
+
#First a trivial check
|
180
|
+
return false if @order != other.order
|
181
|
+
|
182
|
+
#Search now an isomorphism
|
183
|
+
iso = @elements.permutations.find do |p|
|
184
|
+
@elements.product(@elements).all? do |x,y|
|
185
|
+
px, py = other.elements[p.index(x)], other.elements[p.index(y)]
|
186
|
+
|
187
|
+
other.elements[p.index(self[x,y])] == other[px,py]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
#Did we found an isomorphism?
|
192
|
+
!iso.nil?
|
193
|
+
end
|
194
|
+
|
195
|
+
=begin rdoc
|
196
|
+
Checks if this monoid is anti-isomorph to +other+, if so returns true.
|
197
|
+
=end
|
198
|
+
def anti_isomorph_to?(other)
|
199
|
+
transposed = (0...@order).map do |i|
|
200
|
+
@binary_operation.map { |row| row[i].clone }
|
201
|
+
end
|
202
|
+
|
203
|
+
RLSM::Monoid.new(transposed).isomorph_to?(other)
|
204
|
+
end
|
205
|
+
|
206
|
+
=begin rdoc
|
207
|
+
Checks if the monoid is equal to +other+, i.e. the identity map is an isomorphism.
|
208
|
+
=end
|
209
|
+
def ==(other)
|
210
|
+
return false unless @elements == other.elements
|
211
|
+
return false unless @binary_operation == other.binary_operation
|
212
|
+
|
213
|
+
true
|
214
|
+
end
|
215
|
+
|
216
|
+
=begin rdoc
|
217
|
+
Checks if the monoid is commutative, if so returns true.
|
218
|
+
=end
|
219
|
+
def commutative?
|
220
|
+
@elements.product(@elements).all? { |x,y| self[x,y] == self[y,x] }
|
221
|
+
end
|
222
|
+
|
223
|
+
=begin rdoc
|
224
|
+
Returns the submonoid which is generated by the given elements. If one of the given elements isn't a monoid element, an MonoidException is raised.
|
225
|
+
=end
|
226
|
+
def get_submonoid(*args)
|
227
|
+
element_indices = get_closure_of(args).map { |x| @elements.index(x) }
|
228
|
+
|
229
|
+
RLSM::Monoid.new(@binary_operation.values_at(*element_indices).
|
230
|
+
map { |r| r.values_at *element_indices } )
|
231
|
+
end
|
232
|
+
|
233
|
+
=begin rdoc
|
234
|
+
Returns an array of all submonoids of this monoid. The array is sorted in lexicographical order of the submonoid elements.
|
235
|
+
=end
|
236
|
+
def submonoids
|
237
|
+
@elements.powerset.map { |s| get_closure_of(s) }.uniq.sort_lex.map do |s|
|
238
|
+
get_submonoid(s)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
=begin rdoc
|
243
|
+
Returns an array of all proper submonoids of this monoid. The array is sorted in lexicographical order of the submonoid elements.
|
244
|
+
=end
|
245
|
+
def proper_submonoids
|
246
|
+
submonoids.reject { |m| [1,@order].include? m.order }
|
247
|
+
end
|
248
|
+
|
249
|
+
=begin rdoc
|
250
|
+
Returns true if this monoid is a submonoid of +other+.
|
251
|
+
=end
|
252
|
+
def submonoid_of?(other)
|
253
|
+
other.submonoids.include? self
|
254
|
+
end
|
255
|
+
|
256
|
+
#A synonym for submonoid_of?
|
257
|
+
def <=(other)
|
258
|
+
submonoid_of?(other)
|
259
|
+
end
|
260
|
+
|
261
|
+
=begin rdoc
|
262
|
+
Returns true if this monoid is a proper submonoid of +other+.
|
263
|
+
=end
|
264
|
+
def proper_submonoid_of?(other)
|
265
|
+
other.proper_submonoids.include? self
|
266
|
+
end
|
267
|
+
|
268
|
+
#A synonym for proper_submonoid_of?
|
269
|
+
def <(other)
|
270
|
+
proper_submonoid_of?(other)
|
271
|
+
end
|
272
|
+
|
273
|
+
=begin rdoc
|
274
|
+
Returns true if this monoid has +other+ as a submonoid
|
275
|
+
=end
|
276
|
+
def has_as_submonoid?(other)
|
277
|
+
other.submonoid_of?(self)
|
278
|
+
end
|
279
|
+
|
280
|
+
#A synonym for has_as_submonoid?
|
281
|
+
def >=(other)
|
282
|
+
has_as_submonoid?(other)
|
283
|
+
end
|
284
|
+
|
285
|
+
=begin rdoc
|
286
|
+
Returns true if this monoid has +other+ as a proper submonoid
|
287
|
+
=end
|
288
|
+
def has_as_proper_submonoid?(other)
|
289
|
+
other.proper_submonoid_of?(self)
|
290
|
+
end
|
291
|
+
|
292
|
+
#A synonym for has_as_proper_submonoid?
|
293
|
+
def >(other)
|
294
|
+
has_as_proper_submonoid?(other)
|
295
|
+
end
|
296
|
+
|
297
|
+
=begin rdoc
|
298
|
+
Returns the lexicographical smallest subset which generates the monoid
|
299
|
+
=end
|
300
|
+
def generating_subset
|
301
|
+
@elements.powerset.find { |s| get_closure_of(s).size == @order }
|
302
|
+
end
|
303
|
+
|
304
|
+
=begin rdoc
|
305
|
+
Returns the right ideal of the given element. Raises a MonoidException if the given element isn't in the monoid.
|
306
|
+
=end
|
307
|
+
def right_ideal_of(element)
|
308
|
+
check_args(element)
|
309
|
+
|
310
|
+
@binary_operation[@elements.index(element)].uniq.sort
|
311
|
+
end
|
312
|
+
|
313
|
+
=begin rdoc
|
314
|
+
Returns the left ideal of the given element. Raises a MonoidException if the given element isn't in the monoid.
|
315
|
+
=end
|
316
|
+
def left_ideal_of(element)
|
317
|
+
check_args(element)
|
318
|
+
|
319
|
+
i = @elements.index(element)
|
320
|
+
@binary_operation.map { |row| row[i] }.uniq.sort
|
321
|
+
end
|
322
|
+
|
323
|
+
=begin rdoc
|
324
|
+
Returns the (two-sided) ideal of the given element. Raises a MonoidException if the given element isn't in the monoid.
|
325
|
+
=end
|
326
|
+
def ideal_of(element)
|
327
|
+
@elements.product(@elements).inject([]) do |res,xy|
|
328
|
+
x,y = xy.first, xy.last
|
329
|
+
res << self[x,element,y] unless res.include? self[x,element,y]
|
330
|
+
res.sort
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
=begin rdoc
|
335
|
+
Returns the L-class of the given element. Raises a MonoidException if the given element isn't a monoid element.
|
336
|
+
=end
|
337
|
+
def l_class_of(element)
|
338
|
+
l = left_ideal_of(element)
|
339
|
+
@elements.select { |x| left_ideal_of(x) == l }
|
340
|
+
end
|
341
|
+
|
342
|
+
=begin rdoc
|
343
|
+
Returns all different L-classes of the monoid ordered by the lexicographical smallest element of each class
|
344
|
+
=end
|
345
|
+
def l_classes
|
346
|
+
@elements.map { |x| l_class_of(x) }.uniq
|
347
|
+
end
|
348
|
+
|
349
|
+
=begin rdoc
|
350
|
+
Returns true if the monoid is L-trivial.
|
351
|
+
=end
|
352
|
+
def l_trivial?
|
353
|
+
l_classes.all? { |l| l.size == 1 }
|
354
|
+
end
|
355
|
+
|
356
|
+
=begin rdoc
|
357
|
+
Returns the R-class of the given element. Raises a MonoidException if the given element isn't a monoid element.
|
358
|
+
=end
|
359
|
+
def r_class_of(element)
|
360
|
+
r = right_ideal_of(element)
|
361
|
+
@elements.select { |x| right_ideal_of(x) == r }
|
362
|
+
end
|
363
|
+
=begin rdoc
|
364
|
+
Returns all different R-classes of the monoid ordered by the lexicographical smallest element of each class
|
365
|
+
=end
|
366
|
+
def r_classes
|
367
|
+
@elements.map { |x| r_class_of(x) }.uniq
|
368
|
+
end
|
369
|
+
|
370
|
+
=begin rdoc
|
371
|
+
Returns true if the monoid is R-trivial.
|
372
|
+
=end
|
373
|
+
def r_trivial?
|
374
|
+
r_classes.all? { |r| r.size == 1 }
|
375
|
+
end
|
376
|
+
|
377
|
+
=begin rdoc
|
378
|
+
Returns the H-class of the given element. Raises a MonoidException if the given element isn't a monoid element.
|
379
|
+
=end
|
380
|
+
def h_class_of(element)
|
381
|
+
l_class_of(element) & r_class_of(element)
|
382
|
+
end
|
383
|
+
|
384
|
+
=begin rdoc
|
385
|
+
Returns all different H-classes of the monoid ordered by the lexicographical smallest element of each class
|
386
|
+
=end
|
387
|
+
def h_classes
|
388
|
+
@elements.map { |x| h_class_of(x) }.uniq
|
389
|
+
end
|
390
|
+
|
391
|
+
=begin rdoc
|
392
|
+
Returns true if the monoid is H-trivial.
|
393
|
+
=end
|
394
|
+
def h_trivial?
|
395
|
+
h_classes.all? { |h| h.size == 1 }
|
396
|
+
end
|
397
|
+
|
398
|
+
=begin rdoc
|
399
|
+
Returns the D-class of the given element. Raises a MonoidException if the given element isn't a monoid element.
|
400
|
+
=end
|
401
|
+
def d_class_of(element)
|
402
|
+
d = ideal_of(element)
|
403
|
+
@elements.select { |x| ideal_of(x) == d }
|
404
|
+
end
|
405
|
+
|
406
|
+
=begin rdoc
|
407
|
+
Returns all different D-classes of the monoid ordered by the lexicographical smallest element of each class
|
408
|
+
=end
|
409
|
+
def d_classes
|
410
|
+
@elements.map { |x| d_class_of(x) }.uniq
|
411
|
+
end
|
412
|
+
|
413
|
+
=begin rdoc
|
414
|
+
Returns true if the monoid is D-trivial.
|
415
|
+
=end
|
416
|
+
def d_trivial?
|
417
|
+
d_classes.all? { |d| d.size == 1 }
|
418
|
+
end
|
419
|
+
|
420
|
+
#Synonym for d_class_of (in a finite monoid =D= is the same as =J=)
|
421
|
+
def j_class_of(element)
|
422
|
+
d_class_of(element)
|
423
|
+
end
|
424
|
+
|
425
|
+
#Synonym for d_classes (in a finite monoid =D= is the same as =J=)
|
426
|
+
def j_classes
|
427
|
+
d_classes
|
428
|
+
end
|
429
|
+
|
430
|
+
#Synonym for d_trivial? (in a finite monoid =D= is the same as =J=)
|
431
|
+
def j_trivial?
|
432
|
+
d_trivial?
|
433
|
+
end
|
434
|
+
|
435
|
+
=begin rdoc
|
436
|
+
Returns true if the given element is idempotent.
|
437
|
+
=end
|
438
|
+
def idempotent?(x = nil)
|
439
|
+
x ? x == self[x,x] : @elements.all? { |x| idempotent?(x) }
|
440
|
+
end
|
441
|
+
|
442
|
+
=begin rdoc
|
443
|
+
Returns all idempotent elements of the monoid.
|
444
|
+
=end
|
445
|
+
def idempotents
|
446
|
+
@elements.select { |x| idempotent?(x) }
|
447
|
+
end
|
448
|
+
|
449
|
+
=begin rdoc
|
450
|
+
Returns true if the monoid is also a group.
|
451
|
+
=end
|
452
|
+
def group?
|
453
|
+
idempotents.size == 1
|
454
|
+
end
|
455
|
+
|
456
|
+
=begin rdoc
|
457
|
+
Returns true if the given element is a left zero. Raises a MonoidException if the given element isn't a monoid element.
|
458
|
+
=end
|
459
|
+
def left_zero?(element)
|
460
|
+
return false if @order == 1
|
461
|
+
@elements.all? { |x| self[element,x] == element }
|
462
|
+
end
|
463
|
+
|
464
|
+
=begin rdoc
|
465
|
+
Returns all left zeros of the monoid.
|
466
|
+
=end
|
467
|
+
def left_zeros
|
468
|
+
@elements.select { |x| left_zero?(x) }
|
469
|
+
end
|
470
|
+
|
471
|
+
=begin rdoc
|
472
|
+
Returns true if the given element is a right zero. Raises a MonoidException if the given element isn't a monoid element.
|
473
|
+
=end
|
474
|
+
def right_zero?(element)
|
475
|
+
return false if @order == 1
|
476
|
+
@elements.all? { |x| self[x,element] == element }
|
477
|
+
end
|
478
|
+
|
479
|
+
=begin rdoc
|
480
|
+
Returns all right zeros of the monoid.
|
481
|
+
=end
|
482
|
+
def right_zeros
|
483
|
+
@elements.select { |x| right_zero?(x) }
|
484
|
+
end
|
485
|
+
|
486
|
+
=begin rdoc
|
487
|
+
Returns the neutral element.
|
488
|
+
=end
|
489
|
+
def neutral_element
|
490
|
+
@elements.first.dup
|
491
|
+
end
|
492
|
+
|
493
|
+
=begin rdoc
|
494
|
+
Returns the zero element if it exists, nil otherwise.
|
495
|
+
=end
|
496
|
+
def zero_element
|
497
|
+
@elements.find { |x| left_zero?(x) and right_zero?(x) }
|
498
|
+
end
|
499
|
+
|
500
|
+
|
501
|
+
=begin rdoc
|
502
|
+
Returns true if the given set (as an array) is disjunctive.
|
503
|
+
=end
|
504
|
+
def subset_disjunctive?(set)
|
505
|
+
check_args(set)
|
506
|
+
|
507
|
+
tup = @elements.product(@elements)
|
508
|
+
|
509
|
+
tup.all? do |a,b|
|
510
|
+
a == b or tup.any? do |x,y|
|
511
|
+
set.include?(self[x,a,y]) ^ set.include?(self[x,b,y])
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
=begin rdoc
|
517
|
+
Returns the lexicographical smallest subset which is disjunctive.
|
518
|
+
=end
|
519
|
+
def disjunctive_subset
|
520
|
+
@elements.powerset.find { |s| subset_disjunctive? s }
|
521
|
+
end
|
522
|
+
|
523
|
+
=begin rdoc
|
524
|
+
Returns all disjunctive subsets of the monoid in lexicographical order.
|
525
|
+
=end
|
526
|
+
def all_disjunctive_subsets
|
527
|
+
@elements.powerset.select { |s| subset_disjunctive? s }
|
528
|
+
end
|
529
|
+
|
530
|
+
=begin rdoc
|
531
|
+
Returns true if the monoid is syntactic.
|
532
|
+
=end
|
533
|
+
def syntactic?
|
534
|
+
!disjunctive_subset.nil?
|
535
|
+
end
|
536
|
+
|
537
|
+
=begin rdoc
|
538
|
+
Returns true if the monoid is aperiodic. (A synonym for h_trivial?)
|
539
|
+
=end
|
540
|
+
def aperiodic?
|
541
|
+
h_trivial?
|
542
|
+
end
|
543
|
+
|
544
|
+
def to_s # :nodoc:
|
545
|
+
sep = ''
|
546
|
+
sep = ',' if @elements.any? { |x| x.length > 1 }
|
547
|
+
@binary_operation.map { |row| row.join(sep) }.join(' ')
|
548
|
+
end
|
549
|
+
|
550
|
+
def inspect # :nodoc:
|
551
|
+
"<#{self.class} : {#{@elements.join(',')}}; #{to_s}>"
|
552
|
+
end
|
553
|
+
|
554
|
+
=begin rdoc
|
555
|
+
Arranges the elements in such a way that the generators follows directly the neutral element.
|
556
|
+
=end
|
557
|
+
def normalize
|
558
|
+
#new element order
|
559
|
+
elements =
|
560
|
+
[@elements.first] +
|
561
|
+
generating_subset +
|
562
|
+
(@elements[(1..-1)] - generating_subset)
|
563
|
+
|
564
|
+
indices = elements.map { |x| @elements.index(x) }
|
565
|
+
|
566
|
+
#Adjust the binaray operation
|
567
|
+
@binary_operation = @binary_operation.values_at(*indices).map do |row|
|
568
|
+
row.values_at(*indices)
|
569
|
+
end
|
570
|
+
|
571
|
+
#Adjust the elements
|
572
|
+
@elements = elements
|
573
|
+
|
574
|
+
self
|
575
|
+
end
|
576
|
+
|
577
|
+
=begin rdoc
|
578
|
+
Renames the elements to 1,a,b,c,d,e,... (see also elements=). It may be a little bit confusing if the monoid has more than 27 elements, because then the 28th element is named 'aa' which should not confused with the product in the monoid.
|
579
|
+
=end
|
580
|
+
def rename_elements
|
581
|
+
#Create the new elements
|
582
|
+
eles = ['1']
|
583
|
+
if @order > 1
|
584
|
+
eles << 'a'
|
585
|
+
eles << eles.last.succ while eles.size < @order
|
586
|
+
end
|
587
|
+
|
588
|
+
self.elements = eles
|
589
|
+
self
|
590
|
+
end
|
591
|
+
|
592
|
+
=begin rdoc
|
593
|
+
Renames the elements to the given array. Each array entry will be converted to a string.
|
594
|
+
|
595
|
+
A MonoidException will be raised if
|
596
|
+
* the given array has the wrong size
|
597
|
+
* the given array has duplicated elements (e.g. ['a','b','a'])
|
598
|
+
=end
|
599
|
+
def elements=(els)
|
600
|
+
els.map! { |x| x.to_s }
|
601
|
+
|
602
|
+
if els.size != @order
|
603
|
+
raise MonoidException, "Wrong number of elements given!"
|
604
|
+
elsif els.uniq!
|
605
|
+
raise MonoidException, "Given elements aren't unique!"
|
606
|
+
end
|
607
|
+
|
608
|
+
|
609
|
+
|
610
|
+
@binary_operation.map! do |row|
|
611
|
+
row.map { |x| els[@elements.index(x)] }
|
612
|
+
end
|
613
|
+
|
614
|
+
@elements = els
|
615
|
+
end
|
616
|
+
=begin rdoc
|
617
|
+
Returns a DFA which has the elements as states, the binary operation as transitions and the neutral element as initial state.
|
618
|
+
|
619
|
+
As optional parameter one may pass an array of elements which should become the final states. If the monoid is syntactic, these finals must be a disjunctive subset.
|
620
|
+
|
621
|
+
Also if the monoid is syntactic the set returned by disjunctive subset will be used as the finals as default.
|
622
|
+
=end
|
623
|
+
def to_dfa(finals = [])
|
624
|
+
check_args *finals
|
625
|
+
if generating_subset == []
|
626
|
+
return RLSM::DFA.new(:alphabet => [], :states => @elements,
|
627
|
+
:initial => neutral_element,
|
628
|
+
:finals => finals, :transitions => [])
|
629
|
+
end
|
630
|
+
|
631
|
+
if syntactic?
|
632
|
+
if finals.empty?
|
633
|
+
finals = disjunctive_subset
|
634
|
+
else
|
635
|
+
unless all_disjunctive_subsets.include? finals.sort
|
636
|
+
raise MonoidException, "Given finals aren't a disjunctive subset."
|
637
|
+
end
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
RLSM::DFA.create(:initial => neutral_element,
|
642
|
+
:finals => finals,
|
643
|
+
:transitions => get_transitions)
|
644
|
+
end
|
645
|
+
|
646
|
+
private
|
647
|
+
def get_transitions
|
648
|
+
trans = []
|
649
|
+
generating_subset.each do |l|
|
650
|
+
@elements.each do |s|
|
651
|
+
trans << [l,s,self[s,l]]
|
652
|
+
end
|
653
|
+
end
|
654
|
+
trans
|
655
|
+
end
|
656
|
+
|
657
|
+
def check_args(*args)
|
658
|
+
args.flatten!
|
659
|
+
bad = args.find_all { |x| !@elements.include? x }
|
660
|
+
|
661
|
+
if bad.size == 1
|
662
|
+
raise MonoidException, "Bad argument: #{bad[0]}"
|
663
|
+
elsif bad.size > 1
|
664
|
+
raise MonoidException, "Bad arguments: #{bad.join(',')}"
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
def get_closure_of(*args)
|
669
|
+
args.flatten!
|
670
|
+
check_args(args)
|
671
|
+
|
672
|
+
#Add the neutral element if necassary
|
673
|
+
args.unshift @elements.first unless args.include? @elements.first
|
674
|
+
|
675
|
+
searching = true
|
676
|
+
while searching
|
677
|
+
searching = false
|
678
|
+
|
679
|
+
args.product(args).each do |x,y|
|
680
|
+
unless args.include? self[x,y]
|
681
|
+
args << self[x,y]
|
682
|
+
searching = true
|
683
|
+
end
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
args.sort { |x,y| @elements.index(x) <=> @elements.index(y) }
|
688
|
+
end
|
689
|
+
|
690
|
+
def get_binary_operation_from(bo)
|
691
|
+
if bo.class == String
|
692
|
+
return from_string_to_array(bo)
|
693
|
+
else
|
694
|
+
begin
|
695
|
+
return bo.map { |r| r.map { |x| x.to_s } }
|
696
|
+
rescue
|
697
|
+
raise MonoidException, "Something went wrong."
|
698
|
+
end
|
699
|
+
end
|
700
|
+
end
|
701
|
+
|
702
|
+
def from_string_to_array(bo)
|
703
|
+
#Reduce multiple spaces and spaces before or after commas
|
704
|
+
bo.squeeze!(' ')
|
705
|
+
bo.gsub!(', ', ',')
|
706
|
+
bo.gsub!(' ,', ',')
|
707
|
+
|
708
|
+
bo.split.map { |row| split_rows(row) }
|
709
|
+
end
|
710
|
+
|
711
|
+
def split_rows(r)
|
712
|
+
if r.include? ','
|
713
|
+
return r.split(',')
|
714
|
+
else
|
715
|
+
return r.scan(/./)
|
716
|
+
end
|
717
|
+
end
|
718
|
+
|
719
|
+
def validate
|
720
|
+
validate_form_of_binary_operation
|
721
|
+
validate_elements
|
722
|
+
validate_neutral_element
|
723
|
+
validate_associativity
|
724
|
+
end
|
725
|
+
|
726
|
+
def validate_form_of_binary_operation
|
727
|
+
if @binary_operation.empty?
|
728
|
+
raise(MonoidException,
|
729
|
+
"No binary operation given!")
|
730
|
+
end
|
731
|
+
|
732
|
+
unless @binary_operation.all? { |r| r.size == @binary_operation.size }
|
733
|
+
raise(MonoidException,
|
734
|
+
"A binary operation must be quadratic!")
|
735
|
+
end
|
736
|
+
end
|
737
|
+
|
738
|
+
def validate_elements
|
739
|
+
#Have we enough elements
|
740
|
+
unless @elements.size == @order
|
741
|
+
raise(MonoidException,
|
742
|
+
"Expected #@order elements, but got #{@elements.join(',')}")
|
743
|
+
end
|
744
|
+
|
745
|
+
#All elements of the table are known?
|
746
|
+
unless @binary_operation.flatten.all? { |x| @elements.include? x }
|
747
|
+
raise(MonoidException,
|
748
|
+
"There are too many elements in the binary operation.")
|
749
|
+
end
|
750
|
+
end
|
751
|
+
|
752
|
+
def validate_neutral_element
|
753
|
+
#By convention 0 is the index of the neutral element, check this
|
754
|
+
unless @elements.all? do |x|
|
755
|
+
@binary_operation[0][@elements.index(x)] == x and
|
756
|
+
@binary_operation[@elements.index(x)][0] == x
|
757
|
+
end
|
758
|
+
raise(MonoidException,
|
759
|
+
"Convention violated. #{@elements.first} is not a neutral element.")
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
def validate_associativity
|
764
|
+
#Search for a triple which violates the associativity
|
765
|
+
nat = @elements.product(@elements,@elements).find do |triple|
|
766
|
+
x,y,z = triple.map { |a| @elements.index(a) }
|
767
|
+
@binary_operation[x][@elements.index(@binary_operation[y][z])] !=
|
768
|
+
@binary_operation[@elements.index(@binary_operation[x][y])][z]
|
769
|
+
end
|
770
|
+
|
771
|
+
#Found one?
|
772
|
+
if nat
|
773
|
+
err_str = "#{nat[0]}(#{nat[1]}#{nat[2]}) != (#{nat[0]}#{nat[1]})#{nat[2]}"
|
774
|
+
raise(MonoidException,
|
775
|
+
"Given binary operation is not associative: #{err_str}")
|
776
|
+
end
|
777
|
+
end
|
778
|
+
end
|