logic_tools 0.2.1
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 +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +81 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/simplify_qm +12 -0
- data/exe/std_conj +12 -0
- data/exe/std_dij +12 -0
- data/exe/truth_tbl +12 -0
- data/lib/logic_tools.rb +5 -0
- data/lib/logic_tools/logicparse.rb +91 -0
- data/lib/logic_tools/logicsimplify.rb +384 -0
- data/lib/logic_tools/logictree.rb +804 -0
- data/lib/logic_tools/simplify_bug.txt +3 -0
- data/lib/logic_tools/simplify_qm.rb +46 -0
- data/lib/logic_tools/std_conj.rb +43 -0
- data/lib/logic_tools/std_dij.rb +43 -0
- data/lib/logic_tools/truth_tbl.rb +47 -0
- data/lib/logic_tools/version.rb +3 -0
- data/logic_tools.gemspec +44 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0921177eea23a3eaec47a7c1f6f9a3885dad5759
|
4
|
+
data.tar.gz: c0a30c5a79a38e4113096c97398cdaf390a02b8b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 07164addc2bbf46d49af509e473e42daab575fa89a2494661d603a8b2f58389924b0b0f2753d0c09511e747d626636fb1da31a8f167b00461b360bb4a0a99288
|
7
|
+
data.tar.gz: 7e2de12c7322b4a8a67301839dc185e277f7984afebf7984e848da188ed5eacacc2274cae63d1a03b28a934395f6a484be1767523774ba2669f863dcd2c0cf14
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Lovic Gauthier
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# LogicTools
|
2
|
+
|
3
|
+
LogicTools is a set of command-line tools for processing logic expressions.
|
4
|
+
The tools include:
|
5
|
+
* simplify_qm: for simplifying a logic expression.
|
6
|
+
* std_conj: for computing the conjunctive normal form of a logic expression.
|
7
|
+
* std_dij: for computing the disjunctive normal form a of logic expression.
|
8
|
+
* truth_tbl: for generating the truth table of a logic expression.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'logic_tools'
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install logic_tools
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
LogicTools is a command line-based set of tool. Each tool is used as follows:
|
29
|
+
tool "logical expression"
|
30
|
+
|
31
|
+
The logical expression is an expression where:
|
32
|
+
* a logical variable is represented by a single alphabetical character (hence there is in total 56 possible variables);
|
33
|
+
* a logical OR is represented by a '+' character;
|
34
|
+
* a logical AND is represented by a '.' character (but it can be omitted);
|
35
|
+
* a logical NOT is represented by a '~' or a '!' character;
|
36
|
+
* opening and closing parenthesis are represented by, respectively,
|
37
|
+
* '(' and ')' characters.
|
38
|
+
|
39
|
+
Important notice:
|
40
|
+
* the priority among logical operators is as follows: NOT > AND > OR
|
41
|
+
* logical expressions must be put between quotes (the '"' character).
|
42
|
+
|
43
|
+
For instance the following are valid logical expression using the a,b and c variables:
|
44
|
+
"ab+ac"
|
45
|
+
"a.b.c"
|
46
|
+
"a+b+!c"
|
47
|
+
"a~(b+~c)"
|
48
|
+
|
49
|
+
Finally, here are a few examples of LogicTool usage:
|
50
|
+
* simplifying the expression a+ab:
|
51
|
+
simplify_qm "a+ab"
|
52
|
+
-> a
|
53
|
+
* compute the conjunctive normal form of the expression a+ab:
|
54
|
+
std_conj "a+ab"
|
55
|
+
-> ab+a~b
|
56
|
+
* compute the disjunctive normal form of the expression a+ab:
|
57
|
+
std_conj "a+ab"
|
58
|
+
-> (a+b)(a+~b)
|
59
|
+
* compute the truth table of the expression a+ab:
|
60
|
+
truth_tbl "a+ab"
|
61
|
+
-> a b
|
62
|
+
0 0 0
|
63
|
+
0 1 0
|
64
|
+
1 0 1
|
65
|
+
1 1 1
|
66
|
+
|
67
|
+
## Development
|
68
|
+
|
69
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
70
|
+
|
71
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
72
|
+
|
73
|
+
## Contributing
|
74
|
+
|
75
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/civol/logic_tools.
|
76
|
+
|
77
|
+
|
78
|
+
## License
|
79
|
+
|
80
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
81
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "logic_tools"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/exe/simplify_qm
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
######################################################################
|
3
|
+
## Script for launching the simplify_qm.rb program ##
|
4
|
+
######################################################################
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'rubygems'
|
8
|
+
gem 'logic_tools'
|
9
|
+
rescue LoadError
|
10
|
+
end
|
11
|
+
|
12
|
+
load "logic_tools/simplify_qm.rb"
|
data/exe/std_conj
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
######################################################################
|
3
|
+
## Script for launching the simplify.rb program ##
|
4
|
+
######################################################################
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'rubygems'
|
8
|
+
gem 'logic_tools'
|
9
|
+
rescue LoadError
|
10
|
+
end
|
11
|
+
|
12
|
+
load "logic_tools/std_conj.rb"
|
data/exe/std_dij
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
######################################################################
|
3
|
+
## Script for launching the simplify.rb program ##
|
4
|
+
######################################################################
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'rubygems'
|
8
|
+
gem 'logic_tools'
|
9
|
+
rescue LoadError
|
10
|
+
end
|
11
|
+
|
12
|
+
load "logic_tools/std_dij.rb"
|
data/exe/truth_tbl
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
######################################################################
|
3
|
+
## Script for launching the simplify.rb program ##
|
4
|
+
######################################################################
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'rubygems'
|
8
|
+
gem 'logic_tools'
|
9
|
+
rescue LoadError
|
10
|
+
end
|
11
|
+
|
12
|
+
load "logic_tools/truth_tbl.rb"
|
data/lib/logic_tools.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
##########################
|
2
|
+
# Truth table generator #
|
3
|
+
##########################
|
4
|
+
|
5
|
+
# For parsing the inputs
|
6
|
+
require 'parslet'
|
7
|
+
|
8
|
+
# For building logic tress
|
9
|
+
require "logic_tools/logictree.rb"
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
########################################################
|
15
|
+
# Parse a string and convert to a logic tree
|
16
|
+
|
17
|
+
|
18
|
+
module LogicTools
|
19
|
+
|
20
|
+
## The parser of logic expressions
|
21
|
+
class Parser < Parslet::Parser
|
22
|
+
|
23
|
+
# True / false
|
24
|
+
rule(:tru) { str("1") }
|
25
|
+
rule(:fal) { str("0") }
|
26
|
+
# Variable
|
27
|
+
rule(:var) { match('[A-Za-uw-z]') }
|
28
|
+
# And operator
|
29
|
+
rule(:andop) { str("&&") | match('[&\.\*^]') }
|
30
|
+
# Or operator
|
31
|
+
rule(:orop) { match('[+|v]') }
|
32
|
+
# Not operator
|
33
|
+
rule(:notop) { match('[~!]') }
|
34
|
+
|
35
|
+
# Grammar rules
|
36
|
+
root(:expr)
|
37
|
+
rule(:expr) { orexpr }
|
38
|
+
rule(:orexpr) { (andexpr >> ( orop >> andexpr ).repeat).as(:orexpr) }
|
39
|
+
rule(:andexpr) { (notexpr >> ( (andop >> notexpr) | notexpr ).repeat).as(:andexpr) }
|
40
|
+
rule(:notexpr) { ((notop.as(:notop)).repeat >> term).as(:notexpr) }
|
41
|
+
rule(:term) { tru.as(:tru) | fal.as(:fal) | var.as(:var) |
|
42
|
+
( str("(") >> expr >> str(")") ) }
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
## The logic tree generator from the syntax tree
|
47
|
+
class Transform < Parslet::Transform
|
48
|
+
|
49
|
+
# Terminal rules
|
50
|
+
rule(:tru => simple(:tru)) { NodeTrue.new() }
|
51
|
+
rule(:fal => simple(:fal)) { NodeFalse.new() }
|
52
|
+
rule(:var => simple(:var)) { NodeVar.new(var) }
|
53
|
+
rule(:notop => simple(:notop)) { "!" }
|
54
|
+
|
55
|
+
# Not rules
|
56
|
+
rule(:notexpr => simple(:expr)) { expr }
|
57
|
+
rule(:notexpr => sequence(:seq)) do
|
58
|
+
expr = seq.pop
|
59
|
+
if seq.size.even? then
|
60
|
+
expr
|
61
|
+
else
|
62
|
+
NodeNot.new(expr)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# And rules
|
67
|
+
rule(:andexpr => simple(:expr)) { expr }
|
68
|
+
rule(:andexpr => sequence(:seq)) do
|
69
|
+
NodeAnd.new(*seq)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Or rules
|
73
|
+
rule(:orexpr => simple(:expr)) { expr }
|
74
|
+
rule(:orexpr => sequence(:seq)) do
|
75
|
+
NodeOr.new(*seq)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
## The parser/gerator main fuction: converts a string to a logic tree
|
81
|
+
# Param:
|
82
|
+
# +str+:: the string to parse
|
83
|
+
# Return: the resulting logic tree
|
84
|
+
def string2logic(str)
|
85
|
+
# Remove the spaces
|
86
|
+
str = str.gsub(/\s+/, "")
|
87
|
+
# Parse the string
|
88
|
+
return Transform.new.apply(Parser.new.parse(str))
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,384 @@
|
|
1
|
+
###################################################################
|
2
|
+
# Logic tree classes extension for simplifying a logic expression #
|
3
|
+
# using the Quine-Mc Cluskey method #
|
4
|
+
###################################################################
|
5
|
+
|
6
|
+
|
7
|
+
require 'set'
|
8
|
+
|
9
|
+
|
10
|
+
module LogicTools
|
11
|
+
|
12
|
+
|
13
|
+
## Converts an array of variable to bit vector according to their value
|
14
|
+
# @param vars the array of variables to convert
|
15
|
+
def vars2int(vars)
|
16
|
+
res = ""
|
17
|
+
vars.each_with_index do |var,i|
|
18
|
+
res[i] = var.value ? "1" : "0"
|
19
|
+
end
|
20
|
+
res
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
# Class describing an implicant
|
28
|
+
class Implicant
|
29
|
+
include Enumerable
|
30
|
+
attr_reader :mask, # The positions of the x
|
31
|
+
:bits, # The bit vector of the implicant
|
32
|
+
:count, # The number of 1 of the implicant
|
33
|
+
:covers, # The bit values covered by the implicant
|
34
|
+
:prime # Tell if the implicant is prime or not
|
35
|
+
attr_accessor :var # The variable associated with the implicant
|
36
|
+
# Do not interfer at all with the class, so
|
37
|
+
# public and fully accessible
|
38
|
+
protected
|
39
|
+
attr_writer :covers
|
40
|
+
public
|
41
|
+
|
42
|
+
## Create an implicant
|
43
|
+
# @param base if Implicant: copy constructor <br>
|
44
|
+
# otherwise: creat a new implicant from a bit string
|
45
|
+
def initialize(base)
|
46
|
+
if base.is_a?(Implicant)
|
47
|
+
@covers = base.covers.dup
|
48
|
+
@bits = base.bits.dup
|
49
|
+
@mask = base.mask.dup
|
50
|
+
@count = base.count
|
51
|
+
else
|
52
|
+
@bits = base.to_s
|
53
|
+
unless @bits.match(/^[01]*$/)
|
54
|
+
raise "Invalid bit string for an initial implicant: " + @bits
|
55
|
+
end
|
56
|
+
@mask = " " * @bits.size
|
57
|
+
@count = @bits.count("1")
|
58
|
+
@covers = [ @bits ]
|
59
|
+
end
|
60
|
+
@prime = true # By default assumed prime
|
61
|
+
end
|
62
|
+
|
63
|
+
## Convert to a string
|
64
|
+
def to_s
|
65
|
+
@bits
|
66
|
+
end
|
67
|
+
|
68
|
+
## inspect
|
69
|
+
def inspect
|
70
|
+
@bits.dup
|
71
|
+
end
|
72
|
+
|
73
|
+
## Set the prime status
|
74
|
+
# @param st the new status (true or false)
|
75
|
+
def prime=(st)
|
76
|
+
@prime = st ? true : false
|
77
|
+
end
|
78
|
+
|
79
|
+
## Iterate overs the bits of the implicant
|
80
|
+
def each(&blk)
|
81
|
+
@bits.each_char(&blk)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Compare implicants
|
85
|
+
# @param imp the implicant (or simply bit string) to compare with
|
86
|
+
def ==(imp)
|
87
|
+
@bits == imp.to_s
|
88
|
+
end
|
89
|
+
def <=>(imp)
|
90
|
+
@bits <=> imp.to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
# duplicates the implicant
|
94
|
+
def dup
|
95
|
+
Implicant.new(self)
|
96
|
+
end
|
97
|
+
|
98
|
+
## Get a bit by index
|
99
|
+
# @param i the index in the bit string of the implicant
|
100
|
+
def [](i)
|
101
|
+
@bits[i]
|
102
|
+
end
|
103
|
+
|
104
|
+
## Set a bit by index
|
105
|
+
# @param i the index in the bit string of the implicant
|
106
|
+
# @param b the bit to set
|
107
|
+
def []=(i,b)
|
108
|
+
raise "Invalid bit value: #{b}" unless ["0","1","x"].include?(b)
|
109
|
+
return if @bits[i] == b # Already set
|
110
|
+
# Update count and mask
|
111
|
+
@count -= 1 if @bits[i] == "1" # One 1 less
|
112
|
+
@count += 1 if b == "1" # One 1 more
|
113
|
+
@mask[i] = " " if @bits[i] == "x" # One x less
|
114
|
+
@mask[i] = "x" if b == "x" # One x more
|
115
|
+
# Update the bit string
|
116
|
+
@bits[i] = b
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
## Merge with another implicant
|
121
|
+
# @param imp the implicant to merge with
|
122
|
+
# @return the resulting implicant
|
123
|
+
def merge(imp)
|
124
|
+
# Has imp the same mask?
|
125
|
+
return nil unless imp.mask == @mask
|
126
|
+
# First look for a 1-0 or 0-1 difference
|
127
|
+
found = nil
|
128
|
+
@bits.each_char.with_index do |b0,i|
|
129
|
+
b1 = imp.bits[i]
|
130
|
+
# Bits are different
|
131
|
+
if (b0 != b1) then
|
132
|
+
# Stop if there where already a difference
|
133
|
+
if (found)
|
134
|
+
found = nil
|
135
|
+
break
|
136
|
+
end
|
137
|
+
# A 0-1 or a 1-0 difference is found
|
138
|
+
found = i
|
139
|
+
end
|
140
|
+
end
|
141
|
+
# Can merge at bit found
|
142
|
+
if found then
|
143
|
+
# print "merge!\n"
|
144
|
+
# Duplicate current implicant
|
145
|
+
merged = self.dup
|
146
|
+
# And update its x
|
147
|
+
merged[found] = "x"
|
148
|
+
# Finally update its covers
|
149
|
+
merged.covers = @covers | imp.covers
|
150
|
+
return merged
|
151
|
+
end
|
152
|
+
# No merge
|
153
|
+
return nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Class describing a group of implicants with only singletons, sortable
|
158
|
+
# by number of ones
|
159
|
+
class SameXImplicants
|
160
|
+
include Enumerable
|
161
|
+
|
162
|
+
## Default constructor
|
163
|
+
def initialize
|
164
|
+
@implicants = []
|
165
|
+
@singletons = Set.new # Set used for ensuring each implicant is
|
166
|
+
# present only once in the group
|
167
|
+
end
|
168
|
+
|
169
|
+
## Ge the size of the group
|
170
|
+
def size
|
171
|
+
@implicants.size
|
172
|
+
end
|
173
|
+
|
174
|
+
## Iterate of the implicants
|
175
|
+
def each(&blk)
|
176
|
+
@implicants.each(&blk)
|
177
|
+
end
|
178
|
+
|
179
|
+
## Access by index
|
180
|
+
# @param i the index
|
181
|
+
def [](i)
|
182
|
+
@implicants[i]
|
183
|
+
end
|
184
|
+
|
185
|
+
## Add an implicant
|
186
|
+
# @param imp the implicant to add
|
187
|
+
def add(imp)
|
188
|
+
return if @singletons.include?(imp.bits) # Implicant already present
|
189
|
+
@implicants << imp
|
190
|
+
@singletons.add(imp.bits.dup)
|
191
|
+
end
|
192
|
+
alias :<< :add
|
193
|
+
|
194
|
+
# Sort the implicants by number of ones
|
195
|
+
def sort!
|
196
|
+
@implicants.sort_by! {|imp| imp.count }
|
197
|
+
end
|
198
|
+
|
199
|
+
# Convert to a string
|
200
|
+
def to_s
|
201
|
+
@implicants.to_s
|
202
|
+
end
|
203
|
+
def inspect
|
204
|
+
to_s
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
## Class describing a pseudo variable associated to an implicant
|
209
|
+
# Used for the Petrick's method
|
210
|
+
class VarImp < Variable
|
211
|
+
@@base = 0 # The index of the VarImp for building the variable names
|
212
|
+
|
213
|
+
attr_reader :implicant
|
214
|
+
|
215
|
+
## Create the variable
|
216
|
+
# @param imp the implicant to create the variable from
|
217
|
+
def initialize(imp)
|
218
|
+
# Create the name of the variable
|
219
|
+
name = nil
|
220
|
+
begin
|
221
|
+
name = "P" + @@base.to_s
|
222
|
+
@@base += 1
|
223
|
+
end while Variable.exists?(name)
|
224
|
+
# Create the variable
|
225
|
+
super(name)
|
226
|
+
# Associate it with the implicant
|
227
|
+
@implicant = imp
|
228
|
+
imp.var = self
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
|
234
|
+
# Enhance the Node class with expression simplifying
|
235
|
+
class Node
|
236
|
+
|
237
|
+
## Generates an equivalent but simplified representation of the
|
238
|
+
# function.<br>
|
239
|
+
# Uses the Quine-Mc Cluskey method
|
240
|
+
def simplify
|
241
|
+
# Step 1: get the generators
|
242
|
+
|
243
|
+
# Gather the minterms which set the function to 1 encoded as
|
244
|
+
# bitstrings
|
245
|
+
minterms = []
|
246
|
+
each_minterm do |vars|
|
247
|
+
minterms << vars2int(vars)
|
248
|
+
end
|
249
|
+
|
250
|
+
# print "minterms = #{minterms}\n"
|
251
|
+
|
252
|
+
# Create the implicant table
|
253
|
+
implicants = Hash.new {|h,k| h[k] = SameXImplicants.new }
|
254
|
+
|
255
|
+
# Convert the minterms to implicants without x
|
256
|
+
minterms.each do |term|
|
257
|
+
imp = Implicant.new(term)
|
258
|
+
implicants[imp.mask] << imp
|
259
|
+
end
|
260
|
+
|
261
|
+
# print "implicants = #{implicants}\n"
|
262
|
+
|
263
|
+
# Group the adjacent implicants to obtain the generators
|
264
|
+
size = 0
|
265
|
+
generators = []
|
266
|
+
# The main iterator
|
267
|
+
has_merged = nil
|
268
|
+
begin
|
269
|
+
has_merged = false
|
270
|
+
mergeds = Hash.new { |h,k| h[k] = SameXImplicants.new }
|
271
|
+
implicants.each_value do |group|
|
272
|
+
group.sort! # Sort by number of one
|
273
|
+
size = group.size
|
274
|
+
# print "size = #{size}\n"
|
275
|
+
group.each_with_index do |imp0,i0|
|
276
|
+
# print "imp0 = #{imp0}, i0=#{i0}\n"
|
277
|
+
((i0+1)..(size-1)).each do |i1|
|
278
|
+
# Get the next implicant
|
279
|
+
imp1 = group[i1]
|
280
|
+
# print "imp1 = #{imp1}, i1=#{i1}\n"
|
281
|
+
# No need to look further if the number of 1 of imp1
|
282
|
+
# is more than one larger than imp0's
|
283
|
+
break if imp1.count > imp0.count+1
|
284
|
+
# Try to merge
|
285
|
+
mrg = imp0.merge(imp1)
|
286
|
+
# print "mrg = #{mrg}\n"
|
287
|
+
# Can merge
|
288
|
+
if mrg then
|
289
|
+
mergeds[mrg.mask] << mrg
|
290
|
+
# Indicate than a merged happend
|
291
|
+
has_merged = true
|
292
|
+
# Mark the initial generators as not prime
|
293
|
+
imp0.prime = imp1.prime = false
|
294
|
+
end
|
295
|
+
end
|
296
|
+
# Is the term prime?
|
297
|
+
if imp0.prime then
|
298
|
+
# print "imp0 is prime\n"
|
299
|
+
# Yes add it to the generators
|
300
|
+
generators << imp0
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
# print "mergeds=#{mergeds}\n"
|
305
|
+
# Prepare the next iteration
|
306
|
+
implicants = mergeds
|
307
|
+
end while has_merged
|
308
|
+
|
309
|
+
# print "generators with covers:\n"
|
310
|
+
# generators.each {|gen| print gen,": ", gen.covers,"\n" }
|
311
|
+
|
312
|
+
# Step 2: remove the redundancies
|
313
|
+
|
314
|
+
# Select the generators using Petrick's method
|
315
|
+
# For that purpose treat the generators as variables
|
316
|
+
variables = generators.map {|gen| VarImp.new(gen) }
|
317
|
+
|
318
|
+
# Group the variables by cover
|
319
|
+
cover2gen = Hash.new { |h,k| h[k] = [] }
|
320
|
+
variables.each do |var|
|
321
|
+
# print "var=#{var}, implicant=#{var.implicant}, covers=#{var.implicant.covers}\n"
|
322
|
+
var.implicant.covers.each { |cov| cover2gen[cov] << var }
|
323
|
+
end
|
324
|
+
# Convert this hierachical table to a product of sum
|
325
|
+
# First the sum terms
|
326
|
+
sums = cover2gen.each_value.map do |vars|
|
327
|
+
# print "vars=#{vars}\n"
|
328
|
+
if vars.size > 1 then
|
329
|
+
NodeOr.new(*vars.map {|var| NodeVar.new(var) })
|
330
|
+
else
|
331
|
+
NodeVar.new(vars[0])
|
332
|
+
end
|
333
|
+
end
|
334
|
+
# Then the product
|
335
|
+
# expr = NodeAnd.new(*sums).uniq
|
336
|
+
if sums.size > 1 then
|
337
|
+
expr = NodeAnd.new(*sums).reduce
|
338
|
+
else
|
339
|
+
expr = sums[0]
|
340
|
+
end
|
341
|
+
# Convert it to a sum of product
|
342
|
+
# print "expr = #{expr.to_s}\n"
|
343
|
+
expr = expr.to_sum_product(true)
|
344
|
+
# print "Now expr = #{expr.to_s} (#{expr.class})\n"
|
345
|
+
# Select the smallest term (if several)
|
346
|
+
if (expr.op == :or) then
|
347
|
+
smallest = expr.min_by do |term|
|
348
|
+
term.op == :and ? term.size : 1
|
349
|
+
end
|
350
|
+
else
|
351
|
+
smallest = expr
|
352
|
+
end
|
353
|
+
# The corresponding implicants are the selected generators
|
354
|
+
if smallest.op == :and then
|
355
|
+
selected = smallest.map {|term| term.variable.implicant }
|
356
|
+
else
|
357
|
+
selected = [ smallest.variable.implicant ]
|
358
|
+
end
|
359
|
+
|
360
|
+
# Sort by variable order
|
361
|
+
selected.sort_by! { |imp| imp.bits }
|
362
|
+
|
363
|
+
# print "Selected prime implicants are: #{selected}\n"
|
364
|
+
# Generate the resulting tree
|
365
|
+
variables = self.getVariables()
|
366
|
+
# First generate the prime implicants trees
|
367
|
+
selected.map! do |prime|
|
368
|
+
# Generate the litterals
|
369
|
+
litterals = []
|
370
|
+
prime.each.with_index do |c,i|
|
371
|
+
case c
|
372
|
+
when "0" then
|
373
|
+
litterals << NodeNot.new(NodeVar.new(variables[i]))
|
374
|
+
when "1" then litterals << NodeVar.new(variables[i])
|
375
|
+
end
|
376
|
+
end
|
377
|
+
# Generate the tree
|
378
|
+
NodeAnd.new(*litterals)
|
379
|
+
end
|
380
|
+
# Then generate the final sum tree
|
381
|
+
return NodeOr.new(*selected)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|