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,222 @@
1
+ ########################################################
2
+ # Tools for automatically generating logic expressions #
3
+ ########################################################
4
+
5
+ require "logic_tools/logictree.rb"
6
+ require "logic_tools/logiccover.rb"
7
+
8
+ module LogicTools
9
+
10
+ ## Class used for genrating logic expression.
11
+ class Generator
12
+
13
+ ## Creates a new generator for logic expressions on the
14
+ # boolean space based on a +variables+ set.
15
+ def initialize(*variables)
16
+ @variables = variables.map {|var| var.to_s }
17
+ @random = Random.new(0) # The default seed is fixed to 0.
18
+ @max = 2**@variables.size # The max number of cube per random cover.
19
+ @rate= Rational(1,3) # The rate of "-" in a cube.
20
+ end
21
+
22
+ ## Gets the seed.
23
+ def seed
24
+ @random.seed
25
+ end
26
+
27
+ ## Sets the seed to +value+.
28
+ def seed=(value)
29
+ @random = Random.new(value)
30
+ end
31
+
32
+ ## Gets the maximum number of cubes for a cover.
33
+ def max
34
+ return @max
35
+ end
36
+
37
+ ## Sets the maximum number of cubes for a cover.
38
+ def max=(max)
39
+ @max = max.to_i
40
+ end
41
+
42
+ ## Gets the rate of "-" in a cube.
43
+ def rate
44
+ return @rate
45
+ end
46
+
47
+ ## Sets the rate of "-" in a cube.
48
+ def rate=(rate)
49
+ @rate = rate
50
+ end
51
+
52
+ ## Iterates over the variables of the cube
53
+ #
54
+ # Returns an enumberator if no block is given
55
+ def each_variable(&blk)
56
+ # No block given? Return an enumerator
57
+ return to_enum(:each_variable) unless block_given?
58
+ # Block given? Apply it.
59
+ @variables.each(&blk)
60
+ end
61
+
62
+
63
+ ## Creates a random truth table column value.
64
+ def random_column
65
+ return @random.rand(0..(2**(2**(@variables.size))-1))
66
+ end
67
+
68
+ ## Creates a random truth table row value.
69
+ def random_row
70
+ return @random.rand(0..(2**(@variables.size)-1))
71
+ end
72
+ alias random_2row random_row
73
+
74
+ ## Creates a random 3-states row.
75
+ def random_3row
76
+ result = "-" * @variables.size
77
+ @variables.size.times do |i|
78
+ value = @random.rand
79
+ if value > @rate then
80
+ result[i] = value <= @rate + (1-@rate)/2 ? "0" : "1"
81
+ end
82
+ end
83
+ return result
84
+ end
85
+
86
+
87
+ ## Creates a minterm from the binary value of a truth table's +row+.
88
+ def make_minterm(row)
89
+ # Convert the +row+ to a bit string if necessary.
90
+ unless (row.is_a?(String))
91
+ row = row.to_s(2).rjust(@variables.size,"0")
92
+ end
93
+ # Create the minterm from the bit string: an AND where
94
+ # each term is variable if the corresponding bit is "1"
95
+ # and the opposite if the corresponding bit is "0".
96
+ return NodeAnd.new(*row.each_char.with_index.map do |bit,j|
97
+ var = NodeVar.new(Variable.get(@variables[j]))
98
+ bit == "1" ? var : NodeNot.new(var)
99
+ end )
100
+ end
101
+
102
+ ## Create a standard conjunctive form from its values in a
103
+ # truth +table+.
104
+ def make_std_conj(table)
105
+ # Convert the +table+ to a bit string if necessary.
106
+ unless table.is_a?(String) then
107
+ table = table.to_s(2).rjust(2 ** @variables.size,"0")
108
+ end
109
+ # Generate the terms from it.
110
+ terms = []
111
+ table.each_char.with_index do |val,i|
112
+ if (val == "1") then
113
+ terms << make_minterm(i)
114
+ end
115
+ end
116
+ # Generate and return the resulting sum.
117
+ return NodeOr.new(*terms)
118
+ end
119
+
120
+ ## Iterates over all the possible standard conjunctive forms.
121
+ #
122
+ # NOTE: this iteration can be huge!
123
+ def each_std_conj
124
+ # No block given? Return an enumerator.
125
+ return to_enum(:each_std_conj) unless block_given?
126
+
127
+ # Block given? Apply it on each bit.
128
+ # Iterate on each possible truth table.
129
+ ( 2 ** (2 ** @variables.size) ).times do |table|
130
+ # Create the expression and apply the block on it.
131
+ yield(make_std_conj(table))
132
+ end
133
+ end
134
+
135
+ ## Creates a random minterm.
136
+ def random_minterm
137
+ return make_minterm(random_row)
138
+ end
139
+
140
+ ## Creates a random standard conjunctive from.
141
+ def random_std_conj
142
+ return make_std_conj(random_column)
143
+ end
144
+
145
+
146
+
147
+ ## Creates a cube from binary +row+.
148
+ def make_cube(row)
149
+ # Convert the +row+ to a bit string if necessary.
150
+ unless (row.is_a?(String))
151
+ row = row.to_s(2).rjust(@variables.size,"0")
152
+ end
153
+ return Cube.new(row)
154
+ end
155
+
156
+ ## Create a 1-cube cover from its values in a truth +table+.
157
+ def make_1cover(table)
158
+ # Convert the +table+ to a bit string if necessary.
159
+ unless table.is_a?(String) then
160
+ table = table.to_s(2).rjust(2 ** @variables.size,"0")
161
+ end
162
+ # Generate the cover.
163
+ cover = Cover.new(*@variables)
164
+ # Fill it with the required 1-cubes.
165
+ table.each_char.with_index do |val,i|
166
+ if (val == "1") then
167
+ cover << make_cube(i)
168
+ end
169
+ end
170
+ # Returns the cover.
171
+ return cover
172
+ end
173
+
174
+ ## Iterates over all the possible cover made of 1-cubes.
175
+ #
176
+ # NOTE: this iteration can be huge!
177
+ def each_1cover
178
+ # No block given? Return an enumerator.
179
+ return to_enum(:each_1cover) unless block_given?
180
+
181
+ # Block given? Apply it on each bit.
182
+ # Iterate on each possible truth table.
183
+ ( 2 ** (2 ** @variables.size) ).times do |table|
184
+ # Create the expression and apply the block on it.
185
+ yield(make_1cover(table))
186
+ end
187
+ end
188
+
189
+ ## Creates a random 1-cube.
190
+ def random_1cube
191
+ return make_cube(random_2row)
192
+ end
193
+
194
+ ## Creates a random cover made of 1-cubes.
195
+ def random_1cover
196
+ return make_1cover(random_column)
197
+ end
198
+
199
+ ## Creates a random cube.
200
+ def random_cube
201
+ return make_cube(random_3row)
202
+ end
203
+
204
+ ## Creates a random cover.
205
+ def random_cover
206
+ # Create the new cover.
207
+ cover = Cover.new(*@variables)
208
+ # Fill it with a random number of random cubes.
209
+ volume = 0
210
+ @random.rand(0..(@max-1)).times do
211
+ cube = make_cube(random_3row)
212
+ # volume += 2 ** cube.each_bit.count { |b| b == "-" }
213
+ # break if volume >= 2 ** @variables.size
214
+ cover << cube
215
+ end
216
+ # Return it.
217
+ return cover
218
+ end
219
+
220
+ end
221
+
222
+ end
@@ -23,7 +23,10 @@ module LogicTools
23
23
  rule(:fal) { str("0") }
