logic_tools 0.3.3 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/logic_tools/logicconvert.rb +27 -10
- data/lib/logic_tools/logicgenerator.rb +2 -0
- data/lib/logic_tools/logicparse.rb +1 -1
- data/lib/logic_tools/logicsimplify_qm.rb +91 -43
- data/lib/logic_tools/minimal_column_covers.rb +7 -3
- data/lib/logic_tools/simplify_es.rb +1 -0
- data/lib/logic_tools/test_logic_tools.rb +99 -1
- data/lib/logic_tools/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 84eda2845101b1b4e6e59e9ad408f4d2e246e930
|
4
|
+
data.tar.gz: f2b8eaa53ef5b7f14e7a93240a12acc122021d92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b13d4bb1a6227c6abc413296077d374dbbf833b701830ebf5a5d2ad24ef7c13e9a8561367a7ccd67db7bcfb1044c1bfdd5b1755142858ea082d704f6e54a91ca
|
7
|
+
data.tar.gz: 57e5fd61601d088914c024c812b5310474dbd6c526c70169ddacb16098f47e40bf798aa40697156bae67b16c81d9bfb4194180a4192ccd07ed728c2da11e5455
|
data/README.md
CHANGED
@@ -101,6 +101,6 @@ The gem is available as open source under the terms of the [MIT License](http://
|
|
101
101
|
|
102
102
|
|
103
103
|
## To do
|
104
|
-
* Update the code of simplify_qm so that it uses the optimized LogicTool::Cover class
|
104
|
+
* Update the code of simplify_qm so that it uses the optimized LogicTool::Cover class.
|
105
105
|
|
106
106
|
|
@@ -13,19 +13,27 @@ require "logic_tools/logiccover.rb"
|
|
13
13
|
module LogicTools
|
14
14
|
|
15
15
|
class Node
|
16
|
-
## Converts to a cover
|
17
|
-
|
16
|
+
## Converts to a cover of a boolean space based of +variables+.
|
17
|
+
#
|
18
|
+
# NOTE: the variables of the space are also extracted from +self+.
|
19
|
+
def to_cover(*variables)
|
18
20
|
# Check the cases of trivial trees.
|
19
21
|
if self.is_a?(NodeTrue) then
|
20
|
-
|
21
|
-
|
22
|
+
if variables.empty? then
|
23
|
+
cover = Cover.new("all")
|
24
|
+
cover << Cube.new("-")
|
25
|
+
else
|
26
|
+
cover = Cover.new(*variables)
|
27
|
+
cover << Cube.new("-"*variables.size)
|
28
|
+
end
|
22
29
|
return cover
|
23
30
|
elsif self.is_a?(NodeFalse) then
|
24
|
-
return Cover.new()
|
31
|
+
return Cover.new(*variables)
|
25
32
|
end
|
26
33
|
|
27
34
|
# Get the variables for converting them to indexes in the cubes
|
28
|
-
vars = self.get_variables
|
35
|
+
vars = (variables + self.get_variables.map(&:to_s)).uniq
|
36
|
+
# print "vars=#{vars}\n"
|
29
37
|
# Converts the tree rooted by self to a sum of products
|
30
38
|
# (reduced to limit the number of cubes and their sizes).
|
31
39
|
tree = self.to_sum_product.reduce
|
@@ -45,16 +53,25 @@ module LogicTools
|
|
45
53
|
return cover
|
46
54
|
when :variable then
|
47
55
|
# Single variable
|
48
|
-
|
56
|
+
str = "-" * cover.width
|
57
|
+
index = vars.index(tree.variable.to_s)
|
58
|
+
str[index] = "1"
|
59
|
+
cover << Cube.new(str)
|
49
60
|
return cover
|
50
61
|
when :not then
|
51
62
|
# Single complement of a variable
|
52
|
-
|
63
|
+
str = "-" * cover.width
|
64
|
+
index = vars.index(tree.child.variable.to_s)
|
65
|
+
str[index] = "0"
|
66
|
+
cover << Cube.new(str)
|
53
67
|
return cover
|
54
68
|
end
|
55
69
|
|
56
70
|
# Treat the other cases.
|
57
71
|
|
72
|
+
# Ensure we have a sum of product structure.
|
73
|
+
tree = [ tree ] unless tree.op == :or
|
74
|
+
|
58
75
|
# Fill it with the cubes corresponding to each product
|
59
76
|
tree.each do |product|
|
60
77
|
product = [ product ] unless product.is_a?(NodeNary)
|
@@ -63,7 +80,7 @@ module LogicTools
|
|
63
80
|
str = "-"*vars.size
|
64
81
|
product.each do |lit|
|
65
82
|
if lit.is_a?(NodeNot) then
|
66
|
-
index = vars.index(lit.child.variable)
|
83
|
+
index = vars.index(lit.child.variable.to_s)
|
67
84
|
# The litteral is a not
|
68
85
|
if str[index] == "1" then
|
69
86
|
# But it was "1" previously, contradictory cube:
|
@@ -75,7 +92,7 @@ module LogicTools
|
|
75
92
|
str[index] = "0"
|
76
93
|
end
|
77
94
|
else
|
78
|
-
index = vars.index(lit.variable)
|
95
|
+
index = vars.index(lit.variable.to_s)
|
79
96
|
# The litteral is a variable
|
80
97
|
if str[index] == "0" then
|
81
98
|
# But it was "0" previously, contradictory cube:
|
@@ -25,7 +25,7 @@ module LogicTools
|
|
25
25
|
# rule(:var) { match('[A-Za-uw-z]') }
|
26
26
|
rule(:var) do
|
27
27
|
match('[A-Za-z]') |
|
28
|
-
str("{") >> ( match('[
|
28
|
+
str("{") >> ( match('[0-9A-Za-z]').repeat ) >> str("}")
|
29
29
|
end
|
30
30
|
# And operator
|
31
31
|
# rule(:andop) { str("&&") | match('[&\.\*^]') }
|
@@ -7,6 +7,7 @@
|
|
7
7
|
require 'set'
|
8
8
|
|
9
9
|
require "logic_tools/logictree.rb"
|
10
|
+
require "logic_tools/minimal_column_covers.rb"
|
10
11
|
|
11
12
|
module LogicTools
|
12
13
|
|
@@ -252,6 +253,11 @@ module LogicTools
|
|
252
253
|
#
|
253
254
|
# Uses the Quine-Mc Cluskey method.
|
254
255
|
def simplify
|
256
|
+
# Step 0 checks the trivial cases.
|
257
|
+
if self.op == :true or self.op == :false then
|
258
|
+
return self.clone
|
259
|
+
end
|
260
|
+
|
255
261
|
# Step 1: get the generators
|
256
262
|
|
257
263
|
# Gather the minterms which set the function to 1 encoded as
|
@@ -324,55 +330,97 @@ module LogicTools
|
|
324
330
|
# print "generators with covers:\n"
|
325
331
|
# generators.each {|gen| print gen,": ", gen.covers,"\n" }
|
326
332
|
|
327
|
-
# Step 2: remove the redundancies
|
328
|
-
|
329
|
-
# Select the generators using Petrick's method
|
330
|
-
# For that purpose treat the generators as variables
|
331
|
-
variables = generators.map {|gen| VarImp.new(gen) }
|
333
|
+
# Step 2: remove the redundancies by finding the minimal column
|
334
|
+
# sets cover from the generators.
|
332
335
|
|
333
|
-
#
|
336
|
+
# # Select the generators using Petrick's method
|
337
|
+
# # For that purpose treat the generators as variables
|
338
|
+
# variables = generators.map {|gen| VarImp.new(gen) }
|
339
|
+
#
|
340
|
+
# # Group the variables by cover
|
341
|
+
# cover2gen = Hash.new { |h,k| h[k] = [] }
|
342
|
+
# variables.each do |var|
|
343
|
+
# # print "var=#{var}, implicant=#{var.implicant}, covers=#{var.implicant.covers}\n"
|
344
|
+
# var.implicant.covers.each { |cov| cover2gen[cov] << var }
|
345
|
+
# end
|
346
|
+
# # Convert this hierachical table to a product of sum
|
347
|
+
# # First the sum terms
|
348
|
+
# sums = cover2gen.each_value.map do |vars|
|
349
|
+
# # print "vars=#{vars}\n"
|
350
|
+
# if vars.size > 1 then
|
351
|
+
# NodeOr.new(*vars.map {|var| NodeVar.new(var) })
|
352
|
+
# else
|
353
|
+
# NodeVar.new(vars[0])
|
354
|
+
# end
|
355
|
+
# end
|
356
|
+
# # print "sums = #{sums.to_s}\n"
|
357
|
+
# # Then the product
|
358
|
+
# # expr = NodeAnd.new(*sums).uniq
|
359
|
+
# if sums.size > 1 then
|
360
|
+
# expr = NodeAnd.new(*sums).reduce
|
361
|
+
# else
|
362
|
+
# expr = sums[0]
|
363
|
+
# end
|
364
|
+
# # Convert it to a sum of product
|
365
|
+
# # print "expr = #{expr.to_s}\n"
|
366
|
+
# expr = expr.to_sum_product(true)
|
367
|
+
# # print "Now expr = #{expr.to_s} (#{expr.class})\n"
|
368
|
+
# # Select the smallest term (if several)
|
369
|
+
# if (expr.op == :or) then
|
370
|
+
# smallest = expr.min_by do |term|
|
371
|
+
# term.op == :and ? term.size : 1
|
372
|
+
# end
|
373
|
+
# else
|
374
|
+
# smallest = expr
|
375
|
+
# end
|
376
|
+
# # The corresponding implicants are the selected generators
|
377
|
+
# if smallest.op == :and then
|
378
|
+
# selected = smallest.map {|term| term.variable.implicant }
|
379
|
+
# else
|
380
|
+
# selected = [ smallest.variable.implicant ]
|
381
|
+
# end
|
382
|
+
|
383
|
+
# Creates the matrix for looking for the minimal column cover:
|
384
|
+
# the rows stands for the covers and the columns stands for the
|
385
|
+
# generator. A "1" indicates a cover is obtained from the
|
386
|
+
# corresponding generator.
|
387
|
+
matrix = []
|
388
|
+
# Set the index table of the generators for faster lookup.
|
389
|
+
gen2index = {}
|
390
|
+
generators.each.with_index { |gen,i| gen2index[gen] = i }
|
391
|
+
# Group the generators by cover
|
334
392
|
cover2gen = Hash.new { |h,k| h[k] = [] }
|
335
|
-
|
336
|
-
# print "
|
337
|
-
|
393
|
+
generators.each do |gen|
|
394
|
+
# print "gen=#{gen}, covers=#{gen.covers}\n"
|
395
|
+
gen.covers.each { |cover| cover2gen[cover] << gen }
|
338
396
|
end
|
339
|
-
#
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
NodeVar.new(vars[0])
|
347
|
-
end
|
397
|
+
# Fill the matrix with it.
|
398
|
+
cover2gen.each do |cover,gens|
|
399
|
+
# print "cover=#{cover}, gens=#{gens}\n"
|
400
|
+
row = "0" * generators.size
|
401
|
+
# Set the "1" (49 in byte).
|
402
|
+
gens.each { |gen| row.setbyte(gen2index[gen],49) }
|
403
|
+
matrix << row
|
348
404
|
end
|
349
|
-
#
|
350
|
-
#
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
term.op == :and ? term.size : 1
|
365
|
-
end
|
366
|
-
else
|
367
|
-
smallest = expr
|
368
|
-
end
|
369
|
-
# The corresponding implicants are the selected generators
|
370
|
-
if smallest.op == :and then
|
371
|
-
selected = smallest.map {|term| term.variable.implicant }
|
372
|
-
else
|
373
|
-
selected = [ smallest.variable.implicant ]
|
405
|
+
# Find the minimal column cover.
|
406
|
+
# print "matrix=#{matrix}\n"
|
407
|
+
cols = minimal_column_covers(matrix, true)
|
408
|
+
|
409
|
+
# Get the selected generators (implicants).
|
410
|
+
selected = cols.map { |col| generators[col] }
|
411
|
+
|
412
|
+
# Handle the trivial case
|
413
|
+
if selected.empty? then
|
414
|
+
# false case.
|
415
|
+
return NodeFlase.new
|
416
|
+
elsif selected.size == 1 and
|
417
|
+
! selected[0].each.find {|c| c == "1" or c == "0" }
|
418
|
+
# true case
|
419
|
+
return NodeTrue.new
|
374
420
|
end
|
375
421
|
|
422
|
+
# The other cases
|
423
|
+
|
376
424
|
# Sort by variable order
|
377
425
|
selected.sort_by! { |implicant| implicant.bits }
|
378
426
|
|
@@ -120,9 +120,13 @@ module LogicTools
|
|
120
120
|
def solve()
|
121
121
|
# Solve the problem throughly.
|
122
122
|
begin
|
123
|
-
|
124
|
-
|
125
|
-
|
123
|
+
if @deadline != Float::INFINITY then
|
124
|
+
Timeout::timeout(@deadline) {
|
125
|
+
self.branch(0)
|
126
|
+
}
|
127
|
+
else
|
128
|
+
self.branch(0)
|
129
|
+
end
|
126
130
|
rescue Timeout::Error
|
127
131
|
# Time out, is there a solution?
|
128
132
|
# print "Timeout!\n"
|
@@ -5,8 +5,8 @@
|
|
5
5
|
######################################################################
|
6
6
|
|
7
7
|
# require 'minitest/autorun'
|
8
|
-
require "logic_tools/logicsimplify_es.rb"
|
9
8
|
require "logic_tools/logicgenerator.rb"
|
9
|
+
require "logic_tools/logicconvert.rb"
|
10
10
|
|
11
11
|
include LogicTools
|
12
12
|
|
@@ -18,6 +18,9 @@ class TestEspresso # < MiniTest::Unit::TestCase
|
|
18
18
|
# the cover.
|
19
19
|
def initialize(seed = 0, deadline = Float::INFINITY,
|
20
20
|
volume = Float::INFINITY)
|
21
|
+
# Ensures ESPRESSO is used.
|
22
|
+
load "logic_tools/logicsimplify_es.rb"
|
23
|
+
|
21
24
|
@seed = seed
|
22
25
|
@deadline = deadline
|
23
26
|
@volume = volume
|
@@ -85,6 +88,47 @@ class TestEspresso # < MiniTest::Unit::TestCase
|
|
85
88
|
end
|
86
89
|
|
87
90
|
|
91
|
+
## Tests Quine Mac Cluskey on a given +tree+.
|
92
|
+
def test_qm(tree)
|
93
|
+
print "Quine Mc Cluskey algorithm on expression=[#{tree}]...\n"
|
94
|
+
simple = tree.simplify()
|
95
|
+
print "result: [#{tree}]\n"
|
96
|
+
cover = tree.to_cover
|
97
|
+
check0 = (cover + simple.complement).is_tautology?
|
98
|
+
# check0 = same_truth_table?(cover,simple)
|
99
|
+
# assert_equal(true,check0)
|
100
|
+
print "check 0 = #{check0}\n"
|
101
|
+
raise "Test failure" unless check0
|
102
|
+
check1 = (cover.complement + simple).is_tautology?
|
103
|
+
# assert_equal(true,check1)
|
104
|
+
print "check 1 = #{check1}\n"
|
105
|
+
raise "Test failure" unless check1
|
106
|
+
return true
|
107
|
+
end
|
108
|
+
|
109
|
+
## Tests the implementation of the espresso algorithm on each
|
110
|
+
# possible 1-cube cover of 4 variables.
|
111
|
+
#
|
112
|
+
# Test only on cover if a +test+ number is given.
|
113
|
+
def test_qm_all(test = nil)
|
114
|
+
generator = Generator.new("a","b","c","d")
|
115
|
+
generator.seed = @seed
|
116
|
+
if test then
|
117
|
+
test = test.to_i
|
118
|
+
print "Test #{test}: "
|
119
|
+
return test_qm(generator.make_std_conj(test))
|
120
|
+
else
|
121
|
+
generator.each_std_conj.with_index do |tree,i|
|
122
|
+
print "Test #{i}: "
|
123
|
+
return false unless test_qm(tree)
|
124
|
+
end
|
125
|
+
return true
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
|
131
|
+
|
88
132
|
## Tests espresso on a given +cover+.
|
89
133
|
def test_espresso(cover)
|
90
134
|
print "ESPRESSO on cover=[#{cover.to_s}]...\n"
|
@@ -159,3 +203,57 @@ class TestEspresso # < MiniTest::Unit::TestCase
|
|
159
203
|
end
|
160
204
|
|
161
205
|
end
|
206
|
+
|
207
|
+
|
208
|
+
## Class for testing the implementation Quine Mc Cluskey algorithm.
|
209
|
+
class TestQM
|
210
|
+
|
211
|
+
## Creates the tester with a +seed+ for random generation.
|
212
|
+
def initialize(seed = 0)
|
213
|
+
# Ensures QM is used.
|
214
|
+
load "logic_tools/logicsimplify_qm.rb"
|
215
|
+
@seed = seed
|
216
|
+
end
|
217
|
+
|
218
|
+
## Tests Quine Mac Cluskey on a given +tree+.
|
219
|
+
def test_qm(tree,generator)
|
220
|
+
print "Quine Mc Cluskey algorithm on expression=[#{tree}]...\n"
|
221
|
+
simple = tree.simplify()
|
222
|
+
print "result: [#{simple}]\n"
|
223
|
+
cover = tree.to_cover(*generator.each_variable)
|
224
|
+
# print "cover=#{cover}\n"
|
225
|
+
simple_cover = simple.to_cover(*generator.each_variable)
|
226
|
+
# print "simple_cover=#{simple_cover}\n"
|
227
|
+
check0 = (cover + simple_cover.complement).is_tautology?
|
228
|
+
# check0 = same_truth_table?(cover,simple)
|
229
|
+
# assert_equal(true,check0)
|
230
|
+
print "check 0 = #{check0}\n"
|
231
|
+
raise "Test failure" unless check0
|
232
|
+
check1 = (cover.complement + simple_cover).is_tautology?
|
233
|
+
# assert_equal(true,check1)
|
234
|
+
print "check 1 = #{check1}\n"
|
235
|
+
raise "Test failure" unless check1
|
236
|
+
return true
|
237
|
+
end
|
238
|
+
|
239
|
+
## Tests the implementation of the espresso algorithm on each
|
240
|
+
# possible 1-cube cover of 4 variables.
|
241
|
+
#
|
242
|
+
# Test only on cover if a +test+ number is given.
|
243
|
+
def test_qm_all(test = nil)
|
244
|
+
generator = Generator.new("a","b","c","d")
|
245
|
+
generator.seed = @seed
|
246
|
+
if test then
|
247
|
+
test = test.to_i
|
248
|
+
print "Test #{test}: "
|
249
|
+
return test_qm(generator.make_std_conj(test),generator)
|
250
|
+
else
|
251
|
+
generator.each_std_conj.with_index do |tree,i|
|
252
|
+
print "Test #{i}: "
|
253
|
+
return false unless test_qm(tree,generator)
|
254
|
+
end
|
255
|
+
return true
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
end
|
data/lib/logic_tools/version.rb
CHANGED