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 +4 -4
- data/.gitignore +3 -0
- data/exe/is_tautology +12 -0
- data/exe/simplify_es +12 -0
- data/lib/logic_tools/is_tautology.rb +46 -0
- data/lib/logic_tools/logicconvert.rb +142 -0
- data/lib/logic_tools/logiccover.rb +828 -0
- data/lib/logic_tools/logicgenerator.rb +222 -0
- data/lib/logic_tools/logicparse.rb +9 -2
- data/lib/logic_tools/logicsimplify_es.rb +734 -0
- data/lib/logic_tools/{logicsimplify.rb → logicsimplify_qm.rb} +10 -3
- data/lib/logic_tools/logictree.rb +200 -36
- data/lib/logic_tools/minimal_column_covers.rb +481 -0
- data/lib/logic_tools/simplify_es.rb +39 -0
- data/lib/logic_tools/simplify_qm.rb +2 -18
- data/lib/logic_tools/test_logic_tools.rb +161 -0
- data/lib/logic_tools/traces.rb +116 -0
- data/lib/logic_tools/truth_tbl.rb +2 -2
- data/lib/logic_tools/version.rb +1 -1
- metadata +16 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5306034ce28f57c0fb9ee57b9d5c3643682f99e2
|
4
|
+
data.tar.gz: f879af69f02c8d96e94e2dfeccc270147d6e621d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aefc4e9df205c84a42b3157281048ce8c98fe4f097a0d3e3441b0fe13fa8490a4727c66d73aaa92b1af10e1b1ee5c03805e3f941f1f8ae3dc8f563a4d1379a9a
|
7
|
+
data.tar.gz: 03eb53a6df7171d407f6f29f116d3e0ad718767cba29ff4dc19c2bf6bbbc1d6e4ef38754eac8491c2a027008f841fdfd330da36a5f815c7a02a2d751d4ddcc6e
|
data/.gitignore
CHANGED
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
|