logic_tools 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +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
|