rlsm 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,544 @@
1
+ #
2
+ # This file is part of the RLSM gem.
3
+ #
4
+ #(The MIT License)
5
+ #
6
+ #Copyright (c) 2008 Gunther Diemant <g.diemant@gmx.net>
7
+ #
8
+ #Permission is hereby granted, free of charge, to any person obtaining
9
+ #a copy of this software and associated documentation files (the
10
+ #'Software'), to deal in the Software without restriction, including
11
+ #without limitation the rights to use, copy, modify, merge, publish,
12
+ #distribute, sublicense, and/or sell copies of the Software, and to
13
+ #permit persons to whom the Software is furnished to do so, subject to
14
+ #the following conditions:
15
+ #
16
+ #The above copyright notice and this permission notice shall be
17
+ #included in all copies or substantial portions of the Software.
18
+ #
19
+ #THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
20
+ #EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ #MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
+ #IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23
+ #CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24
+ #TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25
+ #SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ #
27
+
28
+
29
+ require File.join(File.dirname(__FILE__), 'monkey_patching')
30
+ require File.join(File.dirname(__FILE__), 'dfa')
31
+
32
+ # A Monoid is a set of elements with an associative binary operation and a neutral element.
33
+ module RLSM
34
+ class Monoid
35
+
36
+ def initialize(table, options = {})
37
+ options[:normalize] = true if options[:normalize].nil?
38
+
39
+ _create_binary_operation(table)
40
+ _check_form_of_binary_operation
41
+ _check_associativity_of_binary_operation
42
+
43
+ @elements = (0...@order).to_a
44
+
45
+ #If no neutral element exists an exception is thrown, otherwise the
46
+ #neutral element is moved to position 0
47
+ _check_and_rearrange_neutral_element
48
+ _normalize if options[:normalize]
49
+ _create_element_names if options[:create_names]
50
+ end
51
+
52
+
53
+ #--
54
+ #READER METHODS
55
+ #++
56
+ #Returns the order of the monoid.
57
+ attr_reader :order
58
+
59
+ #Returns a copy of the elements of the monoid.
60
+ def elements
61
+ @names.dup
62
+ end
63
+
64
+ #Returns a copy of the binary operation of the monoid.
65
+ def binary_operation
66
+ @bo.deep_copy
67
+ end
68
+
69
+ #Returns the product of the given elements
70
+ def[](a,b)
71
+ a,b = check_arguments(a,b)
72
+ @bo[a][b]
73
+ end
74
+
75
+
76
+ #--
77
+ #SOME PROPERTIES CHECKS
78
+ #++
79
+ #Returns true if the monoid is commutative.
80
+ def commutative?
81
+ return @commutative unless @commutative.nil?
82
+
83
+ @commutative = @elements.tuples.all? { |a,b| self[a,b] == self[b,a] }
84
+ end
85
+
86
+ #Returns true if the given element is idempotent. With no arguments called, returns true if all elements are idempotent.
87
+ def idempotent?(x=nil)
88
+ x = *check_arguments(x) if x
89
+
90
+ x ? self[x,x] == x : @elements.all? { |x| idempotent? x }
91
+ end
92
+
93
+ #Returns true if the monoid is syntactic.
94
+ def syntactic?
95
+ return @syntactic unless @syntactic.nil?
96
+
97
+ #Order 1 is a bit special...
98
+ return(@syntactic = true) if @order == 1
99
+
100
+ disjunctive_subset ? @syntactic = true : @syntactic = false
101
+ end
102
+
103
+ #Returns true if the monoid is aperiodic.
104
+ def aperiodic?
105
+ return @aperiodic unless @aperiodic.nil?
106
+
107
+ @aperiodic ||= h_classes.all? { |hc| hc.size == 1 }
108
+ end
109
+
110
+ #Returns true if the monoid is a group.
111
+ def group?
112
+ return @group unless @proup.nil?
113
+
114
+ @group ||= (idempotents.size == 1)
115
+ end
116
+
117
+ #--
118
+ #DISJUNCTIVE SET STUFF
119
+ #++
120
+ #Returns true if the given set s is a disjunctive subset.
121
+ def subset_disjunctive?(s)
122
+ @elements.tuples.all? do |a,b|
123
+ (a == b) or @elements.tuples.any? do |x,y|
124
+ s.include?(self[x,self[a,y]]) ^ s.include?(self[x,self[b,y]])
125
+ end
126
+ end
127
+ end
128
+
129
+ #Returns the first disjunctive subset if there is any. Otherwise nil is returned.
130
+ def disjunctive_subset
131
+ return [] if @order == 1
132
+
133
+ @disjunctive_subset ||=
134
+ @elements.proper_subsets.find { |s| subset_disjunctive?(s) }
135
+ end
136
+
137
+ #Returns all disjunctive subsets.
138
+ def all_disjunctive_subsets
139
+ @ads ||= @elements.proper_subsets.select { |s| subset_disjunctive?(s) }
140
+ end
141
+
142
+
143
+ #--
144
+ #SUBMONOID STUFF
145
+ #++
146
+ #Returns (one of the) smallest sets of elements, which generates the monoid.
147
+ def generating_subset
148
+ @gs ||= @elements.powerset.find do |s|
149
+ _get_closure_of(s).size == @order
150
+ end
151
+ end
152
+
153
+ #Returns true if this monoid is a submonoid of :other:
154
+ def submonoid_of?(other)
155
+ other.have_submonoid?(self)
156
+ end
157
+
158
+ #Returns true if :other: is a submonoid of this monoid.
159
+ def have_as_submonoid?(other)
160
+ submonoids_of_order(other.order).any? { |m| m == other }
161
+ end
162
+
163
+ #Returns all proper submonoids.
164
+ def proper_submonoids(up_to_iso=true)
165
+ (2..@order-1).to_a.map { |i| submonoids_of_order(i, up_to_iso) }.flatten
166
+ end
167
+
168
+ #Returns all submonoids.
169
+ def submonoids(up_to_iso=true)
170
+ (1..@order).to_a.map { |i| submonoids_of_order(i, up_to_iso) }.flatten
171
+ end
172
+
173
+ #Returns all submonoids of the given order.
174
+ def submonoids_of_order(order, up_to_iso = true)
175
+ pos = @elements.powerset.map { |s|
176
+ _get_closure_of(s) }.select { |s| s.size == order }.uniq.sort
177
+
178
+ pos.inject([]) do |soo,s|
179
+ sm = get_submonoid(s)
180
+ unless up_to_iso and soo.any? { |m| m == sm }
181
+ soo << sm
182
+ end
183
+ soo
184
+ end
185
+ end
186
+
187
+ #Returns the submonoid which is generated by the given set of elements :s:
188
+ def get_submonoid(s)
189
+ s = _get_closure_of(s)
190
+
191
+ table = @bo.values_at(*s).map { |r| r.values_at(*s) }
192
+
193
+ table.map! do |row|
194
+ row.map { |x| s.index(x) }
195
+ end
196
+
197
+ Monoid.new table, :names => @names.values_at(*s), :normalize => false
198
+ end
199
+
200
+ #--
201
+ #ISOMORPHISM STUFF
202
+ #++
203
+ #Returns true, if this monoid is isomorph to :other:.
204
+ def isomorph_to?(other)
205
+ #First a trivial check
206
+ return false if @order != other.order
207
+
208
+ isomorphisms.any? do |i|
209
+ @elements.tuples.all? { |a,b| i[self[a,b]] == other[i[a],i[b]] }
210
+ end
211
+ end
212
+
213
+ #Synonym for isomorph_to?
214
+ def ==(other)
215
+ isomorph_to?(other)
216
+ end
217
+
218
+ #Synonym for isomorph_to?
219
+ def eql?(other)
220
+ isomorph_to?(other)
221
+ end
222
+
223
+ #--
224
+ #GREEN RELATIONS AND IDEAL STUFF
225
+ #++
226
+ #Returns the left ideal of :a:.
227
+ def left_ideal_of(a)
228
+ a = *check_arguments(a)
229
+ @elements.inject([]) { |li,x| li.add? self[x,a]; li }.sort
230
+ end
231
+
232
+ #Returns the right ideal of :a:.
233
+ def right_ideal_of(a)
234
+ a = *check_arguments(a)
235
+ @elements.inject([]) { |ri,x| ri.add? self[a,x]; ri }.sort
236
+ end
237
+
238
+ #Returns the two-sided ideal of :a:.
239
+ def ideal_of(a)
240
+ a = *check_arguments(a)
241
+ res = []
242
+ @elements.tuples.each { |x,y| res.add? self[x,self[a,y]] }
243
+ res.sort
244
+ end
245
+
246
+ #Returns the L-class of :a:.
247
+ def l_class_of(a)
248
+ l_a = left_ideal_of a
249
+ (@elements.select { |x| left_ideal_of(x) == l_a }).sort
250
+ end
251
+
252
+ #Returns the R-class of :a:.
253
+ def r_class_of(a)
254
+ r_a = right_ideal_of a
255
+ (@elements.select { |x| right_ideal_of(x) == r_a }).sort
256
+ end
257
+
258
+ #Returns the H-class of :a:.
259
+ def h_class_of(a)
260
+ (l_class_of(a) & r_class_of(a)).sort
261
+ end
262
+
263
+ #Returns the D-class of :a:.
264
+ def d_class_of(a)
265
+ rc_a = r_class_of a
266
+ (@elements.select { |x| (l_class_of(x) & rc_a).size > 0}).sort
267
+ end
268
+
269
+ #Returns all L classes
270
+ def l_classes
271
+ @elements.map { |x| l_class_of(x) }.uniq
272
+ end
273
+
274
+ #Returns all R classes
275
+ def r_classes
276
+ @elements.map { |x| r_class_of(x) }.uniq
277
+ end
278
+
279
+ #Returns all H classes
280
+ def h_classes
281
+ @elements.map { |x| h_class_of(x) }.uniq
282
+ end
283
+
284
+ #Returns all D classes
285
+ def d_classes
286
+ @elements.map { |x| d_class_of(x) }.uniq
287
+ end
288
+
289
+ #Returns true if L relation is the identity
290
+ def l_trivial?
291
+ l_classes.all? { |lc| lc.size == 1 }
292
+ end
293
+
294
+ #Returns true if R relation is the identity
295
+ def r_trivial?
296
+ r_classes.all? { |rc| rc.size == 1 }
297
+ end
298
+
299
+ #Returns true if D relation is the identity
300
+ def d_trivial?
301
+ d_classes.all? { |dc| dc.size == 1 }
302
+ end
303
+
304
+ #Returns true if H relation is the identity. This is a synonym for aperiodic?
305
+ def h_trivial?
306
+ aperiodic?
307
+ end
308
+
309
+ #--
310
+ #SPECIAL ELEMENTS
311
+ #++
312
+ #Returns the index of the null element if any exists, otherwise false is returned.
313
+ def null_element
314
+ return @null_element unless @null_element.nil?
315
+
316
+ #for a null element, there must exist at least two elements
317
+ return @null_element = false if @order == 1
318
+
319
+ ne = @elements.find do |n|
320
+ @elements.all? { |x| self[n,x] == n and self[x,n] == n }
321
+ end
322
+
323
+ @null_element = ne ? ne : false
324
+ end
325
+
326
+ #Returns true if the given element is a left null, i.e. ay = a for all y
327
+ def left_null?(a)
328
+ a = *check_arguments(a)
329
+ return false if @order == 1
330
+ @elements.all? { |y| self[a,y] == a }
331
+ end
332
+
333
+ #Returns true if the given element is a right null, i.e. ya = a for all y
334
+ def right_null?(a)
335
+ a = *check_arguments(a)
336
+ return false if @order == 1
337
+ @elements.all? { |y| self[y,a] == a }
338
+ end
339
+
340
+ #Returns an array containing all left nulls.
341
+ def left_nulls
342
+ @elements.select { |x| left_null? x }
343
+ end
344
+
345
+ #Returns an array containing all right nulls.
346
+ def right_nulls
347
+ @elements.select { |x| right_null? x }
348
+ end
349
+
350
+ #Returns an array with all idempotent elements. (Remark: the neutral element is always idempotent).
351
+ def idempotents
352
+ @idempotents ||= @elements.select { |x| idempotent? x }
353
+ end
354
+
355
+
356
+ #--
357
+ #MISC
358
+ #++
359
+ #Returns a string representation of the binary operator.
360
+ def to_s
361
+ @bo.map { |r| r.join(@order > 10 ? "," : "") }.join(" ")
362
+ end
363
+
364
+ def inspect # :nodoc:
365
+ "<Monoid #{self.object_id}: {" + @names.join(",") + "};#{to_s}>"
366
+ end
367
+
368
+ def hash # :nodoc:
369
+ isomorphisms.map { |i| bo_str_after_iso(i) }.min
370
+ end
371
+
372
+ #Returns a DFA with the monoid elements as states, the neutral element as initial state and transitions given by the binary operation. The argument gives the final states. If the monoid is syntactic, the finals must be a disjunctive subset. If no argument is given in this case, the smallest disjunctive subset is used.
373
+ def to_dfa(finals = [])
374
+ alph = @names.values_at(generating_subset)
375
+ states = @names.clone
376
+ inital = @names.first
377
+
378
+ if syntactic?
379
+ if finals.empty?
380
+ finals = disjunctive_subset
381
+ else
382
+ unless all_disjunctive_subsets.include? check_arguments(*finals).sort
383
+ raise MonoidException, "finals must be a disjunctive subset"
384
+ end
385
+ end
386
+ end
387
+
388
+ trans = []
389
+ alph.each do |char|
390
+ @names.each { |s1| trans << [char, s1, self[s1,char]] }
391
+ end
392
+
393
+ RLSM::DFA.new alph, states, initial, finals, trans
394
+ end
395
+
396
+ private
397
+ def isomorphisms
398
+ #In every monoid the neutral element is 0, so isomorphisms must
399
+ #map 0 to 0
400
+ @elements[1..-1].permutations.map { |p| p.unshift(0) }
401
+ end
402
+
403
+ def check_arguments(*args)
404
+ #Get the internal representation of each argument
405
+ args.map! { |x| if x.kind_of? Integer then x else @names.index(x) end }
406
+
407
+ unless args.all? { |arg| @elements.include? arg }
408
+ raise MonoidException, "Bad Argument: #{args.inspect}"
409
+ end
410
+
411
+ args
412
+ end
413
+
414
+ def transpose(a,b)
415
+ #somthing to do?
416
+ return if a == b
417
+
418
+ a,b = check_arguments(a,b)
419
+
420
+ #create the transposition
421
+ t = (0...@order).to_a
422
+ t[a], t[b] = b, a
423
+
424
+ #Rename the elements
425
+ @bo = @bo.flatten.map { |x| t[x] }/@order
426
+
427
+ #swap the columns
428
+ @bo.map! { |r| r[a], r[b] = r[t[a]], r[t[b]]; r }
429
+
430
+ #swap the rows
431
+ @bo[a], @bo[b] = @bo[t[a]], @bo[t[b]]
432
+
433
+ #update names
434
+ @names[a], @names[b] = @names[b], @names[a]
435
+ end
436
+
437
+ def bo_str_after_iso(iso)
438
+ bo = @bo.deep_copy
439
+ (0...@order).to_a.tuples.each do |i,j|
440
+ bo[i][j] = iso[@bo[iso.index(i)][iso.index(j)]]
441
+ end
442
+
443
+ bo.map { |r| r.join(@order >10 ? ',' : '') }.join(' ')
444
+ end
445
+
446
+ def _get_closure_of(s)
447
+ res = s.dup
448
+ res = check_arguments(*res)
449
+ res.add? 0
450
+
451
+ order = 1
452
+
453
+ loop do
454
+ order = res.size
455
+ res.tuples.each do |a,b|
456
+ res.add? self[a,b]
457
+ res.add? self[b,a]
458
+ end
459
+
460
+ break if order == res.size
461
+ end
462
+
463
+ res.sort
464
+ end
465
+
466
+ def _create_binary_operation(table)
467
+ if table.instance_of? Array
468
+ @order = table.size
469
+ @bo = table
470
+ elsif table.instance_of? String
471
+ #Normalizing the string, i.e. removing double spaces, trailing newlines...
472
+ table.chomp!
473
+ table.squeeze!(' ')
474
+ table.gsub!(", ", ",")
475
+
476
+ #Take the number of rows as order of the monoid
477
+ @order = table.split.size
478
+
479
+ #Transform now the string in a matrix
480
+ if table.include? ","
481
+ @bo = table.gsub(" ", ",").split(",")/@order
482
+ else
483
+ @bo = table.gsub(" ", "").scan(/./)/@order
484
+ end
485
+ end
486
+
487
+ #Convert to internal represenation
488
+ @names = @bo.flatten.uniq.clone
489
+ @bo = (@bo.flatten.map { |e| @names.index(e) })/@order
490
+ end
491
+
492
+ def _check_form_of_binary_operation
493
+ #Is the matrix quadratic?
494
+ unless @bo.all? { |r| r.size == @order }
495
+ raise MonoidException, "InitError: A binary operation must be quadratic."
496
+ end
497
+
498
+ #Are the matrix elements in the right range?
499
+ unless @bo.flatten.all? { |e| (0...@order).include? e }
500
+ raise MonoidException, "InitError: Too big numbers in description."
501
+ end
502
+ end
503
+
504
+ def _check_associativity_of_binary_operation
505
+ unless (0...@order).to_a.triples.all? do |a,b,c|
506
+ @bo[a][@bo[b][c]] == @bo[@bo[a][b]][c]
507
+ end
508
+ raise MonoidException, "InitError: Given binary operation isn't associative."
509
+ end
510
+ end
511
+
512
+ def _check_and_rearrange_neutral_element
513
+ one = (0...@order).find do |e|
514
+ (0...@order).all? { |x| @bo[e][x] == @bo[x][e] and @bo[e][x] == x }
515
+ end
516
+
517
+ one ? transpose(0,one) : raise(MonoidException, "InitError: Given binary operation has no neutral element.")
518
+ end
519
+
520
+ def _create_element_names
521
+ @names = []
522
+ char = "a"
523
+ @elements.each do |i|
524
+ if i == 0
525
+ @names << "1"
526
+ else
527
+ @names << char.clone
528
+ char.succ!
529
+ end
530
+ end
531
+ end
532
+
533
+ def _normalize
534
+ gs = generating_subset
535
+
536
+ #rearrange such that the generators are the first elements
537
+ #after the neutral element
538
+ gs.each_with_index { |x,i| transpose(x,i+1) }
539
+
540
+ #set the new generating subset
541
+ @gs = (1..gs.size).to_a
542
+ end
543
+ end
544
+ end
@@ -0,0 +1,123 @@
1
+ #
2
+ # This file is part of the RLSM gem.
3
+ #
4
+ #(The MIT License)
5
+ #
6
+ #Copyright (c) 2008 Gunther Diemant <g.diemant@gmx.net>
7
+ #
8
+ #Permission is hereby granted, free of charge, to any person obtaining
9
+ #a copy of this software and associated documentation files (the
10
+ #'Software'), to deal in the Software without restriction, including
11
+ #without limitation the rights to use, copy, modify, merge, publish,
12
+ #distribute, sublicense, and/or sell copies of the Software, and to
13
+ #permit persons to whom the Software is furnished to do so, subject to
14
+ #the following conditions:
15
+ #
16
+ #The above copyright notice and this permission notice shall be
17
+ #included in all copies or substantial portions of the Software.
18
+ #
19
+ #THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
20
+ #EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ #MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
+ #IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23
+ #CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24
+ #TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25
+ #SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ #
27
+
28
+
29
+ require "rubygems"
30
+ require "sqlite3"
31
+ require "singleton"
32
+
33
+ module RLSM
34
+ class MonoidDB
35
+
36
+ Columns = [:binop, :m_order, :num_generators,
37
+ :num_idempotents, :num_left_nulls, :num_right_nulls,
38
+ :syntactic, :idempotent, :aperiodic,
39
+ :commutative, :is_group, :has_null,
40
+ :l_trivial, :r_trivial, :d_trivial]
41
+
42
+ include Singleton
43
+
44
+ attr_reader :db
45
+
46
+ def self.query(query, &block)
47
+ if block_given?
48
+ instance.db.execute(query, &block)
49
+ else
50
+ instance.db.execute(query)
51
+ end
52
+ end
53
+
54
+ def self.find(params = {}, &block)
55
+ if block_given?
56
+ query construct_query(params), &block
57
+ else
58
+ query construct_query(params)
59
+ end
60
+ end
61
+
62
+ def self.count(params = {})
63
+ q = construct_query(params).sub('T binop F',
64
+ "T count(*), total(syntactic) F")
65
+ query(q).first.map { |x| x.to_i }
66
+ end
67
+
68
+ def self.statistic
69
+ res = instance.db.execute2 <<SQL
70
+ SELECT
71
+ m_order AS 'Order',
72
+ count(*) AS 'Total',
73
+ total(syntactic) AS 'Syntactic',
74
+ total(is_group) AS 'Groups',
75
+ total(commutative) AS 'Commutative',
76
+ total(aperiodic) AS 'Aperiodic',
77
+ total(idempotent) AS 'Idempotent'
78
+ FROM monoids
79
+ GROUP BY m_order
80
+ ORDER BY 'Order' ASC;
81
+ SQL
82
+
83
+ desc = res.shift
84
+ res.map! { |row| row.map { |x| x.to_i } }
85
+ res.unshift desc
86
+
87
+ res
88
+ end
89
+
90
+ private
91
+ def initialize
92
+ db_name = File.join(File.dirname(__FILE__), 'data', 'monoids.db')
93
+ @db = SQLite3::Database.open(db_name)
94
+ end
95
+
96
+ def self.construct_query(params)
97
+ limit = ""
98
+ if params[:limit]
99
+ limit = "\nLIMIT #{params[:limit]}"
100
+ params.delete :limit
101
+ if params[:offset]
102
+ limit += " OFFSET #{params[:offset]}"
103
+ end
104
+ end
105
+
106
+ order_by = "\nORDER BY binop #{params[:ordering] || 'ASC'}"
107
+
108
+ params.delete :ordering
109
+
110
+ q = "SELECT binop FROM monoids"
111
+
112
+ where = Columns.map do |col|
113
+ params[col] ? (col.to_s + " = " + params[col]) : nil
114
+ end.compact.join(" AND ")
115
+
116
+ if where.length > 0
117
+ q += "\nWHERE " + where
118
+ end
119
+
120
+ q + order_by + limit + ";"
121
+ end
122
+ end
123
+ end