logic_tools 0.3.3 → 0.3.4
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/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