logic_tools 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,481 @@
1
+ #########################################################################
2
+ # Algorithm for computing the minimal column covers of a boolean matrix #
3
+ #########################################################################
4
+
5
+ # require 'set'
6
+ require 'timeout'
7
+
8
+ module LogicTools
9
+
10
+ ## Converts a +product+ of sum to a sum of product.
11
+ #
12
+ # NOTE: * Both the input are outputs are represented as array of arrays.
13
+ def to_sum_product_array(product)
14
+ return product[0].map {|term| [term] } if product.size == 1
15
+ # Generate the initial terms.
16
+ sum = product[0].product(product[1])
17
+ sum.each {|term| term.sort!.uniq! }
18
+ sum.uniq!
19
+ # Fill then with each factor to the resulting sum of product.
20
+ # print "sum = #{sum}, product=#{product}\n"
21
+ (2..(product.size-1)).each do |i|
22
+ sum.map! do |term|
23
+ # # print "mapping #{product[i]}\n"
24
+ set = []
25
+ product[i].each do |fact|
26
+ if term.include?(fact) then
27
+ set << term unless set.include?(term)
28
+ else
29
+ nterm = term.clone
30
+ nterm << fact
31
+ nterm.sort!
32
+ set << nterm
33
+ end
34
+ end
35
+ set
36
+ end
37
+ sum.flatten!(1)
38
+ # print "then sum=#{sum}\n"
39
+ sum.uniq!
40
+ # print "now sum=#{sum}\n"
41
+ # pid, size = `ps ax -o pid,rss | grep -E "^[[:space:]]*#{$$}"`.strip.split.map(&:to_i)
42
+ # print "memory usage=#{size}\n"
43
+ end
44
+ # print "\n"
45
+ return sum
46
+ end
47
+
48
+
49
+ ## Class for storing and counting occurences of objects.
50
+ class HashCounter < Hash
51
+
52
+ ## Creates a new hash counter.
53
+ def initialize
54
+ self.default = 0
55
+ end
56
+
57
+ ## Increments the number of +element+.
58
+ def inc(element)
59
+ self[elem] += 1
60
+ end
61
+
62
+ ## Decrements the number of +element+.
63
+ def dec(element)
64
+ if (self[elem] -= 1) == 0 then
65
+ # No more instance of the element, remove the entry.
66
+ self.delete(elem)
67
+ end
68
+ end
69
+ end
70
+
71
+
72
+ ## Class for applying branch and bound for extracting from a product of sums
73
+ # the smallest term of the corresponding sum of products.
74
+ #
75
+ # Attributes:
76
+ # +product+:: the product to extract the term from.
77
+ # +cur_term+:: the current term.
78
+ # +best_term+:: the best term found.
79
+ # +best_cost+:: the best cost.
80
+ # +deadline+:: time before returning the current best solution.
81
+ # +time+:: initial time.
82
+ class SmallestSumTerm
83
+
84
+ ## Creates the solver for a product.
85
+ def initialize(product, deadline = Float::INFINITY)
86
+ @product = product
87
+ @cur_term = []
88
+ # @cur_term = HashCounter.new
89
+ @best_term = nil
90
+ @best_cost = @cur_cost = Float::INFINITY
91
+ @deadline = deadline
92
+ end
93
+
94
+ ## Selects a +term+ for solution.
95
+ def make_best(term)
96
+ # print "make_best\n"
97
+ @best_term = term.uniq
98
+ @best_cost = @best_term.size
99
+ end
100
+
101
+ ## Bounds a partial +term+.
102
+ #
103
+ # # NOTE: may modify term through uniq! (for performance purpose.)
104
+ # NOTE: It is assumed that term is hash-like
105
+ def bound(term)
106
+ # if Time.now - @time >= @deadline and
107
+ # @best_cost < Float::INFINITY then
108
+ # # Time over, force a high cost.
109
+ # return Float::INFINITY
110
+ # end
111
+ if (term.size >= @best_cost) then
112
+ return term.uniq.size
113
+ else
114
+ return term.size
115
+ end
116
+ # return term.size
117
+ end
118
+
119
+ ## Solves the problem using branch and bound.
120
+ def solve()
121
+ # Solve the problem throughly.
122
+ begin
123
+ Timeout::timeout(@deadline) {
124
+ self.branch(0)
125
+ }
126
+ rescue Timeout::Error
127
+ # Time out, is there a solution?
128
+ # print "Timeout!\n"
129
+ unless @best_term
130
+ # No, quickly create one including the first element
131
+ # of each factor.
132
+ @best_term = @product.map {|fact| fact[0] }
133
+ @best_term.uniq!
134
+ end
135
+ end
136
+ return @best_term
137
+ end
138
+
139
+ ## Branch in the branch and bound algorithm.
140
+ def branch(pi)
141
+ # # Start the timer if required.
142
+ # @time = Time.now if (pi == 0)
143
+ # # Check the deadline.
144
+ # if Time.now - @time >= @deadline and
145
+ # @best_cost < Float::INFINITY then
146
+ # # Time over, end here.
147
+ # return @best_term
148
+ # end
149
+ # Bound the current term.
150
+ if (self.bound(@cur_term) < @best_cost)
151
+ # Better bound, can go on with the current solution.
152
+ if pi == @product.size then
153
+ # But this is the end, so update the best term.
154
+ make_best(@cur_term)
155
+ else
156
+ # Still a possible solution, recurse.
157
+ @product[pi].each do |elem|
158
+ @cur_term.push(elem)
159
+ # @cur_term.inc(elem)
160
+ # solve(pi+1)
161
+ branch(pi+1)
162
+ @cur_term.pop
163
+ # @cur_term.dec(elem)
164
+ end
165
+ end
166
+ end
167
+ return @best_term
168
+ end
169
+ end
170
+
171
+
172
+ ## Extracts from a +product+ of sums the smallest term of the corresponding
173
+ # sum of products.
174
+ #
175
+ # NOTE: * Both the input are outputs are represented as array of arrays.
176
+ # * Uses a branch and bound algorithm.
177
+ def smallest_sum_term(product, deadline = Float::INFINITY)
178
+ return [product[0][0]] if product.size == 1
179
+
180
+ # Create the solver and applies it
181
+ return SmallestSumTerm.new(product,deadline).solve
182
+ end
183
+
184
+ ## Computes the minimal column covers of a boolean +matrix+.
185
+ #
186
+ # If +smallest+ is set to one, the method returns the smallest minimal
187
+ # column cover instead.
188
+ #
189
+ # The +matrix+ is assumed to be an array of string, each string
190
+ # representing a boolean row ("0" for false and "1" for true).
191
+ def minimal_column_covers(matrix, smallest = false,
192
+ deadline = Float::INFINITY)
193
+ # print "matrix=#{matrix}\n"
194
+
195
+ # Step 1: reduce the matrix for faster processing.
196
+ # First put appart the essential columns.
197
+ essentials = []
198
+ matrix.each do |row|
199
+ col = nil
200
+ row.each_byte.with_index do |c,i|
201
+ # if c == "1" then
202
+ if c == 49 then
203
+ if col then
204
+ # The row has several "1", no essential column there.
205
+ col = nil
206
+ break
207
+ end
208
+ col = i
209
+ end
210
+ end
211
+ # An essential column is found.
212
+ essentials << col if col
213
+ end
214
+ essentials.uniq!
215
+ # print "essentials = #{essentials}\n"
216
+ # The remove the rows covered by essential columns.
217
+ keep = [ true ] * matrix.size
218
+ essentials.each do |col|
219
+ matrix.each.with_index do |row,i|
220
+ # keep[i] = false if row[col] == "1"
221
+ keep[i] = false if row.getbyte(col) == 49
222
+ end
223
+ end
224
+ # print "keep = #{keep}\n"
225
+ reduced = matrix.select.with_index {|row,i| keep[i] }
226
+ # print "matrix = #{matrix}\n"
227
+ # print "reduced = #{reduced}\n"
228
+ if reduced.empty? then
229
+ # Essentials columns are enough for the cover, end here.
230
+ if smallest then
231
+ return essentials
232
+ else
233
+ return [ essentials ]
234
+ end
235
+ end
236
+
237
+ to_optimize = false
238
+ removed_columns = []
239
+ begin
240
+ to_optimize = false
241
+ # Then remove the dominating rows
242
+ reduced.uniq!
243
+ reduced = reduced.select.with_index do |row0,i|
244
+ ! reduced.find.with_index do |row1,j|
245
+ if i == j then
246
+ false
247
+ else
248
+ # The row is dominating if in includes another row.
249
+ res = row0.each_byte.with_index.find do |c,j|
250
+ # row1[j] == "1" and c == "0"
251
+ row1.getbyte(j) == 49 and c == 48
252
+ end
253
+ # Not dominating if res
254
+ !res
255
+ end
256
+ end
257
+ end
258
+
259
+ # # Finally remove the dominated columns if only one column cover
260
+ # # is required.
261
+ # if smallest and reduced.size >= 1 then
262
+ # size = reduced[0].size
263
+ # size.times.reverse_each do |col0|
264
+ # next if removed_columns.include?(col0)
265
+ # size.times do |col1|
266
+ # next if col0 == col1
267
+ # # The column is dominated if it is included into another.
268
+ # res = reduced.find do |row|
269
+ # row[col0] == "1" and row[col1] == "0"
270
+ # end
271
+ # # Not dominated if res
272
+ # unless res
273
+ # to_optimize = true
274
+ # # print "removing column=#{col0}\n"
275
+ # # Dominated, remove it
276
+ # reduced.each { |row| row[col0] = "0" }
277
+ # removed_columns << col0
278
+ # end
279
+ # end
280
+ # end
281
+ # end
282
+ end while(to_optimize)
283
+
284
+ # print "now reduced=#{reduced}\n"
285
+
286
+ # Step 2: Generate the Petrick's product.
287
+ product = []
288
+ reduced.each do |row|
289
+ term = []
290
+ # Get the columns covering the row.
291
+ row.each_byte.with_index do |bit,i|
292
+ # term << i if bit == "1"
293
+ term << i if bit == 49
294
+ end
295
+ product << term unless term.empty?
296
+ end
297
+
298
+
299
+ if smallest then
300
+ if product.empty? then
301
+ return essentials
302
+ end
303
+ cover = smallest_sum_term(product,deadline)
304
+ if essentials then
305
+ # print "essentials =#{essentials} cover=#{cover}\n"
306
+ essentials.each {|cube| cover.unshift(cube) }
307
+ return cover
308
+ else
309
+ return cover
310
+ end
311
+ end
312
+
313
+ # print "product=#{product}\n"
314
+ if product.empty? then
315
+ sum = product
316
+ else
317
+ product.each {|fact| fact.sort!.uniq! }
318
+ product.sort!.uniq!
319
+ # print "product=#{product}\n"
320
+ sum = to_sum_product_array(product)
321
+ # print "sum=#{sum}\n"
322
+ sum.each {|term| term.uniq! }
323
+ sum.uniq!
324
+ sum.sort_by! {|term| term.size }
325
+ # print "sum=#{sum}\n"
326
+ end
327
+
328
+ # # Add the essentials to the result and return it.
329
+ # if smallest then
330
+ # # print "smallest_cover=#{smallest_cover}, essentials=#{essentials}\n"
331
+ # return essentials if sum.empty?
332
+ # # Look for the smallest cover
333
+ # sum.sort_by! { |cover| cover.size }
334
+ # if essentials then
335
+ # return sum[0] + essentials
336
+ # else
337
+ # return sum[0]
338
+ # end
339
+ # else
340
+ sum.map! { |cover| essentials + cover }
341
+ return sum
342
+ # end
343
+ end
344
+
345
+
346
+
347
+ # ## Computes the minimal column covers of a boolean +matrix+.
348
+ # #
349
+ # # If +smallest+ is set to one, the method returns the smallest minimal
350
+ # # column cover instead.
351
+ # #
352
+ # # The +matrix+ is assumed to be an array of string, each string
353
+ # # representing a boolean row ("0" for false and "1" for true).
354
+ # def minimal_column_covers2(matrix,smallest = false)
355
+ # # print "matrix=#{matrix}\n"
356
+ # # Generate a variables nodes for each column: their name is
357
+ # # directly the column number.
358
+ # variables = matrix[0].size.times.map {|i| Variable.get("#{i}") }
359
+
360
+ # # Step 1: reduce the matrix for faster processing.
361
+ # # First put appart the essential columns.
362
+ # essentials = []
363
+ # matrix.each do |row|
364
+ # col = nil
365
+ # row.each_char.with_index do |c,i|
366
+ # if c == "1" then
367
+ # if col then
368
+ # # The row has several "1", no essential column there.
369
+ # col = nil
370
+ # break
371
+ # end
372
+ # col = i
373
+ # end
374
+ # end
375
+ # # An essential column is found.
376
+ # essentials << col if col
377
+ # end
378
+ # essentials.uniq!
379
+ # # print "essentials = #{essentials}\n"
380
+ # # The remove the rows covered by essential columns.
381
+ # keep = [ true ] * matrix.size
382
+ # essentials.each do |col|
383
+ # matrix.each.with_index do |row,i|
384
+ # keep[i] = false if row[col] == "1"
385
+ # end
386
+ # end
387
+ # # print "keep = #{keep}\n"
388
+ # reduced = matrix.select.with_index {|row,i| keep[i] }
389
+ # # print "matrix = #{matrix}\n"
390
+ # # print "reduced = #{reduced}\n"
391
+ # if reduced.empty? then
392
+ # # Essentials columns are enough for the cover, end here.
393
+ # if smallest then
394
+ # return essentials
395
+ # else
396
+ # return [ essentials ]
397
+ # end
398
+ # end
399
+ # # Then remove the dominating rows
400
+ # # For that purpose, sort them lexicographically.
401
+ # # print "reduced=#{reduced}\n"
402
+ # reduced.sort!.reverse!
403
+ # reduced = reduced.select.with_index do |row,i|
404
+ # i == reduced.size-1 or row.each_char.with_index.find do |c,j|
405
+ # ( c == "0" ) and ( (i+1)..(reduced.size-1) ).each.find do |k|
406
+ # reduced[k][j] == "1"
407
+ # end
408
+ # end
409
+ # end
410
+ # # print "now reduced=#{reduced}\n"
411
+
412
+ # # Step 2: Generate the Petrick's product.
413
+ # product = []
414
+ # reduced.each do |row|
415
+ # term = []
416
+ # # Get the columns covering the row.
417
+ # row.each_char.with_index do |bit,i|
418
+ # term << NodeVar.new(variables[i]) if bit == "1"
419
+ # end
420
+ # if term.size == 1 then
421
+ # product << term[0]
422
+ # elsif term.size > 1 then
423
+ # product << NodeOr.new(*term)
424
+ # end
425
+ # end
426
+ # # print "product=#{product}\n"
427
+ # if (product.empty?) then
428
+ # sum = nil
429
+ # elsif (product.size == 1)
430
+ # sum = product[0]
431
+ # else
432
+ # product = NodeAnd.new(*product)
433
+ # sum = product.sort.reduce.to_sum_product(true).sort.reduce
434
+ # end
435
+
436
+ # # Step 3: Convert the product to sum of product.
437
+ # # print "sum=#{sum}\n"
438
+ # unless sum
439
+ # # No minimal cover
440
+ # if smallest then
441
+ # return nil
442
+ # else
443
+ # return []
444
+ # end
445
+ # end
446
+ # sum = [ sum ] unless sum.is_a?(NodeOr) # In case sum is not a sum
447
+
448
+ # # Step4: Each term of the sum is a minimal cover.
449
+ # result = []
450
+ # smallest_cover = nil
451
+ # sum.each do |term|
452
+ # # Maybe the term is a litteral, if yes, make it an array
453
+ # term = [ term ] unless term.is_a?(NodeNary)
454
+ # # The name of a variable is directly the column number!
455
+ # cover = term.each.map do |lit|
456
+ # lit.variable.to_s.to_i
457
+ # end
458
+ # result << cover
459
+ # # In case we look for the smallest
460
+ # if smallest then
461
+ # if smallest_cover == nil then
462
+ # smallest_cover = cover
463
+ # elsif cover.size < smallest_cover.size
464
+ # smallest_cover = cover
465
+ # end
466
+ # end
467
+ # end
468
+ # # Add the essentials to the result and return it.
469
+ # if smallest then
470
+ # # print "smallest_cover=#{smallest_cover}, essentials=#{essentials}\n"
471
+ # return essentials unless smallest_cover
472
+ # return smallest_cover unless essentials
473
+ # return smallest_cover + essentials
474
+ # else
475
+ # result.map! { |cover| cover + essentials }
476
+ # return result
477
+ # end
478
+ # end
479
+
480
+
481
+ end