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 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