24
24
  # Variable
25
25
  # rule(:var) { match('[A-Za-uw-z]') }
26
- rule(:var) { match('[A-Za-z]') }
26
+ rule(:var) do
27
+ match('[A-Za-z]') |
28
+ str("{") >> ( match('[a^Za-z]').repeat ) >> str("}")
29
+ end
27
30
  # And operator
28
31
  # rule(:andop) { str("&&") | match('[&\.\*^]') }
29
32
  rule(:andop) { str(".") }
@@ -50,7 +53,11 @@ module LogicTools
50
53
  # Terminal rules
51
54
  rule(:tru => simple(:tru)) { NodeTrue.new() }
52
55
  rule(:fal => simple(:fal)) { NodeFalse.new() }
53
- rule(:var => simple(:var)) { NodeVar.new(var) }
56
+ rule(:var => simple(:var)) do
57
+ name = var.to_s
58
+ name = name[1..-2] if name.size > 1 # Remove the {} if any.
59
+ NodeVar.new(name)
60
+ end
54
61
  rule(:notop => simple(:notop)) { "!" }
55
62
 
56
63
  # Not rules
@@ -0,0 +1,734 @@
1
+
2
+ require "logic_tools/logictree.rb"
3
+ require "logic_tools/logiccover.rb"
4
+ require "logic_tools/minimal_column_covers.rb"
5
+ require "logic_tools/logicconvert.rb"
6
+
7
+ require "logic_tools/traces.rb"
8
+
9
+ module LogicTools
10
+
11
+
12
+
13
+ # Enhances the Cube class with methods for applying the
14
+ # Espresso algorithm
15
+ class Cube
16
+
17
+ ## Computes the blocking matrix relatively to an +off+ cover.
18
+ #
19
+ # NOTE: * The blocking matrix's first row gives the column number
20
+ # of each litteral of the cube.
21
+ # * The blocking matrix's other rows represents the cubes
22
+ # of the off cover.
23
+ # * The block matrix's cells are set to "1" if corresponding
24
+ # +self+ litteral has a different polarity (1,0 or 0,1) than
25
+ # the corresponding off cover's cube and set to "0" otherwise
26
+ # (including the "-" cases).
27
+ def blocking_matrix(off)
28
+ # Create the result matrix.
29
+ blocking = []
30
+ # Get the column number of the litterals of self.
31
+ # litterals = @bits.size.times.find_all {|i| @bits[i] != "-" }
32
+ litterals = @bits.size.times.find_all {|i| @bits.getbyte(i) != 45 }
33
+ # This is the first row of the blocking matrix.
34
+ blocking << litterals
35
+ # Build the other rows: one per cube of the off cover.
36
+ off.each_cube do |cube|
37
+ # print "for off cube=#{cube}\n"
38
+ # Create the new row: by default blocking.
39
+ row = "0" * litterals.size
40
+ blocking << row
41
+ # Fill it
42
+ litterals.each.with_index do |col,i|
43
+ # if cube[col] != "-" and @bits[col] != cube[col] then
44
+ if cube.getbyte(col) != 45 and
45
+ @bits.getbyte(col) != cube.getbyte(col) then
46
+ # Non blocking, put a "1".
47
+ # row[i] = "1"
48
+ row.setbyte(i,49)
49
+ end
50
+ end
51
+ # print "blocking row=#{row}\n"
52
+ end
53
+ # Returns the resulting matrix
54
+ return blocking
55
+ end
56
+ end
57
+
58
+
59
+ ## Sorts the cubes of a +cover+ by weight.
60
+ #
61
+ # Returns a new cover containing the sorted cubes.
62
+ def order(cover)
63
+ # Step 1: Compute the weight of each cube
64
+ weights = [ 0 ] * cover.size
65
+ # For that purpose first compute the weight of each column
66
+ # (number of ones)
67
+ col_weights = [ 0 ] * cover.width
68
+ cover.width.times do |i|
69
+ # cover.each_cube { |cube| col_weights[i] += 1 if cube[i] == "1" }
70
+ cover.each_cube do |cube|
71
+ col_weights[i] += 1 if cube.getbyte(i) == 49
72
+ end
73
+ end
74
+ # Then the weight of a cube is the scalar product of its
75
+ # bits with the column weights.
76
+ cover.each_cube.with_index do |cube,j|
77
+ cube.each_byte.with_index do |bit,i|
78
+ # weights[j] += col_weights[i] if bit == "1"
79
+ weights[j] += col_weights[i] if bit == 49
80
+ end
81
+ end
82
+
83
+ # Step 2: stort the cubes by weight
84
+ new_cubes = cover.each_cube.sort_by.with_index { |cube,i| weights[i] }
85
+
86
+ # Creates a new cover with the sorted cubes and return it.
87
+ sorted = Cover.new(*cover.each_variable)
88
+ new_cubes.each { |cube| sorted << cube }
89
+ return sorted
90
+ end
91
+
92
+
93
+ ## Expands cover +on+ as long it does not intersects with +off+.
94
+ #
95
+ # NOTE: this step requires to find the minimal column set cover of
96
+ # a matrix, this algorthim can be very slow and is therefore terminate
97
+ # before an optimal solution is found is a +deadline+ is exceeded.
98
+ #
99
+ # Returns the resulting cover.
100
+ def expand(on,off,deadline)
101
+ # Step 1: sort the cubes by weight.
102
+ on = order(on)
103
+ # print "#3.1 #{Time.now}\n"
104
+ # print "on=[#{on.to_s}]\n"
105
+
106
+ # Create the resulting cover.
107
+ cover = Cover.new(*on.each_variable)
108
+
109
+ # Step 2: Expand the cubes in order of their weights.
110
+ on.each_cube do |cube|
111
+ # print "#3.2 #{Time.now} cube=#{cube}\n"
112
+ # Builds the blocking matrix
113
+ blocking = cube.blocking_matrix(off)
114
+ # print "blocking=[#{blocking}]\n"
115
+ # Select the smallest minimal column cover of the blocking
116
+ # matrix: it will be the expansion
117
+ col_cover = minimal_column_covers(blocking[1..-1],true,deadline)
118
+ # print "col_cover=#{col_cover}\n"
119
+ # This is the new cube
120
+ bits = "-" * cube.width
121
+ col_cover.each do |col|
122
+ # The first row of the blocking matrix give the actual
123
+ # column of the litteral
124
+ col = blocking[0][col]
125
+ # bits[col] = cube[col]
126
+ bits.setbyte(col,cube.getbyte(col))
127
+ end
128
+ # print "expand result=#{bits}\n"
129
+ # Create and add the new expanded cube.
130
+ cover << Cube.new(bits,false) # No need to clone bits.
131
+ end
132
+
133
+ return cover
134
+ end
135
+
136
+
137
+ ## Represents an empty cube.
138
+ #
139
+ # NOTE: for irredundant usage only.
140
+ class VoidCube < Cube
141
+ def initialize(size)
142
+ # NOTE: This bit string is a phony, since the cube is void.
143
+ super("-" * size,false)
144
+ # The real bits
145
+ @vbits = " " * size
146
+ end
147
+
148
+ ## Evaluates the corresponding function's value for a binary +input+.
149
+ #
150
+ # +input+ is assumed to be an integer.
151
+ # Returns the evaluation result as a boolean.
152
+ def eval(input)
153
+ return false
154
+ end
155
+
156
+ ## Converts to a string.
157
+ def to_s # :nodoc:
158
+ return @vbits.clone
159
+ end
160
+
161
+ ## Iterates over the bits of the cube as bytes.
162
+ #
163
+ # Returns an enumerator if no block given.
164
+ def each_byte(&blk)
165
+ # No block given? Return an enumerator.
166
+ return to_enum(:each_byte) unless block_given?
167
+
168
+ # Block given? Apply it on each bit.
169
+ @vbits.each_byte(&blk)
170
+ end
171
+
172
+ ## Iterates over the bits of the cube as chars.
173
+ #
174
+ # Returns an enumerator if no block given.
175
+ def each_char(&blk)
176
+ # No block given? Return an enumerator.
177
+ return to_enum(:each_char) unless block_given?
178
+
179
+ # Block given? Apply it on each bit.
180
+ @vbits.each_char(&blk)
181
+ end
182
+ alias each each_char
183
+
184
+ ## The bit string defining the cube.
185
+ #
186
+ # Should not be modified directly, hence set as protected.
187
+ def bits
188
+ raise "A VoidCube cannot be modified."
189
+ end
190
+ protected :bits
191
+
192
+ ## Compares with another +cube+.
193
+ def ==(cube) # :nodoc:
194
+ @vbits == cube.bits
195
+ end
196
+ alias eql? ==
197
+ def <=>(cube) #:nodoc:
198
+ @vbits <=> cube.bits
199
+ end
200
+
201
+ ## Gets the hash of a cube
202
+ def hash
203
+ @vbits.hash
204
+ end
205
+
206
+ ## duplicates the cube.
207
+ def clone # :nodoc:
208
+ VoidCube.new(self.width)
209
+ end
210
+ alias dup clone
211
+
212
+ ## Gets the char value of bit +i+.
213
+ def [](i)
214
+ @vbits[i]
215
+ end
216
+
217
+ ## Gets the byte value of bit +i+.
218
+ def getbyte(i)
219
+ @vbits.getbyte(i)
220
+ end
221
+
222
+ ## Sets the char value of bit +i+ to +b+.
223
+ #
224
+ # Invalid for a VoidCube.
225
+ def []=(i,b)
226
+ raise "A VoidCube cannot be modified."
227
+ end
228
+
229
+ ## Sets the byte value of bit +i+ to +b+.
230
+ #
231
+ # Invalid for a VoidCube.
232
+ def setbyte(i,b)
233
+ raise "A VoidCube cannot be modified."
234
+ end
235
+ end
236
+
237
+
238
+ ## Generates the cofactor of +cover+ obtained when +var+ is set to +val+
239
+ # while keeping the cubes indexes in the cover.
240
+ #
241
+ # NOTE: for irreduntant only since the resulting cover is not in a
242
+ # valid state!
243
+ def cofactor_indexed(cover,var,val)
244
+ # if val != "0" and val != "1" then
245
+ if val != 48 and val != 49 then
246
+ raise "Invalid value for generating a cofactor: #{val}"
247
+ end
248
+ # Get the index of the variable.
249
+ i = cover.variable_index(var)
250
+ # Create the new cover.
251
+ ncover = Cover.new(*@variables)
252
+ # Set its cubes.
253
+ cover.each_cube do |cube|
254
+ cube = cube.to_s
255
+ # cube[i] = "-" if cube[i] == val
256
+ cube.setbyte(i,45) if cube.getbyte(i) == val
257
+ # if cube[i] == "-" then
258
+ if cube.getbyte(i) == 45 then
259
+ ncover << Cube.new(cube,false) # No need to clone cube.
260
+ else
261
+ # Add an empty cube for keeping the index.
262
+ ncover << VoidCube.new(ncover.width)
263
+ end
264
+ end
265
+ return ncover
266
+ end
267
+
268
+ ## Generates the generalized cofactor of +cover+ from +cube+
269
+ # while keeping the cubes indexes in the cover.
270
+ #
271
+ # NOTE: for irreduntant only since the resulting cover is not in a
272
+ # valid state!
273
+ def cofactor_cube_indexed(cover,cube)
274
+ # Create the new cover.
275
+ ncover = Cover.new(*@variables)
276
+ # Set its cubes.
277
+ cover.each_cube do |scube|
278
+ scube = scube.to_s
279
+ scube.size.times do |i|
280
+ # if scube.getbyte(i) == cube[i] then
281
+ if scube.getbyte(i) == cube.getbyte(i) then
282
+ # scube[i] = "-"
283
+ scube.setbyte(i,45)
284
+ # elsif (scube[i] != "-" and cube[i] != "-") then
285
+ elsif (scube.getbyte(i) != 45 and cube.getbyte(i) != 45) then
286
+ # The cube is to remove from the cover.
287
+ scube = nil
288
+ break
289
+ end
290
+ end
291
+ if scube then
292
+ # The cube is to keep in the cofactor.
293
+ ncover << Cube.new(scube,false) # No need to clone scube.
294
+ else
295
+ # Add an empty cube for keeping the index.
296
+ ncover << VoidCube.new(ncover.width)
297
+ end
298
+ end
299
+ return ncover
300
+ end
301
+
302
+ ## Computes the minimal set cover of a +cover+ along with a +dc+
303
+ # (don't care) cover.
304
+ #
305
+ # Return the set as a list of cube indexes in the cover.
306
+ def minimal_set_covers(cover,dc)
307
+ # print "minimal_set_cover with cover=#{cover} and dc=#{dc}\n"
308
+ # Look for a binate variable to split on.
309
+ binate = (cover+dc).find_binate
310
+ # binate = cover.find_binate
311
+ # # Gets its index
312
+ # i = cover.variable_index(binate)
313
+ unless binate then
314
+ # The cover is actually unate, process it the fast way.
315
+ # Look for "-" only cubes.
316
+ # First in +dc+: if there is an "-" only cube, there cannot
317
+ # be any minimal set cover.
318
+ dc.each_cube do |cube|
319
+ # return [] unless cube.each.find { |b| b != "-" }
320
+ return [] unless cube.each_byte.find { |b| b != 45 }
321
+ end
322
+ # Then in +cover+: each "-" only cube correspond to a cube in the
323
+ # minimal set cover.
324
+ result = []
325
+ cover.each.with_index do |cube,i|
326
+ # print "cube=#{cube} i=#{i}\n"
327
+ # result << i unless cube.each.find { |b| b != "-" }
328
+ result << i unless cube.each_byte.find { |b| b != 45 }
329
+ end
330
+ # print "result=#{result}\n"
331
+ return [ result ]
332
+ else
333
+ # Compute the cofactors over the binate variables.
334
+ # cf0 = cofactor_indexed(cover,binate,"0")
335
+ cf0 = cofactor_indexed(cover,binate,48)
336
+ # cf1 = cofactor_indexed(cover,binate,"1")
337
+ cf1 = cofactor_indexed(cover,binate,49)
338
+ # df0 = cofactor_indexed(dc,binate,"0")
339
+ df0 = cofactor_indexed(dc,binate,48)
340
+ # df1 = cofactor_indexed(dc,binate,"1")
341
+ df1 = cofactor_indexed(dc,binate,49)
342
+ # Process each cofactor and merge their results
343
+ return [ minimal_set_covers(cf0,df0), minimal_set_covers(cf1,df1) ].flatten(1)
344
+ end
345
+ end
346
+
347
+
348
+ ## Removes the cubes of the +on+ cover that are redundant for the joint +on+
349
+ # and +dc+ covers.
350
+ #
351
+ # NOTE: this step requires to find the minimal column set cover of
352
+ # a matrix, this algorthim can be very slow and is therefore terminate
353
+ # before an optimal solution is found is a +deadline+ is exceeded.
354
+ #
355
+ # Returns the new cover.
356
+ def irredundant(on,dc,deadline)
357
+ # Step 1: get the relatively essential.
358
+ # print "on=#{on}\n"
359
+ cubes, es_rel = on.each_cube.partition do |cube|
360
+ ((on+dc) - cube).cofactor_cube(cube).is_tautology?
361
+ end
362
+ return on.clone if cubes.empty? # There were only relatively essentials.
363
+ # print "cubes = #{cubes}\n"
364
+ # print "es_rel = #{es_rel}\n"
365
+
366
+ # Step 2: get the partially and totally redundants.
367
+ es_rel_dc = Cover.new(*on.each_variable)
368
+ es_rel.each { |cube| es_rel_dc << cube }
369
+ dc.each { |cube| es_rel_dc << cube }
370
+ red_tot, red_par = cubes.partition do |cube|
371
+ es_rel_dc.cofactor_cube(cube).is_tautology?
372
+ end
373
+ # red_par is to be used as a cover.
374
+ red_par_cover = Cover.new(*on.each_variable)
375
+ red_par.each { |cube| red_par_cover << cube }
376
+ # print "es_rel_dc = #{es_rel_dc}\n"
377
+ # print "red_tot = #{red_tot}\n"
378
+ # print "red_par = #{red_par}\n"
379
+
380
+ # Step 3: get the minimal sets of partially redundant.
381
+ red_par_sets = red_par.map do |cube|
382
+ # print "for cube=#{cube}\n"
383
+ minimal_set_covers( cofactor_cube_indexed(red_par_cover,cube),
384
+ cofactor_cube_indexed(es_rel_dc,cube) )
385
+ end
386
+ # red_par_sets.each { |set| set.map! {|i| red_par[i] } }
387
+ # print "red_par_sets=#{red_par_sets}\n"
388
+
389
+ # Step 4: find the smallest minimal set using the minimal column covers
390
+ # algorithm.
391
+ # For that purpose build the boolean matrix whose columns are for the
392
+ # partially redundant cubes and the rows are for the sets, "1"
393
+ # indication the cube is the in set.
394
+ matrix = []
395
+ red_par_sets.each do |sets|
396
+ sets.each do |set|
397
+ row = "0" * red_par.size
398
+ # set.each { |i| row[i] = "1" }
399
+ set.each { |i| row.setbyte(i,49) }
400
+ matrix << row
401
+ end
402
+ end
403
+ # print "matrix=#{matrix}\n"
404
+ smallest_set_cols = minimal_column_covers(matrix,true,deadline)
405
+ # print "smallest_set_cols=#{smallest_set_cols}\n"
406
+
407
+ # Creates a new cover with the relative essential cubes and the
408
+ # smallest set of partially redundant cubes.
409
+ cover = Cover.new(*on.each_variable)
410
+ es_rel.each { |cube| cover << cube.clone }
411
+ # smallest_set_cols.each do |set|
412
+ # set.each { |col| cover << red_par[col].clone }
413
+ # end
414
+ smallest_set_cols.each { |col| cover << red_par[col].clone }
415
+ # print "cover=#{cover}\n"
416
+ return cover
417
+ end
418
+
419
+ ## Remove quickly some cubes of the +on+ cover that are redundant.
420
+ #
421
+ # Returns the new cover.
422
+ def irredundant_partial(on)
423
+ result = Cover.new(*on.each_variable)
424
+ on.each.with_index do |cube,i|
425
+ # Is cube included somewhere?
426
+ unless on.each.with_index.find {|cube1,j| j != i and cube1.include?(cube) }
427
+ # No, keep the cube.
428
+ result << cube
429
+ end
430
+ end
431
+ end
432
+
433
+
434
+ ## Get the essential cubes from the +on+ cover which are not covered
435
+ # by the +dc+ (don't care) cover.
436
+ #
437
+ # Returns the new cover.
438
+ def essentials(on,dc)
439
+ # Create the essential list.
440
+ es = []
441
+
442
+ # For each cube of on, check if it is essential.
443
+ on.each_cube do |cube|
444
+ # Step 1: build the +cover+ (on-cube)+dc.
445
+ # NOTE: could be done without allocating multiple covers,
446
+ # but this is much readable this way, so kept as is as long
447
+ # as there do not seem to be any much performance penalty.
448
+ cover = (on-cube)+dc
449
+ # Step 2: Gather the concensus beteen each cube of +cover+
450
+ # and their sharp with +cube+.
451
+ cons = Cover.new(*on.each_variable)
452
+ cover.each_cube do |ccube|
453
+ # Depending on the distance.
454
+ dist = cube.distance(ccube)
455
+ # If the distance is >1 there is no consensus.
456
+ # Otherwise:
457
+ if (dist == 1) then
458
+ # The distance is 1, the consensus is computed directly.
459
+ cons << ccube.consensus(cube)
460
+ elsif (dist == 0)
461
+ # The distance is 0, sharp ccube from cube and
462
+ # compute the concensus from each resulting prime cube.
463
+ ccube.sharp(cube).each do |scube|
464
+ scube = scube.consensus(cube)
465
+ cons << scube if scube
466
+ end
467
+ end
468
+ end
469
+ # Step 3: check if +cube+ is covered by cover+cons.
470
+ # This is done by checking is the cofactor with cube
471
+ # is not a tautology.
472
+ unless (cons+dc).cofactor_cube(cube).is_tautology?
473
+ # +cube+ is not covered by cover+cons, it is an essential.
474
+ es << cube
475
+ end
476
+ end
477
+
478
+ # Create the resulting cover.
479
+ result = Cover.new(*on.each_variable)
480
+ es.each { |es| result << es }
481
+ return result
482
+ end
483
+
484
+
485
+ ## Computes the cost of a +cover+.
486
+ #
487
+ # The cost of the cover is sum of the number of variable of each cube.
488
+ def cost(cover)
489
+ return cover.each_cube.reduce(0) do |sum, cube|
490
+ # sum + cube.each_bit.count { |b| b != "-" }
491
+ sum + cube.each_byte.count { |b| b != 45 }
492
+ end
493
+ end
494
+
495
+ ## Compute the maximum reduction of a cube from an +on+ cover
496
+ # which does not intersect with another +dc+ cover.
497
+ def max_reduce(cube,on,dc)
498
+ # print "max_reduce with cube=#{cube} on=#{on} dc=#{dc}\n"
499
+ # Step 1: create the cover to get the reduction from.
500
+ cover = ((on + dc) - cube).cofactor_cube(cube)
501
+ # print "cover=#{cover}, taut=#{cover.is_tautology?}\n"
502
+ # Step 2: complement it
503
+ compl = cover.complement
504
+ # print "compl=#{compl}\n"
505
+ # Step 3: get the smallest cube containing the complemented cover
506
+ sccc = compl.smallest_containing_cube
507
+ # print "sccc=#{sccc}\n"
508
+ # The result is the intersection of this cube with +cube+.
509
+ return cube.intersect(sccc)
510
+ end
511
+
512
+ ## Reduces cover +on+ esuring +dc+ (don't care) is not intersected.
513
+ #
514
+ # Returns the resulting cover.
515
+ def reduce(on,dc)
516
+ # Step 1: sorts on's cubes to achieve a better reduce.
517
+ on = order(on)
518
+ # print "ordered on=#{on.to_s}\n"
519
+
520
+ # Step 2: reduce each cube and add it to the resulting cover.
521
+ cover = Cover.new(*on.each_variable)
522
+ on.each_cube.to_a.reverse_each do |cube|
523
+ reduced = max_reduce(cube,on,dc)
524
+ # print "cube=#{cube} reduced to #{reduced}\n"
525
+ cover << reduced if reduced # Add the cube if not empty
526
+ on = (on - cube)
527
+ on << reduced if reduced # Add the cube if not empty
528
+ end
529
+ return cover
530
+ end
531
+
532
+
533
+ # Enhances the Cover class with simplifying using the Espresso
534
+ # algorithm.
535
+ class Cover
536
+
537
+ include LogicTools::Traces
538
+
539
+ # ## The deadline for minimal columns covers.
540
+ # @@deadline = Float::INFINITY
541
+ # def Cover.deadline
542
+ # @@deadline
543
+ # end
544
+
545
+
546
+ ## Generates an equivalent but simplified cover from a set
547
+ # splitting it for faster solution.
548
+ #
549
+ # Param:
550
+ # * +deadline+:: the deadline for each step in second.
551
+ # * +volume+:: the "volume" above which the cover is split before
552
+ # being solved.
553
+ #
554
+ # NOTE: the deadline is acutally applied to the longest step
555
+ # only.
556
+ #
557
+ def split_simplify(deadline,volume)
558
+ info { "Spliting..." }
559
+ # The on set is a copy of self [F].
560
+ on = self.simpler_clone
561
+ on0 = Cover.new(*@variables)
562
+ (0..(on.size/2-1)).each do |i|
563
+ on0 << on[i].clone
564
+ end
565
+ on1 = Cover.new(*@variables)
566
+ (((on.size)/2)..(on.size-1)).each do |i|
567
+ on1 << on[i].clone
568
+ end
569
+ debug { "on0=#{on0}\n" }
570
+ debug { "on1=#{on1}\n" }
571
+ # Simplify each part independently
572
+ inc_indent
573
+ on0 = on0.simplify(deadline,volume)
574
+ on1 = on1.simplify(deadline,volume)
575
+ dec_indent
576
+ # And merge the results for simplifying it globally.
577
+ on = on0 + on1
578
+ on.uniq!
579
+ new_cost = cost(on)
580
+ # if (new_cost >= @first_cost) then
581
+ # info { "Giving up with final cost=#{new_cost}" }
582
+ # # Probably not much possible optimization, end here.
583
+ # result = self.clone
584
+ # result.uniq!
585
+ # return result
586
+ # end
587
+ # Try to go on but with a timer (set to 7 times the deadline since
588
+ # there are 7 different steps in total).
589
+ begin
590
+ Timeout::timeout(7*deadline) {
591
+ on = on.simplify(deadline,Float::INFINITY)
592
+ }
593
+ rescue Timeout::Error
594
+ info do
595
+ "Time out for global optimization, ends here..."
596
+ end
597
+ end
598
+ info do
599
+ "Final cost: #{cost(on)} (with #{on.size} cubes)"
600
+ end
601
+ return on
602
+ end
603
+
604
+
605
+
606
+
607
+ ## Generates an equivalent but simplified cover.
608
+ #
609
+ # Param:
610
+ # * +deadline+:: the deadline for irredudant in seconds.
611
+ # * +volume+:: the "volume" above which the cover is split before
612
+ # being solved.
613
+ #
614
+ # Uses the Espresso method.
615
+ def simplify(deadline = Float::INFINITY, volume = Float::INFINITY)
616
+ # Compute the cost before any simplifying.
617
+ @first_cost = cost(self)
618
+ info do
619
+ "Cost before simplifying: #{@first_cost} " +
620
+ "(with #{@cubes.size} cubes)"
621
+ end
622
+ # If the cover is too big, split before solving.
623
+ if (self.size > 2) and (self.size * (self.width ** 2) > volume) then
624
+ return split_simplify(deadline,volume)
625
+ end
626
+
627
+ # Step 1:
628
+ # The on set is a copy of self [F].
629
+ on = self.simpler_clone
630
+
631
+ # Initialization
632
+ #
633
+ # print "on=#{on}\n"
634
+ # print "#1 #{Time.now}\n"
635
+ # And the initial set of don't care: dc [D].
636
+ dc = Cover.new(*on.each_variable) # At first dc is empty
637
+
638
+ # print "#2 #{Time.now}\n"
639
+ # Step 2: generate the complement cover: off [R = COMPLEMENT(F)].
640
+ off = on.complement
641
+ # off = irredundant_partial(off) # quickly simlify off.
642
+ # print "off=#{off}\n"
643
+ info { "off with #{off.size} cubes." }
644
+
645
+ #
646
+ # Process the cover by pieces if the off and the on are too big.
647
+
648
+ # If on and off are too big together, split before solving.
649
+ if (on.size > 2) and (on.size*off.size > volume) then
650
+ return split_simplify(deadline,volume)
651
+ end
652
+
653
+ # print "#3 #{Time.now}\n"
654
+ # Step 3: perform the initial expansion [F = EXPAND(F,R)].
655
+ on = expand(on,off,deadline)
656
+ # print "expand:\non=#{on}\n"
657
+ # Remove the duplicates.
658
+ on.uniq!
659
+
660
+ # print "#4 #{Time.now}\n"
661
+ # Step 4: perform the irredundant cover [F = IRREDUNDANT(F,D)].
662
+ on = irredundant(on,dc,deadline)
663
+ # print "irredundant:\non=#{on}\n"
664
+
665
+ # print "#5 #{Time.now}\n"
666
+ # Step 5: Detect the essential primes [E = ESSENTIAL(F,D)].
667
+ essentials = essentials(on,dc)
668
+ # print "essentials=#{essentials}\n"
669
+
670
+ # print "#6 #{Time.now}\n"
671
+ # Step 6: remove the essential primes from on and add them to dc
672
+ on = on - essentials
673
+ dc = dc + essentials
674
+
675
+ # Optimiation loop
676
+
677
+ # Computes the cost after preprocessing.
678
+ new_cost = cost(on)
679
+ essentials_cost = cost(essentials)
680
+ info { "After preprocessing, cost=#{new_cost+essentials_cost}" }
681
+ if new_cost >0 then
682
+ begin
683
+ # print "#7.1 #{Time.now}\n"
684
+ cost = new_cost
685
+ # Step 1: perform the reduction of on [F = REDUCE(F,D)]
686
+ on = LogicTools.reduce(on,dc)
687
+ # print "reduce:\non=#{on.to_s}\n"
688
+ # Step 2: perform the expansion of on [F = EXPAND(F,R)]
689
+ on = expand(on,off,deadline)
690
+ # Also remove the duplicates
691
+ on.uniq!
692
+ # Step 3: perform the irredundant cover [F = IRREDUNDANT(F,D)]
693
+ on = irredundant(on,dc,deadline)
694
+ # on.each_cube do |cube|
695
+ # if ((on+dc)-cube).cofactor_cube(cube).is_tautology? then
696
+ # print "on=[#{on}]\ndc=[#{dc}]\ncube=#{cube}\n"
697
+ # raise "REDUNDANT AFTER IRREDUNDANT"
698
+ # end
699
+ # end
700
+ # Step 4: compute the cost
701
+ new_cost = cost(on)
702
+ info { "cost=#{new_cost+essentials_cost}" }
703
+ end while(new_cost < cost)
704
+ end
705
+
706
+ # Readd the essential primes to the on result
707
+ on += essentials
708
+
709
+ # This is the resulting cover.
710
+ info { "Final cost: #{cost(on)} (with #{on.size} cubes)" }
711
+ return on
712
+ end
713
+ end
714
+
715
+
716
+ # Enhances the Node class with expression simplifying using the
717
+ # Espresso algorithm.
718
+ class Node
719
+
720
+ ## Generates an equivalent but simplified representation of the
721
+ # expression represented by the tree rooted by the current node.
722
+ #
723
+ # Uses the Espresso method.
724
+ def simplify()
725
+ # Initialization
726
+
727
+ # Step 1: generate the simplified cover.
728
+ cover = self.to_cover.simplify
729
+
730
+ # Step 2: generate the resulting tree from the resulting cover.
731
+ return cover.to_tree
732
+ end
733
+ end
734
+ end