logic_tools 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.0
5
+ before_install: gem install bundler -v 1.13.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in logic_tools.gemspec
4
+ gemspec
@@ -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.
@@ -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
+
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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"
@@ -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"
@@ -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"
@@ -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"
@@ -0,0 +1,5 @@
1
+ require "logic_tools/version"
2
+
3
+ module LogicTools
4
+ # Your code goes here...
5
+ end
@@ -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