prop_logic 0.1.0
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 +10 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +140 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/prop_logic/and_term.rb +77 -0
- data/lib/prop_logic/brute_force_sat_solver.rb +20 -0
- data/lib/prop_logic/constants.rb +45 -0
- data/lib/prop_logic/functions.rb +26 -0
- data/lib/prop_logic/not_term.rb +68 -0
- data/lib/prop_logic/or_term.rb +75 -0
- data/lib/prop_logic/sat_solver.rb +13 -0
- data/lib/prop_logic/term.rb +149 -0
- data/lib/prop_logic/then_term.rb +25 -0
- data/lib/prop_logic/variable.rb +49 -0
- data/lib/prop_logic/version.rb +3 -0
- data/lib/prop_logic.rb +11 -0
- data/prop_logic.gemspec +31 -0
- metadata +122 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 37cd959dffbbeeb64bde6b241794bec73c4415a6
|
4
|
+
data.tar.gz: 3a836ab88dd8e721b0e10ed4f1d0d7ffdb93fdd1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 50f268b87efd5333fcbffbe2e72a48e13e87c25ed79e12d570f37043e692dae4d5eb02d9e7291e1262ee26e18509e0274dc018a801fb4d62ad60b4f2c214e37f
|
7
|
+
data.tar.gz: 1c74a4734264ed6395a0eba5806e03902c138b65f2ddc45551f01e9fd2ad36aa2f6e1ead12006749e4af542c064c015339f40853cf8ac6eefae0b4d46b40660f
|
data/.gitignore
ADDED
data/.rspec
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) 2015 Jkr2255
|
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,140 @@
|
|
1
|
+
# PropLogic
|
2
|
+
|
3
|
+
[](https://travis-ci.org/jkr2255/prop_logic)
|
4
|
+
|
5
|
+
PropLogic implements propositional logic in Ruby, usable like normal variables.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'prop_logic'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install prop_logic
|
22
|
+
|
23
|
+
### Requirements
|
24
|
+
Using with CRuby, Version >= 2.0.0 is required. Doesn't work stably on 1.9.x due to unreliable behaviors on weak reference.
|
25
|
+
|
26
|
+
In JRuby and Rubinus it should work.
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
### Overview
|
30
|
+
First, variables can be declared using `PropLogic.new_variable`. Next, it can be calculated using normal Ruby operators
|
31
|
+
such as `&`, `|`, `~`, and some methods. Finally, you can test satisfiability of these expressions.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
# declartion
|
35
|
+
a = PropLogic.new_variable 'a'
|
36
|
+
b = PropLogic.new_variable 'b'
|
37
|
+
c = PropLogic.new_variable 'c'
|
38
|
+
|
39
|
+
# calculation
|
40
|
+
expr1 = (a & b) | c
|
41
|
+
expr2 = ~(a.then(b))
|
42
|
+
expr3 = expr1 | expr2
|
43
|
+
|
44
|
+
# conversion
|
45
|
+
nnf = expr3.to_nnf
|
46
|
+
cnf = expr3.to_cnf
|
47
|
+
sat = expr3.sat?
|
48
|
+
|
49
|
+
# comparement
|
50
|
+
diff = (~a | ~b).equiv?(~(a & b)) # true
|
51
|
+
|
52
|
+
# assignment
|
53
|
+
(a & b).assign_true(a).assign_false(b).reduce # PropLogic::False
|
54
|
+
```
|
55
|
+
|
56
|
+
## Restriction
|
57
|
+
SAT solver bundled with this gem is brute-force solver (intended only for testing), so it is inappropriate to use for
|
58
|
+
real-scale problems.
|
59
|
+
|
60
|
+
## References
|
61
|
+
`PropLogic::Term` is immutable, meaning that all calculations return new Terms.
|
62
|
+
### `PropLogic::Term` instance methods
|
63
|
+
#### `#and(*others)`, `#&(*others)`
|
64
|
+
calculate `self & others[0] & others[1] & ...`.
|
65
|
+
#### `#or(*others)`, `#|(*others)`
|
66
|
+
calculate `self | others[0] | others[1] & ...`.
|
67
|
+
|
68
|
+
#### Warning for reducing with `&` / `|`
|
69
|
+
Because of immutability and internal system of and/or terms, `many_terms.reduce(&:and)` is exteremely slow.
|
70
|
+
Use `PropLogic.all_and`/`PropLogic.all_or` or `one_term.and(*others)` to avoid this pitfall.
|
71
|
+
|
72
|
+
#### `#not()`,`#~()`, `#-@()`
|
73
|
+
calculate `Not(self)`. `#!` is not present because it confuses Ruby behavior. (if present, `!term` is always *truthy*)
|
74
|
+
|
75
|
+
#### `#then(other), #>>(other)`
|
76
|
+
caslculate `If self then other`.
|
77
|
+
|
78
|
+
#### NNF
|
79
|
+
NNF doesn't contain following terms:
|
80
|
+
- If-then
|
81
|
+
- Negation of And/Or
|
82
|
+
- Double negation
|
83
|
+
|
84
|
+
`#nnf?` checks if the term is NNF, and `#to_nnf` returns term converted to NNF.
|
85
|
+
|
86
|
+
#### Reduction
|
87
|
+
Term is regarded as reduced if it is NNF and it contains no constants (`PropLogic::True`/`PropLogic::False`).
|
88
|
+
|
89
|
+
`#reduced?` checks if the term is reduced, and `#reduce` returns reduced term.
|
90
|
+
|
91
|
+
#### Reduction
|
92
|
+
CNF is one of these:
|
93
|
+
1. Variable and its negation
|
94
|
+
2. Logical sum of multiple 1
|
95
|
+
3. Logical product of multiple (1 or 2)
|
96
|
+
|
97
|
+
`#cnf?` checks if the term is CNF, and `#to_cnf` returns terms converted to CNF (may use extra variables).
|
98
|
+
|
99
|
+
#### SAT judgement
|
100
|
+
`#sat?` returns:
|
101
|
+
- boolean `false` if unsatisfiable
|
102
|
+
- `nil` if satisfiability is undetermined
|
103
|
+
- One term satisfying original term if satisfiable
|
104
|
+
|
105
|
+
`#unsat?` returns `true` if unsatisfiable, `false` otherwise.
|
106
|
+
|
107
|
+
### `PropLogic` module methods
|
108
|
+
#### `PropLogic.new_variable(name = nil)`
|
109
|
+
declare new variable with name `name`. if `name` is not supplied, unique name is set.
|
110
|
+
|
111
|
+
Notice that even if the same `name` as before specified, it returns *different* variable.
|
112
|
+
|
113
|
+
#### `PropLogic.all_and(*terms)`/`PropLogic.all_or(*terms)`
|
114
|
+
calculate all and/or of `terms`. Use this when and/or calculation for many variables.
|
115
|
+
|
116
|
+
#### `PropLogic.sat_solver`/ PropLogic.sat_solver=`
|
117
|
+
set/get SAT solver object. It shoud have `#call(term)` method and return value is described in `Term#sat?`.
|
118
|
+
|
119
|
+
### Information
|
120
|
+
`PropLogic::Term` has some subclasses, but these classes are subject to change.
|
121
|
+
Using subclasses of `PropLogic::Term` directly is not recommended.
|
122
|
+
|
123
|
+
## Development
|
124
|
+
|
125
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
126
|
+
|
127
|
+
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` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
128
|
+
|
129
|
+
## Contributing
|
130
|
+
|
131
|
+
1. Fork it ( https://github.com/[my-github-username]/prop_logic/fork )
|
132
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
133
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
134
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
135
|
+
5. Create a new Pull Request
|
136
|
+
|
137
|
+
## ToDo
|
138
|
+
|
139
|
+
- Introduce special blocks to build terms inside
|
140
|
+
- Add nontrivial SAT solver for practical usage
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "prop_logic"
|
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
@@ -0,0 +1,77 @@
|
|
1
|
+
module PropLogic
|
2
|
+
class AndTerm < Term
|
3
|
+
def initialize(*terms)
|
4
|
+
@terms = terms.map{|t| t.is_a?(AndTerm) ? t.terms : t}.flatten.freeze
|
5
|
+
@is_nnf = @terms.all?(&:nnf?)
|
6
|
+
# term with negative terms are no longer terated as reduced
|
7
|
+
@is_reduced = @is_nnf && @terms.all? do |term|
|
8
|
+
if term.is_a?(Constant) || !term.reduced?
|
9
|
+
false
|
10
|
+
elsif !(term.is_a?(NotTerm))
|
11
|
+
true
|
12
|
+
else
|
13
|
+
# NotTerm
|
14
|
+
term.terms[0].is_a?(Variable)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
return unless @is_reduced
|
18
|
+
# check contradicted variables (mark as unreduced)
|
19
|
+
# Negated terms (except variables) doesn't come here
|
20
|
+
not_terms = @terms.select{ |t| t.is_a?(NotTerm) }
|
21
|
+
negated_variales = not_terms.map{|t| t.terms[0]}
|
22
|
+
@is_reduced = false unless (negated_variales & @terms).empty?
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s(in_term = false)
|
26
|
+
str = @terms.map(&:to_s_in_term).join(' & ')
|
27
|
+
in_term ? "( #{str} )" : str
|
28
|
+
end
|
29
|
+
|
30
|
+
def nnf?
|
31
|
+
@is_nnf
|
32
|
+
end
|
33
|
+
|
34
|
+
def reduced?
|
35
|
+
@is_reduced
|
36
|
+
end
|
37
|
+
|
38
|
+
def reduce
|
39
|
+
return self if reduced?
|
40
|
+
reduced_terms = @terms.map(&:reduce)
|
41
|
+
reduced_terms.reject!{|term| term.equal?(True)}
|
42
|
+
return True if reduced_terms.empty?
|
43
|
+
if reduced_terms.any?{|term| term.equal?(False)}
|
44
|
+
False
|
45
|
+
elsif reduced_terms.length == 1
|
46
|
+
reduced_terms[0]
|
47
|
+
else
|
48
|
+
# detect contradicted terms
|
49
|
+
not_terms = reduced_terms.select{|term| term.is_a?(NotTerm)}
|
50
|
+
negated_terms = not_terms.map{|term| term.terms[0]}
|
51
|
+
return False unless (negated_terms & reduced_terms).empty?
|
52
|
+
Term.get self.class, *reduced_terms
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def cnf?
|
57
|
+
return false unless reduced?
|
58
|
+
@terms.all?(&:cnf?)
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_cnf
|
62
|
+
return super unless reduced?
|
63
|
+
return self if cnf?
|
64
|
+
pool = []
|
65
|
+
without_pools = PropLogic.all_and(*@terms.map{|t| t.tseitin(pool)})
|
66
|
+
PropLogic.all_and(without_pools, *pool)
|
67
|
+
end
|
68
|
+
|
69
|
+
def tseitin(pool)
|
70
|
+
val = Variable.new
|
71
|
+
terms = @terms.map{|t| t.cnf? ? t : t.tseitin(pool)}
|
72
|
+
pool.concat terms.map{|t| ~val | t }
|
73
|
+
val
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module PropLogic
|
2
|
+
module BruteForceSatSolver
|
3
|
+
class << self
|
4
|
+
def call(term)
|
5
|
+
# obvious value
|
6
|
+
return True if term == True
|
7
|
+
variables = term.variables
|
8
|
+
PropLogic.all_combination(variables) do |trues|
|
9
|
+
falses = variables - trues
|
10
|
+
next unless term.assign(trues, falses).reduce == True
|
11
|
+
# SAT
|
12
|
+
negated_falses = falses.map(&:not)
|
13
|
+
return PropLogic.all_and(*trues, *negated_falses)
|
14
|
+
end
|
15
|
+
# UNSAT
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module PropLogic
|
4
|
+
class Constant < Variable
|
5
|
+
def variables
|
6
|
+
[]
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_cnf
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
class TrueConstant < Constant
|
16
|
+
include Singleton
|
17
|
+
def to_s(*)
|
18
|
+
'true'
|
19
|
+
end
|
20
|
+
|
21
|
+
def assign(trues, falses, variables = nil)
|
22
|
+
if falses.include?(self)
|
23
|
+
raise ArgumentError, 'Contradicted assignment'
|
24
|
+
end
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
class FalseConstant < Constant
|
31
|
+
include Singleton
|
32
|
+
def to_s(*)
|
33
|
+
'false'
|
34
|
+
end
|
35
|
+
def assign(trues, falses, variables = nil)
|
36
|
+
if trues.include?(self)
|
37
|
+
raise ArgumentError, 'Contradicted assignment'
|
38
|
+
end
|
39
|
+
self
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
True = TrueConstant.instance.freeze
|
44
|
+
False = FalseConstant.instance.freeze
|
45
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module PropLogic
|
2
|
+
module Functions
|
3
|
+
def all_or(*args)
|
4
|
+
Term.get OrTerm, *args
|
5
|
+
end
|
6
|
+
|
7
|
+
def all_and(*args)
|
8
|
+
Term.get AndTerm, *args
|
9
|
+
end
|
10
|
+
|
11
|
+
def new_variable(*args)
|
12
|
+
Variable.new *args
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
extend Functions
|
17
|
+
|
18
|
+
def all_combination(arr)
|
19
|
+
0.upto(arr.length) do |num|
|
20
|
+
arr.combination(num){|c| yield c}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module_function :all_combination
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module PropLogic
|
2
|
+
class NotTerm < Term
|
3
|
+
def initialize(term)
|
4
|
+
@terms = [term].freeze
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s(*)
|
8
|
+
"~" + @terms[0].to_s(true)
|
9
|
+
end
|
10
|
+
|
11
|
+
def nnf?
|
12
|
+
@terms[0].is_a?(Variable)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_nnf
|
16
|
+
term = @terms[0]
|
17
|
+
case term
|
18
|
+
when NotTerm
|
19
|
+
term.terms[0].to_nnf
|
20
|
+
when Variable
|
21
|
+
self
|
22
|
+
when ThenTerm
|
23
|
+
(~(term.to_nnf)).to_nnf
|
24
|
+
when AndTerm
|
25
|
+
PropLogic.all_or(*term.terms.map{|t| (~t).to_nnf})
|
26
|
+
when OrTerm
|
27
|
+
PropLogic.all_and(*term.terms.map{|t| (~t).to_nnf})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def reduced?
|
32
|
+
nnf? && ! (@terms[0].is_a?(Constant))
|
33
|
+
end
|
34
|
+
|
35
|
+
def reduce
|
36
|
+
return self if reduced?
|
37
|
+
reduced_term = @terms[0].reduce
|
38
|
+
case reduced_term
|
39
|
+
when TrueConstant
|
40
|
+
False
|
41
|
+
when FalseConstant
|
42
|
+
True
|
43
|
+
else
|
44
|
+
(~reduced_term).to_nnf
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_cnf
|
49
|
+
if reduced?
|
50
|
+
self
|
51
|
+
else
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def tseitin(pool)
|
57
|
+
if nnf?
|
58
|
+
self
|
59
|
+
elsif @terms[0].is_a?(NotTerm) && @terms[0].terms[0].is_a(Variable)
|
60
|
+
@terms[0].terms[0]
|
61
|
+
else
|
62
|
+
raise 'Non-NNF terms cannot be converted to Tseitin form.' + self.to_s
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
alias_method :cnf?, :reduced?
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module PropLogic
|
2
|
+
class OrTerm < Term
|
3
|
+
def initialize(*terms)
|
4
|
+
@terms = terms.map{|t| t.is_a?(OrTerm) ? t.terms : t}.flatten.freeze
|
5
|
+
@is_nnf = @terms.all?(&:nnf?)
|
6
|
+
@is_reduced = @is_nnf && @terms.all? do |term|
|
7
|
+
if term.is_a?(Constant) || !(term.reduced?)
|
8
|
+
false
|
9
|
+
elsif !(term.is_a?(NotTerm))
|
10
|
+
true
|
11
|
+
else
|
12
|
+
# NotTerm
|
13
|
+
term.terms[0].is_a?(Variable)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
return unless @is_reduced
|
17
|
+
# check obvious variables (mark as unreduced)
|
18
|
+
# Negated terms (except variables) doesn't come here
|
19
|
+
not_terms = @terms.select{ |t| t.is_a?(NotTerm) }
|
20
|
+
negated_variales = not_terms.map{|t| t.terms[0]}
|
21
|
+
@is_reduced = false unless (negated_variales & @terms).empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s(in_term = false)
|
25
|
+
str = @terms.map(&:to_s_in_term).join(' | ')
|
26
|
+
in_term ? "( #{str} )" : str
|
27
|
+
end
|
28
|
+
|
29
|
+
def nnf?
|
30
|
+
@is_nnf
|
31
|
+
end
|
32
|
+
|
33
|
+
def reduced?
|
34
|
+
@is_reduced
|
35
|
+
end
|
36
|
+
|
37
|
+
def reduce
|
38
|
+
return self if reduced?
|
39
|
+
reduced_terms = @terms.map(&:reduce)
|
40
|
+
reduced_terms.reject!{|term| term.equal?(False)}
|
41
|
+
return False if reduced_terms.empty?
|
42
|
+
if reduced_terms.any?{|term| term.equal?(True)}
|
43
|
+
True
|
44
|
+
elsif reduced_terms.length == 1
|
45
|
+
reduced_terms[0]
|
46
|
+
else
|
47
|
+
not_terms = reduced_terms.select{|term| term.is_a?(NotTerm)}
|
48
|
+
negated_terms = not_terms.map{|term| term.terms[0]}
|
49
|
+
return True unless (negated_terms & reduced_terms).empty?
|
50
|
+
Term.get self.class, *reduced_terms
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def cnf?
|
55
|
+
return false unless reduced?
|
56
|
+
! @terms.any?{ |term| term.is_a?(AndTerm) }
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_cnf
|
60
|
+
return super unless reduced?
|
61
|
+
return self if cnf?
|
62
|
+
pool = []
|
63
|
+
without_pools = tseitin(pool)
|
64
|
+
PropLogic.all_and(without_pools, *pool)
|
65
|
+
end
|
66
|
+
|
67
|
+
def tseitin(pool)
|
68
|
+
val = Variable.new
|
69
|
+
terms = @terms.map{|t| t.tseitin(pool)}
|
70
|
+
pool << (~val | PropLogic.all_or(*terms))
|
71
|
+
val
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'ref'
|
2
|
+
|
3
|
+
module PropLogic
|
4
|
+
class Term
|
5
|
+
def initialize
|
6
|
+
raise NotImplementedError, 'Term cannot be initialized'
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize_copy(*)
|
10
|
+
raise TypeError, 'Term cannot be duplicated (immutable, not necessary)'
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
protected :new
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :terms
|
18
|
+
|
19
|
+
def and(*others)
|
20
|
+
others.unshift self
|
21
|
+
Term.get AndTerm, *others
|
22
|
+
end
|
23
|
+
|
24
|
+
alias_method :&, :and
|
25
|
+
|
26
|
+
def or(*others)
|
27
|
+
others.unshift self
|
28
|
+
Term.get OrTerm, *others
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :|, :or
|
32
|
+
|
33
|
+
def not
|
34
|
+
Term.get NotTerm, self
|
35
|
+
end
|
36
|
+
|
37
|
+
alias_method :~, :not
|
38
|
+
alias_method :-@, :not
|
39
|
+
|
40
|
+
def then(other)
|
41
|
+
Term.get ThenTerm, self, other
|
42
|
+
end
|
43
|
+
|
44
|
+
alias_method :>>, :then
|
45
|
+
|
46
|
+
def to_s_in_term
|
47
|
+
to_s true
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_nnf
|
51
|
+
if nnf?
|
52
|
+
self
|
53
|
+
else
|
54
|
+
Term.get self.class, *@terms.map(&:to_nnf)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def nnf?
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
def reduce
|
63
|
+
if reduced?
|
64
|
+
self
|
65
|
+
else
|
66
|
+
Term.get self.class, *@terms.map(&:reduce)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def reduced?
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_cnf
|
75
|
+
reduce.to_cnf
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.validate_terms(*terms)
|
79
|
+
terms.map do |term|
|
80
|
+
case term
|
81
|
+
when TrueClass
|
82
|
+
True
|
83
|
+
when FalseClass
|
84
|
+
False
|
85
|
+
when Term
|
86
|
+
term
|
87
|
+
else
|
88
|
+
raise TypeError, "#{term.class} cannot be treated as term"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.get(klass, *terms)
|
94
|
+
@table ||= Ref::WeakValueMap.new
|
95
|
+
terms = validate_terms(*terms)
|
96
|
+
if klass == AndTerm || klass == OrTerm
|
97
|
+
terms = terms.map{|t| t.is_a?(klass) ? t.terms : t}.flatten
|
98
|
+
end
|
99
|
+
key = klass.name + terms.map(&:object_id).join(',')
|
100
|
+
return @table[key] if @table[key]
|
101
|
+
ret = klass.__send__ :new, *terms
|
102
|
+
@table[key] = ret
|
103
|
+
ret.freeze
|
104
|
+
end
|
105
|
+
|
106
|
+
def cnf?
|
107
|
+
false
|
108
|
+
end
|
109
|
+
|
110
|
+
def variables
|
111
|
+
@terms.map(&:variables).flatten.uniq
|
112
|
+
end
|
113
|
+
|
114
|
+
def assign(trues, falses, variables = nil)
|
115
|
+
# contradicted assignment
|
116
|
+
raise ArgumentError, 'Contradicted assignment' unless (trues & falses).empty?
|
117
|
+
variables ||= trues | falses
|
118
|
+
assigned_terms = terms.map do |term|
|
119
|
+
if (term.variables & variables).empty?
|
120
|
+
term
|
121
|
+
else
|
122
|
+
term.assign(trues, falses, variables)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
Term.get self.class, *assigned_terms
|
126
|
+
end
|
127
|
+
|
128
|
+
def assign_true(*variables)
|
129
|
+
assign variables, []
|
130
|
+
end
|
131
|
+
|
132
|
+
def assign_false(*variables)
|
133
|
+
assign [], variables
|
134
|
+
end
|
135
|
+
|
136
|
+
def sat?
|
137
|
+
PropLogic.sat_solver.call(self)
|
138
|
+
end
|
139
|
+
|
140
|
+
def unsat?
|
141
|
+
sat? == false
|
142
|
+
end
|
143
|
+
|
144
|
+
def equiv?(other)
|
145
|
+
((self | other) & (~self | ~other)).unsat?
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module PropLogic
|
2
|
+
class ThenTerm < Term
|
3
|
+
def initialize(term1, term2)
|
4
|
+
@terms = [term1, term2].freeze
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s(in_term = false)
|
8
|
+
str = "#{@terms[0].to_s(true)} => #{@terms[1].to_s(true)}"
|
9
|
+
in_term ? "( #{str} )" : str
|
10
|
+
end
|
11
|
+
|
12
|
+
def nnf?
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_nnf
|
17
|
+
(~@terms[0]).to_nnf | @terms[1].to_nnf
|
18
|
+
end
|
19
|
+
|
20
|
+
def reduce
|
21
|
+
to_nnf.reduce
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module PropLogic
|
2
|
+
class Variable < Term
|
3
|
+
def initialize(name = nil)
|
4
|
+
@name = name || "v_#{object_id}"
|
5
|
+
@terms = [].freeze
|
6
|
+
freeze
|
7
|
+
end
|
8
|
+
|
9
|
+
public_class_method :new
|
10
|
+
|
11
|
+
def to_s(*)
|
12
|
+
@name
|
13
|
+
end
|
14
|
+
|
15
|
+
def nnf?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
def reduced?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_cnf
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def tseitin(pool)
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def cnf?
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def variables
|
36
|
+
[self]
|
37
|
+
end
|
38
|
+
|
39
|
+
def assign(trues, falses, variables = nil)
|
40
|
+
if trues.include? self
|
41
|
+
True
|
42
|
+
elsif falses.include? self
|
43
|
+
False
|
44
|
+
else
|
45
|
+
self
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/prop_logic.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "prop_logic/version"
|
2
|
+
require "prop_logic/term"
|
3
|
+
require "prop_logic/and_term"
|
4
|
+
require "prop_logic/or_term"
|
5
|
+
require "prop_logic/not_term"
|
6
|
+
require "prop_logic/then_term"
|
7
|
+
require 'prop_logic/variable'
|
8
|
+
require 'prop_logic/constants'
|
9
|
+
require 'prop_logic/functions'
|
10
|
+
require 'prop_logic/brute_force_sat_solver'
|
11
|
+
require 'prop_logic/sat_solver'
|
data/prop_logic.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'prop_logic/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "prop_logic"
|
8
|
+
spec.version = PropLogic::VERSION
|
9
|
+
spec.authors = ["Jkr2255"]
|
10
|
+
spec.email = ["magnesium.oxide.play@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Propositional logic for Ruby}
|
13
|
+
spec.description = %q{Write propositional logic formulae using Ruby DSL.}
|
14
|
+
spec.homepage = "https://github.com/jkr2255/prop_logic"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_dependency "ref", '~> 2.0'
|
24
|
+
spec.required_ruby_version = '>= 2.0.0'
|
25
|
+
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
28
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
29
|
+
spec.add_development_dependency "rspec", '~> 3.0'
|
30
|
+
# spec.add_development_dependency "pry-byebug", '~> 3.3'
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: prop_logic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jkr2255
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ref
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
prerelease: false
|
21
|
+
type: :runtime
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
prerelease: false
|
35
|
+
type: :development
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
prerelease: false
|
49
|
+
type: :development
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
prerelease: false
|
63
|
+
type: :development
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
description: Write propositional logic formulae using Ruby DSL.
|
70
|
+
email:
|
71
|
+
- magnesium.oxide.play@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".rspec"
|
78
|
+
- ".travis.yml"
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- bin/console
|
84
|
+
- bin/setup
|
85
|
+
- lib/prop_logic.rb
|
86
|
+
- lib/prop_logic/and_term.rb
|
87
|
+
- lib/prop_logic/brute_force_sat_solver.rb
|
88
|
+
- lib/prop_logic/constants.rb
|
89
|
+
- lib/prop_logic/functions.rb
|
90
|
+
- lib/prop_logic/not_term.rb
|
91
|
+
- lib/prop_logic/or_term.rb
|
92
|
+
- lib/prop_logic/sat_solver.rb
|
93
|
+
- lib/prop_logic/term.rb
|
94
|
+
- lib/prop_logic/then_term.rb
|
95
|
+
- lib/prop_logic/variable.rb
|
96
|
+
- lib/prop_logic/version.rb
|
97
|
+
- prop_logic.gemspec
|
98
|
+
homepage: https://github.com/jkr2255/prop_logic
|
99
|
+
licenses:
|
100
|
+
- MIT
|
101
|
+
metadata: {}
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.0.0
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
requirements: []
|
117
|
+
rubyforge_project:
|
118
|
+
rubygems_version: 2.5.1
|
119
|
+
signing_key:
|
120
|
+
specification_version: 4
|
121
|
+
summary: Propositional logic for Ruby
|
122
|
+
test_files: []
|