logic_tools 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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