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