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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e3959c85da2264b72f5ae3a4b0fe8eda6cfab768
4
- data.tar.gz: 0424225bcee4edac9f7e161024b61c9427aa91a1
3
+ metadata.gz: 84eda2845101b1b4e6e59e9ad408f4d2e246e930
4
+ data.tar.gz: f2b8eaa53ef5b7f14e7a93240a12acc122021d92
5
5
  SHA512:
6
- metadata.gz: ba63032be6f4b07afadab832f410f4b365132ac89fd5f0e7e926b9413e7de3d7e6f96cca1a710b187e18c0599681ba1002a86c1dab2b8997b882f110c773a8a2
7
- data.tar.gz: fb61f85742828cae87c0ac3ff377ffef9a51c0cb4a740f52bf6da16b0a73565f8505302799acddc5c024b5065c017b4340cedde8dba2917d74b3e72c61a492e6
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 and minimal\_column\_covers methods.
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
- def to_cover()
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
- cover = Cover.new("all")
21
- cover << Cube.new("-")
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
- cover << Cube.new("1")
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
- cover << Cube.new("0")
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:
@@ -113,6 +113,8 @@ module LogicTools
113
113
  terms << make_minterm(i)
114
114
  end
115
115
  end
116
+ # If no term, return a NodeFalse
117
+ return NodeFalse.new if terms.empty?
116
118
  # Generate and return the resulting sum.
117
119
  return NodeOr.new(*terms)
118
120
  end
@@ -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('[a^Za-z]').repeat ) >> str("}")
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
- # Group the variables by cover
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
- variables.each do |var|
336
- # print "var=#{var}, implicant=#{var.implicant}, covers=#{var.implicant.covers}\n"
337
- var.implicant.covers.each { |cov| cover2gen[cov] << var }
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
- # Convert this hierachical table to a product of sum
340
- # First the sum terms
341
- sums = cover2gen.each_value.map do |vars|
342
- # print "vars=#{vars}\n"
343
- if vars.size > 1 then
344
- NodeOr.new(*vars.map {|var| NodeVar.new(var) })
345
- else
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
- # print "sums = #{sums.to_s}\n"
350
- # Then the product
351
- # expr = NodeAnd.new(*sums).uniq
352
- if sums.size > 1 then
353
- expr = NodeAnd.new(*sums).reduce
354
- else
355
- expr = sums[0]
356
- end
357
- # Convert it to a sum of product
358
- # print "expr = #{expr.to_s}\n"
359
- expr = expr.to_sum_product(true)
360
- # print "Now expr = #{expr.to_s} (#{expr.class})\n"
361
- # Select the smallest term (if several)
362
- if (expr.op == :or) then
363
- smallest = expr.min_by do |term|
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
- Timeout::timeout(@deadline) {
124
- self.branch(0)
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"
@@ -32,6 +32,7 @@ each_input do |expr|
32
32
 
33
33
  # Simplify it
34
34
  simple = parsed.simplify
35
+ simple = simple.sort
35
36
  # print "Computation done\n"
36
37
 
37
38
  # Display the result
@@ -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
@@ -1,3 +1,3 @@
1
1
  module LogicTools
2
- VERSION = "0.3.3"
2
+ VERSION = "0.3.4"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logic_tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lovic Gauthier