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