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.
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