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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 901ddc00175b7abf309f63f45e246752e350d467
4
- data.tar.gz: 6989af12e0185e71e8bad6922aebc6eb7cf17412
3
+ metadata.gz: 5306034ce28f57c0fb9ee57b9d5c3643682f99e2
4
+ data.tar.gz: f879af69f02c8d96e94e2dfeccc270147d6e621d
5
5
  SHA512:
6
- metadata.gz: a618bc881cb959c8fef9311414ed4b969aafd67592f25eb3f21620f0da2d0eb5d909702b313391dc5c3af054049addc1add4cf3c8bd3d533994039ad7d0b0952
7
- data.tar.gz: 9b1de33709d8088ef53347d578b4898aac18324a68494471852303fe45629c93a6741437fcf2ef89ac98872eec4f775b8a866dfe6ae34f812d69d1310c56aae5
6
+ metadata.gz: aefc4e9df205c84a42b3157281048ce8c98fe4f097a0d3e3441b0fe13fa8490a4727c66d73aaa92b1af10e1b1ee5c03805e3f941f1f8ae3dc8f563a4d1379a9a
7
+ data.tar.gz: 03eb53a6df7171d407f6f29f116d3e0ad718767cba29ff4dc19c2bf6bbbc1d6e4ef38754eac8491c2a027008f841fdfd330da36a5f815c7a02a2d751d4ddcc6e
data/.gitignore CHANGED
@@ -7,3 +7,6 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ /lib/test.rb
11
+ /lib/examples.txt
12
+ /lib/result.txt
data/exe/is_tautology ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ ######################################################################
3
+ ## Script for launching the is_tautology.rb program ##
4
+ ######################################################################
5
+
6
+ begin
7
+ require 'rubygems'
8
+ gem 'logic_tools'
9
+ rescue LoadError
10
+ end
11
+
12
+ load "logic_tools/is_tautology.rb"
data/exe/simplify_es ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ ######################################################################
3
+ ## Script for launching the simplify_qm.rb program ##
4
+ ######################################################################
5
+
6
+ begin
7
+ require 'rubygems'
8
+ gem 'logic_tools'
9
+ rescue LoadError
10
+ end
11
+
12
+ load "logic_tools/simplify_es.rb"
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+ #########################################################################
3
+ # Checks if a logic expression is a tautology #
4
+ #########################################################################
5
+
6
+
7
+ # For building logic trees
8
+ require "logic_tools/logictree.rb"
9
+
10
+ # For parsing the inputs
11
+ require "logic_tools/logicparse.rb"
12
+
13
+ # For converting the inputs to covers.
14
+ require "logic_tools/logicconvert.rb"
15
+
16
+ # For processing the cover (and therfore check tautology)
17
+ require "logic_tools/logiccover.rb"
18
+
19
+ # For the command line interface
20
+ require "logic_tools/logicinput.rb"
21
+
22
+ include LogicTools
23
+
24
+
25
+
26
+
27
+
28
+ ############################
29
+ # The main program
30
+
31
+ # Iterrate on each expression
32
+ each_input do |expr|
33
+ # Parse the expression.
34
+ parsed = string2logic(expr)
35
+ # print "parsed=#{parsed}\n"
36
+
37
+ # Convert it to a cover.
38
+ cover = parsed.to_cover
39
+ # print "cover=#{cover}\n"
40
+
41
+ # Checks if it is a tautology.
42
+ check = cover.is_tautology?
43
+
44
+ # Display the result
45
+ print check.to_s, "\n"
46
+ end
@@ -0,0 +1,142 @@
1
+ ######################################################################
2
+ # Sets of tools for converting logic representations to one another #
3
+ ######################################################################
4
+
5
+ # AND-OR-NOT tree representation.
6
+ require "logic_tools/logictree.rb"
7
+
8
+ # Cover representation.
9
+ require "logic_tools/logiccover.rb"
10
+
11
+ # TODO: bdd representation.
12
+
13
+ module LogicTools
14
+
15
+ class Node
16
+ ## Converts to a cover.
17
+ def to_cover()
18
+ # Check the cases of trivial trees.
19
+ if self.is_a?(NodeTrue) then
20
+ cover = Cover.new("all")
21
+ cover << Cube.new("-")
22
+ return cover
23
+ elsif self.is_a?(NodeFalse) then
24
+ return Cover.new()
25
+ end
26
+
27
+ # Get the variables for converting them to indexes in the cubes
28
+ vars = self.get_variables
29
+ # Converts the tree rooted by self to a sum of products
30
+ # (reduced to limit the number of cubes and their sizes).
31
+ tree = self.to_sum_product.reduce
32
+ # print "tree=#{tree}\n"
33
+
34
+ # Create an empty cover.
35
+ cover = Cover.new(*vars)
36
+
37
+ # Treat the trival cases.
38
+ case tree.op
39
+ when :true then
40
+ # Logic true
41
+ cover << Cube.new("-" * cover.width)
42
+ return cover
43
+ when :false then
44
+ # Logic false
45
+ return cover
46
+ when :variable then
47
+ # Single variable
48
+ cover << Cube.new("1")
49
+ return cover
50
+ when :not then
51
+ # Single complement of a variable
52
+ cover << Cube.new("0")
53
+ return cover
54
+ end
55
+
56
+ # Treat the other cases.
57
+
58
+ # Fill it with the cubes corresponding to each product
59
+ tree.each do |product|
60
+ product = [ product ] unless product.is_a?(NodeNary)
61
+ # print "product=#{product}\n"
62
+ # Generate the bit string of the cube
63
+ str = "-"*vars.size
64
+ product.each do |lit|
65
+ if lit.is_a?(NodeNot) then
66
+ index = vars.index(lit.child.variable)
67
+ # The litteral is a not
68
+ if str[index] == "1" then
69
+ # But it was "1" previously, contradictory cube:
70
+ # mark it for removal
71
+ str = nil
72
+ break
73
+ else
74
+ # No contradiction, put a "0"
75
+ str[index] = "0"
76
+ end
77
+ else
78
+ index = vars.index(lit.variable)
79
+ # The litteral is a variable
80
+ if str[index] == "0" then
81
+ # But it was "0" previously, contradictory cube:
82
+ # mark it for removal.
83
+ str = nil
84
+ break
85
+ else
86
+ # No contradiction, put a "1"
87
+ str[index] = "1"
88
+ end
89
+ end
90
+ end
91
+ # Create and add the corresponding cube if any.
92
+ cover.add(Cube.new(str)) if str
93
+ end
94
+ # print "cover=#{cover}\n"
95
+ # Remove the duplicate cubes if any.
96
+ cover.uniq!
97
+ # Return the resulting cover.
98
+ return cover
99
+ end
100
+ end
101
+
102
+ class Cover
103
+ ## Coverts to an AND-OR-NOT tree.
104
+ def to_tree()
105
+ # Generate the variable index.
106
+ vars = self.each_variable.to_a
107
+
108
+ # Treat the trivial cases.
109
+ if vars.empty? then
110
+ return self.empty? ? NodeFalse.new : NodeTrue.new
111
+ end
112
+ return NodeFalse.new if self.empty?
113
+
114
+ # Treat the other cases.
115
+
116
+ # Generate the products.
117
+ prods = self.each_cube.map do |cube|
118
+ # Generate the litterals of the and
119
+ litterals = []
120
+ cube.each.with_index do |val,i|
121
+ if val == "0" then
122
+ # "0" bits are converted to not litteral.
123
+ litterals << NodeNot.new(NodeVar.new(vars[i]))
124
+ elsif val == "1" then
125
+ # "1" bits are converted to variable litteral
126
+ litterals << NodeVar.new(vars[i])
127
+ end
128
+ end
129
+ # Create and and with the generated litterals.
130
+ NodeAnd.new(*litterals)
131
+ end
132
+ # Is there an empty and?
133
+ if prods.find { |node| node.empty? } then
134
+ # Yes, this is a tautology.
135
+ return NodeTrue.new
136
+ else
137
+ # No, generate the sum and return it.
138
+ return NodeOr.new(*prods)
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,828 @@
1
+ ########################################################################
2
+ # Logic cover classes: used for describing covers of boolean functions #
3
+ ########################################################################
4
+
5
+
6
+ require "logic_tools/minimal_column_covers.rb"
7
+
8
+ module LogicTools
9
+
10
+ ##
11
+ # Represents a boolean cube.
12
+ #
13
+ # NOTE: cubes are crutial for the performance of the implementation
14
+ # of the simplifying algorithm.
15
+ # Hence they are internally represented as strings, since they are
16
+ # much more energy efficient and usually faster than arrays.
17
+ #
18
+ # Then there are two interfaces:
19
+ # * The standard interface is based on strings and substrings only and
20
+ # is safe but slow.
21
+ #
22
+ # It includes the '[]' and '[]=' operators for accessing one bit,
23
+ # and each_char (or simply each) for iterating over the bits.
24
+ #
25
+ # With this interface, "0" stands for false, "1" for true and "-" for
26
+ # don't care.
27
+ #
28
+ # * The fast interface is based on bytes of a string, is fast but unsafe
29
+ # (it does not check the bits).
30
+ #
31
+ # It includes getbyte and setbyte for accessing one bit,
32
+ # and each_byte for iterating over the bits.
33
+ #
34
+ # With this interface, 48 stands for false, 49 for true and 45 for
35
+ # don't care.
36
+ #
37
+ class Cube
38
+
39
+ ## Creates a new cube from a bit string +bits+.
40
+ #
41
+ # NOTE: If +safe+ is set to false, +bits+ is not checked nor cloned.
42
+ def initialize(bits, safe = true)
43
+ if safe then
44
+ bits = bits.to_s
45
+ unless bits.match(/^[01-]*$/)
46
+ raise "Invalid bit string for describing a cube: "+ bits
47
+ end
48
+ @bits = bits.clone
49
+ else
50
+ @bits = bits.to_s
51
+ end
52
+ end
53
+
54
+ ## Gets the width (number of variables of the boolean space).
55
+ def width
56
+ return @bits.length
57
+ end
58
+
59
+ ## Evaluates the corresponding function's value for a binary +input+.
60
+ #
61
+ # +input+ is assumed to be an integer.
62
+ # Returns the evaluation result as a boolean.
63
+ def eval(input)
64
+ result = true
65
+ @bits.each_byte.with_index do |bit,i|
66
+ if bit == 49 then
67
+ result &= ((input & (2**i)) != 0)
68
+ elsif bit == 48 then
69
+ result &= ((input & (2**i)) == 0)
70
+ end
71
+ end
72
+ return result
73
+ end
74
+
75
+ ## Computes the distance with another +cube+.
76
+ def distance(cube)
77
+ return @bits.each_byte.with_index.count do |b,i|
78
+ # b != "-" and cube[i] != "-" and b != cube[i]
79
+ b != 45 and cube.getbyte(i) != 45 and b != cube.getbyte(i)
80
+ end
81
+ end
82
+
83
+ ## Converts to a string.
84
+ def to_s # :nodoc:
85
+ @bits.clone
86
+ end
87
+
88
+ ## Iterates over the bits of the cube as bytes.
89
+ #
90
+ # Returns an enumerator if no block given.
91
+ def each_byte(&blk)
92
+ # No block given? Return an enumerator.
93
+ return to_enum(:each_byte) unless block_given?
94
+
95
+ # Block given? Apply it on each bit.
96
+ @bits.each_byte(&blk)
97
+ end
98
+
99
+ ## Iterates over the bits of the cube as chars.
100
+ def each_char(&blk)
101
+ # No block given? Return an enumerator.
102
+ return to_enum(:each_char) unless block_given?
103
+
104
+ # Block given? Apply it on each bit.
105
+ @bits.each_char(&blk)
106
+ end
107
+ alias each each_char
108
+
109
+ ## The bit string defining the cube.
110
+ #
111
+ # Should not be modified directly, hence set as protected.
112
+ attr_reader :bits
113
+ protected :bits
114
+
115
+ ## Compares with another +cube+.
116
+ def ==(cube) # :nodoc:
117
+ @bits == cube.bits
118
+ end
119
+ alias eql? ==
120
+ def <=>(cube) #:nodoc:
121
+ @bits <=> cube.bits
122
+ end
123
+
124
+ ## Gets the hash of a cube
125
+ def hash
126
+ @bits.hash
127
+ end
128
+
129
+ ## duplicates the cube.
130
+ def clone # :nodoc:
131
+ Cube.new(@bits)
132
+ end
133
+ alias dup clone
134
+
135
+ ## Gets the char value of bit +i+.
136
+ def [](i)
137
+ @bits[i]
138
+ end
139
+
140
+ ## Gets the byte value of bit +i+.
141
+ def getbyte(i)
142
+ @bits.getbyte(i)
143
+ end
144
+
145
+ ## Sets the char value of bit +i+ to +b+.
146
+ def []=(i,b)
147
+ raise "Invalid bit value: #{b}" unless ["0","1","-"].include?(b)
148
+ # Update the bit string
149
+ @bits[i] = b
150
+ end
151
+
152
+ ## Sets the byte value of bit +i+ to +b+.
153
+ #
154
+ # NOTE: does not check b, so please use with caution.
155
+ def setbyte(i,b)
156
+ @bits.setbyte(i,b)
157
+ end
158
+
159
+ ## Computes the consensus with another +cube+.
160
+ #
161
+ # Returns the concensus cube if any.
162
+ def consensus(cube)
163
+ # Step 1: compute the distance between the cubes.
164
+ dist = self.distance(cube)
165
+ # Step 2: depending on the distance.
166
+ return nil if (dist != 1) # Distance is not 1: no consensus
167
+ # Distance is 1, the consensus is a single cube
168
+ # built by setting to "-" the opposite variable, and
169
+ # keeping all the other.
170
+ cbits = "-" * cube.width
171
+ @bits.each_byte.with_index do |bit,i|
172
+ # if bit != "-" then
173
+ if bit != 45 then
174
+ # cbits[i] = bit if (cube[i] == "-" or cube[i] == bit)
175
+ cbits.setbyte(i,bit) if (cube.getbyte(i) == 45 or
176
+ cube.getbyte(i) == bit)
177
+ else
178
+ # cbits[i] = cube[i]
179
+ cbits.setbyte(i,cube.getbyte(i))
180
+ end
181
+ end
182
+ return Cube.new(cbits,false) # No need to clone cbits.
183
+ end
184
+
185
+ ## Computes the sharp operation with another +cube+.
186
+ #
187
+ # Returns the resulting list of cubes as an array.
188
+ #
189
+ # (NOTE: not as a cover).
190
+ def sharp(cube)
191
+ result = []
192
+ # There is one such cube per litteral which is in
193
+ # self but not in cube.
194
+ @bits.each_byte.with_index do |bit,i|
195
+ # next if (cube[i] == "-" or cube[i] == bit)
196
+ next if (cube.getbyte(i) == 45 or cube.getbyte(i) == bit)
197
+ cbits = @bits.clone
198
+ # cbits[i] = (cube[i] == "0") ? "1" : "0"
199
+ cbits.setbyte(i, (cube.getbyte(i) == 48) ? 49 : 48)
200
+ result << Cube.new(cbits,false) # No need to clone cbits
201
+ end
202
+ # Remove duplicate cubes before ending.
203
+ result.uniq!
204
+ return result
205
+ end
206
+
207
+ ## Checks if +self+ can be merged with +cube+
208
+ def can_merge?(cube)
209
+ found = false # 0 to 1 or 1 to 0 pattern found
210
+ @bits.each_byte.with_index do |bit,i|
211
+ if (bit != cube.getbyte(i)) then
212
+ # Found one different bit
213
+ return false if found # But there were already one
214
+ found = true
215
+ end
216
+ end
217
+ # Can be merged
218
+ return true
219
+ end
220
+
221
+ ## Merges +self+ with +cube+ if possible.
222
+ #
223
+ # Returns the merge result as a new cube, or nil in case of failure.
224
+ def merge(cube)
225
+ # Create the bit string of the result.
226
+ cbits = "-" * self.width
227
+ found = false # 0 to 1 or 1 to 0 pattern found
228
+ @bits.each_byte.with_index do |bit,i|
229
+ if (bit != cube.getbyte(i)) then
230
+ # Found one different bit
231
+ return nil if found # But there were already one
232
+ found = true
233
+ else
234
+ # cbits[i] = bit
235
+ cbits.setbyte(i,bit)
236
+ end
237
+ end
238
+ # Can be merged
239
+ return Cube.new(cbits,false) # No need to clone cbits.
240
+ end
241
+
242
+ ## Checks if +self+ intersects with another +cube+.
243
+ def intersects?(cube)
244
+ return nil unless cube # No cube: intersection is empty.
245
+ # Cubes intersects if they do not include any 0,1 or 1,0 pattern.
246
+ return (not @bits.each_byte.with_index.find do |bit,i|
247
+ # bit != "-" and cube[i] != "-" and bit != cube[i]
248
+ bit != 45 and cube.getbyte(i) != 45 and bit != cube.getbyte(i)
249
+ end)
250
+ end
251
+
252
+ ## Creates the intersection between +self+ and another +cube+.
253
+ #
254
+ # Return a new cube giving the intersection, or nil if there is none.
255
+ def intersect(cube)
256
+ return nil unless cube # No cube: intersection is empty.
257
+ cbits = "-" * self.width
258
+ # Cubes intersects if they do not include any 0,1 or 1,0 pattern.
259
+ @bits.each_byte.with_index do |bit,i|
260
+ # if bit == "-" then
261
+ if bit == 45 then
262
+ # cbits[i] = cube[i]
263
+ cbits.setbyte(i,cube.getbyte(i))
264
+ # elsif cube[i] == "-" then
265
+ elsif cube.getbyte(i) == 45 then
266
+ # cbits[i] = bit
267
+ cbits.setbyte(i,bit)
268
+ elsif bit != cube.getbyte(i) then
269
+ # No intersection.
270
+ return nil
271
+ else
272
+ # cbits[i] = bit
273
+ cbits.setbyte(i,bit)
274
+ end
275
+ end
276
+ return Cube.new(cbits,false) # No need to duplicate cbits.
277
+ end
278
+
279
+ ## Tells if +cube+ is included.
280
+ def include?(cube)
281
+ # Look for a proof of non inclusion.
282
+ ! @bits.each_byte.with_index.find do |bit,i|
283
+ # bit != "-" and cube[i] != bit
284
+ bit != 45 and cube.getbyte(i) != bit
285
+ end
286
+ end
287
+
288
+ ## Iterates over the minterms included by the cube.
289
+ #
290
+ # The minterm are represented by bit strings.
291
+ #
292
+ # Returns an iterator if no block is given.
293
+ def each_minterm
294
+ # No block given? Return an enumerator.
295
+ return to_enum(:each_minterm) unless block_given?
296
+
297
+ # Block given? Apply it.
298
+ # Locate the "-" in the bit: they are the source of alternatives
299
+ # free_cols = @bits.size.times.find_all {|i| @bits[i] == "-" }
300
+ free_cols = @bits.size.times.find_all {|i| @bits.getbyte(i) == 45 }
301
+ # Generate each possible min term
302
+ if (free_cols.empty?) then
303
+ # Only one minterm
304
+ yield(@bits.clone)
305
+ else
306
+ # There are several minterms
307
+ (2 ** (free_cols.size)).times do |sel|
308
+ # Generate the minterm corresponding combination +sel+.
309
+ minterm = @bits.clone
310
+ free_cols.each.with_index do |col,i|
311
+ if (sel & (2 ** i) == 0) then
312
+ # The column is to 0
313
+ # minterm[col] = "0"
314
+ minterm.setbyte(col,48)
315
+ else
316
+ # The column is to 1
317
+ # minterm[col] = "1"
318
+ minterm.setbyte(col,49)
319
+ end
320
+ end
321
+ # The minterm is ready, use it.
322
+ yield(minterm)
323
+ end
324
+ end
325
+ end
326
+ end
327
+
328
+
329
+ ##
330
+ # Represents a cover of a boolean function.
331
+ class Cover
332
+
333
+ ## Creates a new cover on a boolean space represented by a list of
334
+ # +variables+.
335
+ def initialize(*variables)
336
+ @variables = *variables
337
+ # Initialize the cover
338
+ @cubes = []
339
+ # @sorted = false # Initialy, the cover is not sorted
340
+ end
341
+
342
+ ## Gets the width (the number of variables of the boolean space).
343
+ def width
344
+ return @variables.length
345
+ end
346
+
347
+ ## Gets the size (the number of cubes).
348
+ def size
349
+ return @cubes.size
350
+ end
351
+
352
+ ## Tells if the cover is empty.
353
+ def empty?
354
+ return @cubes.empty?
355
+ end
356
+
357
+ ## Gets a variable by +index+.
358
+ def variable(index)
359
+ return @variables[index].clone
360
+ end
361
+
362
+ ## Gets the index of a +variable+.
363
+ def variable_index(variable)
364
+ return @variables.index(variable)
365
+ end
366
+
367
+ ## Adds a +cube+ to the cover.
368
+ #
369
+ # Creates a new cube if +cube+ is not an instance of LogicTools::Cube.
370
+ def add(cube)
371
+ # Check the cube.
372
+ cube = Cube.new(cube) unless cube.is_a?(Cube)
373
+ if cube.width != self.width then
374
+ raise "Invalid cube width for #{cube}, expecting: #{self.width}"
375
+ end
376
+ # The cube is valid, add it.
377
+ @cubes.push(cube)
378
+ # # The cubes of the cover are therefore unsorted.
379
+ # @sorted = false
380
+ end
381
+ alias << add
382
+
383
+ ## Adds a +cube+ in front of the cover.
384
+ #
385
+ # Creates a new cube if +cube+ is not an instance of LogicTools::Cube.
386
+ def unshift(cube)
387
+ # Check the cube.
388
+ cube = Cube.new(cube) unless cube.is_a?(Cube)
389
+ if cube.width != self.width then
390
+ raise "Invalid cube width for #{cube}, expecting: #{self.width}"
391
+ end
392
+ # The cube is valid, add it.
393
+ @cubes.unshift(cube)
394
+ # # The cubes of the cover are therefore unsorted.
395
+ # @sorted = false
396
+ end
397
+
398
+ ## Iterates over the cubes of the cover.
399
+ #
400
+ # Returns an enumerator if no block is given.
401
+ def each_cube(&blk)
402
+ # No block given? Return an enumerator.
403
+ return to_enum(:each_cube) unless block_given?
404
+ # Block given? Apply it.
405
+ @cubes.each(&blk)
406
+ end
407
+ alias each each_cube
408
+
409
+ ## Gets a cube by +index+.
410
+ def [](index)
411
+ return @cubes[index]
412
+ end
413
+
414
+ ## Duplicates the cover.
415
+ def clone # :nodoc:
416
+ cover = Cover.new(*@variables)
417
+ @cubes.each { |cube| cover << cube }
418
+ return cover
419
+ end
420
+ alias dup clone
421
+
422
+ ## Duplicate the cover while improving a bit the result (for faster
423
+ # processing later).
424
+ def simpler_clone
425
+ # Clone.
426
+ cover = self.clone
427
+ # But remove duplicate cubes.
428
+ cover.uniq!
429
+ # And sort the cubes to group together cubes with a shorter
430
+ # distance.
431
+ cover.sort! { |c0,c1| c0.distance(c1) - 1 }
432
+ return cover
433
+ end
434
+
435
+ ## Iterates over the variables of the cube
436
+ #
437
+ # Returns an enumberator if no block is given
438
+ def each_variable(&blk)
439
+ # No block given? Return an enumerator
440
+ return to_enum(:each_variable) unless block_given?
441
+ # Block given? Apply it.
442
+ @variables.each(&blk)
443
+ end
444
+
445
+ ## Evaluates the corresponding function's value for a binary +input+.
446
+ #
447
+ # +input+ is assumed to be an integer.
448
+ # Returns the evaluation result as a boolean.
449
+ def eval(input)
450
+ # Evaluates each cube, if one results in true the result is true.
451
+ return !!@cubes.each.find {|cube| cube.eval(input) }
452
+ end
453
+
454
+ ## Converts to a string.
455
+ def to_s # :nodoc:
456
+ "[#{@variables.join(",")}],#{@cubes.join(",")}"
457
+ end
458
+
459
+ # ## Sorts the cubes.
460
+ # def sort!
461
+ # @cubes.sort! unless @sorted
462
+ # # Remember the cubes are sorted to avoid doing it again.
463
+ # @sorted = true
464
+ # return self
465
+ # end
466
+
467
+ ## Removes duplicate cubes.
468
+ def uniq!
469
+ @cubes.uniq!
470
+ return self
471
+ end
472
+
473
+ ## Sorts the cubes of the cover.
474
+ def sort!(&blk)
475
+ @cubes.sort!(&blk)
476
+ end
477
+
478
+ ## Generates the cofactor obtained when +var+ is set to +val+.
479
+ def cofactor(var,val)
480
+ # if val != "0" and val != "1" then
481
+ if val != 48 and val != 49 then
482
+ raise "Invalid value for generating a cofactor: #{val}"
483
+ end
484
+ # Get the index of the variable.
485
+ i = @variables.index(var)
486
+ # Create the new cover.
487
+ cover = Cover.new(*@variables)
488
+ # Set its cubes.
489
+ @cubes.each do |cube|
490
+ cube = cube.to_s
491
+ # cube[i] = "-" if cube[i] == val
492
+ cube.setbyte(i,45) if cube.getbyte(i) == val
493
+ # cover << Cube.new(cube) if cube[i] == "-"
494
+ cover << Cube.new(cube,false) if cube.getbyte(i) == 45
495
+ end
496
+ cover.uniq!
497
+ return cover
498
+ end
499
+
500
+ ## Generates the generalized cofactor from +cube+.
501
+ def cofactor_cube(cube)
502
+ # Create the new cover.
503
+ cover = Cover.new(*@variables)
504
+ # Set its cubes.
505
+ @cubes.each do |scube|
506
+ scube = scube.to_s
507
+ scube.size.times do |i|
508
+ if scube.getbyte(i) == cube.getbyte(i) then
509
+ # scube[i] = "-"
510
+ scube.setbyte(i,45)
511
+ # elsif (scube[i] != "-" and cube[i] != "-") then
512
+ elsif (scube.getbyte(i)!=45 and cube.getbyte(i)!=45) then
513
+ # The cube is to remove from the cover.
514
+ scube = nil
515
+ break
516
+ end
517
+ end
518
+ if scube then
519
+ # The cube is to keep in the cofactor.
520
+ cover << Cube.new(scube,false)
521
+ end
522
+ end
523
+ cover.uniq!
524
+ return cover
525
+ end
526
+
527
+ ## Looks for a binate variable.
528
+ #
529
+ # Returns the found binate variable or nil if not found.
530
+ #
531
+ # NOTE: Can also be used for checking if the cover is unate.
532
+ def find_binate
533
+ # Merge the cube over one another until a 1 over 0 or 0 over 1
534
+ # is met.
535
+ # The merging rules are to followings:
536
+ # 1 over 1 => 1
537
+ # 1 over - => 1
538
+ # 1 over 0 => not unate
539
+ # 0 over 0 => 0
540
+ # 0 over - => 0
541
+ # 0 over 1 => not unate
542
+ merge = "-" * self.width
543
+ @cubes.each do |cube|
544
+ cube.each_byte.with_index do |bit,i|
545
+ # if bit == "1" then
546
+ if bit == 49 then
547
+ # if merge[i] == "0" then
548
+ if merge.getbyte(i) == 48 then
549
+ # A 1 over 0 is found, a binate variable is found.
550
+ return @variables[i]
551
+ else
552
+ # merge[i] = "1"
553
+ merge.setbyte(i,49)
554
+ end
555
+ # elsif bit == "0" then
556
+ elsif bit == 48 then
557
+ # if merge[i] == "1" then
558
+ if merge.getbyte(i) == 49 then
559
+ # A 0 over 1 is found, a binate variable is found.
560
+ return @variables[i]
561
+ else
562
+ # merge[i] = "0"
563
+ merge.setbyte(i,48)
564
+ end
565
+ end
566
+ end
567
+ end
568
+ # The cover is unate: no binate variable.
569
+ return nil
570
+ end
571
+
572
+
573
+ ## Creates the union of self and +cover+.
574
+ #
575
+ # +cover+ is either an instance of LogicTools::Cover or
576
+ # a single instance of LogicTools::Cube.
577
+ def unite(cover)
578
+ # Check if the covers are compatible.
579
+ if (cover.width != self.width) then
580
+ raise "Incompatible cover for union: #{cover}"
581
+ end
582
+ # Creates the union cover.
583
+ union = Cover.new(*@variables)
584
+ # Fill it with the cubes of self and +cover+.
585
+ @cubes.each { |cube| union.add(cube.clone) }
586
+ if cover.is_a?(Cover) then
587
+ cover.each_cube { |cube| union.add(cube.clone) }
588
+ elsif cover.is_a?(Cube) then
589
+ union.add(cover.clone)
590
+ else
591
+ raise "Invalid class for cover union: #{cover.class}"
592
+ end
593
+ # Return the result.
594
+ return union
595
+ end
596
+ alias + unite
597
+
598
+ ## Creates the subtraction from +self+ minus one +cover+.
599
+ #
600
+ # +cover+ is either an instance of LogicTools::Cover or
601
+ # a single instance of LogicTools::Cube.
602
+ def subtract(cover)
603
+ # Check if the covers are compatible.
604
+ if (cover.width != self.width) then
605
+ raise "Incompatible cover for union: #{cover}"
606
+ end
607
+ # Creates the substraction cover.
608
+ subtraction = Cover.new(*@variables)
609
+ if cover.is_a?(Cube) then
610
+ cover = [cover]
611
+ elsif !(cover.is_a?(Cover)) then
612
+ raise "Invalid class for cover union: #{cover.class}"
613
+ end
614
+ @cubes.each do |cube|
615
+ subtraction << cube unless cover.each.include?(cube)
616
+ end
617
+ # Return the result.
618
+ return subtraction
619
+ end
620
+ alias - subtract
621
+
622
+ ## Generates the complement cover.
623
+ def complement
624
+ # First treat the case when the cover is empty:
625
+ # the result is the tautology.
626
+ if @cubes.empty? then
627
+ result = Cover.new(*@variables)
628
+ result << Cube.new("-"*self.width,false)
629
+ return result
630
+ end
631
+ # Otherwise...
632
+
633
+ # Look for a binate variable to split on.
634
+ binate = self.find_binate
635
+ unless binate then
636
+ # The cover is actually unate, complement it the fast way.
637
+ # Step 1: Generate the following boolean matrix:
638
+ # each "0" and "1" is transformed to "1"
639
+ # each "-" is transformed to "0"
640
+ matrix = []
641
+ @cubes.each do |cube|
642
+ line = " " * self.width
643
+ matrix << line
644
+ cube.each_byte.with_index do |bit,i|
645
+ # line[i] = (bit == "0" or bit == "1") ? "1" : "0"
646
+ line.setbyte(i, (bit == 48 or bit == 49) ? 49 : 48 )
647
+ end
648
+ end
649
+ # Step 2: finds all the minimal column covers of the matrix
650
+ mins = minimal_column_covers(matrix)
651
+ # Step 3: generates the complent cover from the minimal
652
+ # column covers.
653
+ # Each minimal column cover is converted to a cube using
654
+ # the following rules (only valid because the initial cover
655
+ # is unate):
656
+ # * a minimal column whose variable can be reduced to 1
657
+ # is converted to the not of the variable
658
+ # * a minimal column whose variable can be reduced to 0 is
659
+ # converted to the variable
660
+ #
661
+ # +result+ is the final complement cover.
662
+ result = Cover.new(*@variables)
663
+ # print "mins=#{mins}\n"
664
+ mins.each do |min|
665
+ # +cbits+ is the bit string describing the cube built
666
+ # from the column cover +min+.
667
+ cbits = "-" * self.width
668
+ min.each do |col|
669
+ # if @cubes.find {|cube| cube[col] == "1" } then
670
+ if @cubes.find {|cube| cube.getbyte(col) == 49 } then
671
+ # cbits[col] = "0"
672
+ cbits.setbyte(col,48)
673
+ else
674
+ # cbits[col] = "1"
675
+ cbits.setbyte(col,49)
676
+ end
677
+ end
678
+ result << Cube.new(cbits,false)
679
+ end
680
+ return result
681
+ else
682
+ # Compute the cofactors over the binate variables.
683
+ # cf0 = self.cofactor(binate,"0")
684
+ cf0 = self.cofactor(binate,48)
685
+ # cf1 = self.cofactor(binate,"1")
686
+ cf1 = self.cofactor(binate,49)
687
+ # Complement them.
688
+ cf0 = cf0.complement
689
+ cf1 = cf1.complement
690
+ # Build the resulting complement cover as:
691
+ # (cf0 and (not binate)) or (cf1 and binate)
692
+ result = Cover.new(*@variables)
693
+ # Get the index of the binate variable.
694
+ i = @variables.index(binate)
695
+ cf0.each_cube do |cube| # cf0 and (not binate)
696
+ # if cube[i] != "1" then
697
+ if cube.getbyte(i) != 49 then
698
+ # Cube's binate is not "1" so the cube can be kept
699
+ # cube[i] = "0"
700
+ cube.setbyte(i,48)
701
+ result << cube
702
+ end
703
+ end
704
+ cf1.each_cube do |cube| # cf1 and binate
705
+ # if cube[i] != "0" then
706
+ if cube.getbyte(i) != 48 then
707
+ # Cube's binate is not "0" so the cube can be kept
708
+ # cube[i] = "1"
709
+ cube.setbyte(i,49)
710
+ result << cube
711
+ end
712
+ end
713
+ return result
714
+ end
715
+ end
716
+
717
+
718
+ ## Checks if self is a tautology.
719
+ def is_tautology?
720
+ # Look for a binate variable to split on.
721
+ binate = self.find_binate
722
+ # Gets its index
723
+ i = @variables.index(binate)
724
+ unless binate then
725
+ # The cover is actually unate, check it the fast way.
726
+ # Does it contain a "-" only cube? If yes, this is a tautology.
727
+ @cubes.each do |cube|
728
+ # return true unless cube.each_bit.find { |bit| bit != "-" }
729
+ return true unless cube.each_byte.find { |bit| bit != 45 }
730
+ end
731
+ # No "-" only cube, this is not a tautology
732
+ return false
733
+ #
734
+ # Other techniques: actually general, not necessarily on
735
+ # unate cover! Therefore WRONG place!
736
+ # The cover is actually unate, check it the fast way.
737
+ # Does it contain a "-" only cube? If yes, this is a tautology.
738
+ # @cubes.each do |cube|
739
+ # # return true unless cube.each_bit.find { |bit| bit != "-" }
740
+ # return true unless cube.each_bit.find { |bit| bit != 45 }
741
+ # end
742
+ # # Is there a "1" only or "0" only column? If yes, this is not
743
+ # # a tautology.
744
+ # self.width.times do |col|
745
+ # fbit = @cubes[0][col]
746
+ # # next if fbit == "-"
747
+ # next if fbit == 45
748
+ # next if (1..(@cubes.size-1)).each.find do |bit|
749
+ # bit != fbit
750
+ # end
751
+ # return false # Not a tautology.
752
+ # end
753
+ # # Check the upper bound of the number of minterms:
754
+ # # if < 2**width, not a tautology.
755
+ # num_minterms = 0
756
+ # @cubes.each do |cube|
757
+ # # num_minterms += 2 ** cube.each_bit.count {|b| b == "-"}
758
+ # num_minterms += 2 ** cube.each_bit.count {|b| b == 45}
759
+ # end
760
+ # return false if num_minterms < 2**self.width
761
+ # # Last check: the truth table.
762
+ # (2**self.width).times do |input|
763
+ # return false if self.eval(input) == 0
764
+ # end
765
+ else
766
+ # Compute the cofactors over the binate variables.
767
+ # cf0 = self.cofactor(binate,"0")
768
+ cf0 = self.cofactor(binate,48)
769
+ # cf1 = self.cofactor(binate,"1")
770
+ cf1 = self.cofactor(binate,49)
771
+ # Check both: if there are tautologies, self is also a
772
+ # tautology
773
+ return ( cf0.is_tautology? and cf1.is_tautology? )
774
+ end
775
+ end
776
+
777
+
778
+ ## Creates the smallest cube containing +self+.
779
+ def smallest_containing_cube
780
+ return nil if @cubes.empty? # Empty cover case.
781
+ # Create a new cube including "-" unless the columns of
782
+ # all the cubes are identical.
783
+ cbits = "-" * self.width
784
+ self.width.times do |i|
785
+ # print "cbits=#{cbits}\n"
786
+ # cbits[i] = @cubes.reduce(nil) do |bit,cube|
787
+ cbits.setbyte(i, @cubes.reduce(nil) do |bit,cube|
788
+ # print "bit=#{bit} i=#{i} cube=#{cube}\n"
789
+ if bit == nil then
790
+ # bit = cube[i]
791
+ bit = cube.getbyte(i)
792
+ # elsif bit != cube[i]
793
+ elsif bit != cube.getbyte(i)
794
+ # bit = "-"
795
+ bit = 45
796
+ break bit
797
+ end
798
+ bit
799
+ end)
800
+ end
801
+ return Cube.new(cbits,false) # No need to clone cbits
802
+ end
803
+
804
+
805
+ ## Checks if +self+ intersects with +cube_or_cover+.
806
+ #
807
+ # +cube_or_cover+ is either a full LogicTools::Cover object or a single
808
+ # cube object (LogicTools::Cube or bit string).
809
+ def intersects?(cube_or_cover)
810
+ if cube_or_cover.is_a?(Cover) then
811
+ # Cover case: check intersect with each cube of +cube_or_cover+.
812
+ #
813
+ # NOTE: !! is for converting the result to boolean.
814
+ return !!( cube_or_cover.each_cube.find do |cube|
815
+ self.intersects?(cube)
816
+ end )
817
+ else
818
+ # Cube case.
819
+ #
820
+ # NOTE: !! is for converting the result to boolean.
821
+ return !!( @cubes.find do |cube|
822
+ cube.intersects?(cube_or_cover)
823
+ end )
824
+ end
825
+ end
826
+ end
827
+
828
+ end