logica 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +50 -0
- data/.travis.yml +9 -0
- data/CHANGELOG.md +14 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +91 -0
- data/Rakefile +17 -0
- data/bin/console +14 -0
- data/bin/setup +6 -0
- data/lib/logica/comparable_by_state.rb +24 -0
- data/lib/logica/predicate_factory.rb +111 -0
- data/lib/logica/predicates/ad_hoc.rb +16 -0
- data/lib/logica/predicates/base.rb +210 -0
- data/lib/logica/predicates/compounds/at_least.rb +90 -0
- data/lib/logica/predicates/compounds/base.rb +134 -0
- data/lib/logica/predicates/compounds/conjunction.rb +75 -0
- data/lib/logica/predicates/compounds/disjunction.rb +79 -0
- data/lib/logica/predicates/contradiction.rb +25 -0
- data/lib/logica/predicates/negation.rb +45 -0
- data/lib/logica/predicates/partial_application.rb +30 -0
- data/lib/logica/predicates/tautology.rb +29 -0
- data/lib/logica/version.rb +3 -0
- data/lib/logica.rb +25 -0
- data/logica.gemspec +38 -0
- metadata +197 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
module Logica
|
2
|
+
module Predicates
|
3
|
+
module Compounds
|
4
|
+
class AtLeast < Base
|
5
|
+
attr_reader :amount
|
6
|
+
|
7
|
+
def initialize(amount, predicates)
|
8
|
+
super(predicates)
|
9
|
+
@amount = amount
|
10
|
+
end
|
11
|
+
|
12
|
+
def satisfied_by?(*arguments)
|
13
|
+
true_count = 0
|
14
|
+
predicates_count = predicates.size
|
15
|
+
predicates.each_with_index do |predicate, index|
|
16
|
+
true_count += 1 if predicate.satisfied_by?(*arguments)
|
17
|
+
return true if true_count >= amount
|
18
|
+
remaining_predicates_count = predicates_count - (index + 1)
|
19
|
+
return false if true_count + remaining_predicates_count < amount
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def and(other)
|
24
|
+
other.and_with_at_least(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def and_with_other(other, options = {})
|
28
|
+
default_options = {
|
29
|
+
other_first: true
|
30
|
+
}
|
31
|
+
options = default_options.merge(options)
|
32
|
+
|
33
|
+
subsumed = predicates.select { |pred| pred.generalization_of?(other) }
|
34
|
+
new_amount = amount - subsumed.size
|
35
|
+
new_predicates = predicates - subsumed
|
36
|
+
new_at_least = predicate_factory.at_least(new_amount, new_predicates)
|
37
|
+
|
38
|
+
pair = options[:other_first] ? [other, new_at_least] : [new_at_least, other]
|
39
|
+
predicate_factory.conjunction_from_pair(*pair)
|
40
|
+
end
|
41
|
+
|
42
|
+
def portion_satisfied_by(*arguments)
|
43
|
+
if satisfied_by?(*arguments)
|
44
|
+
self
|
45
|
+
else
|
46
|
+
to_disjunction.portion_satisfied_by(*arguments)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def remainder_unsatisfied_by(*arguments)
|
51
|
+
if satisfied_by?(*arguments)
|
52
|
+
predicate_factory.contradiction
|
53
|
+
else
|
54
|
+
unsatisfied_predicates = predicates_unsatisfied_by(*arguments)
|
55
|
+
satisfied_count = predicates.size - unsatisfied_predicates.size
|
56
|
+
new_amount = amount - satisfied_count
|
57
|
+
|
58
|
+
predicate_factory.at_least(new_amount, unsatisfied_predicates)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_disjunction
|
63
|
+
@to_disjunction ||= begin
|
64
|
+
combinations = predicates.combination(amount)
|
65
|
+
|
66
|
+
conjunctions = combinations.map do |amount_preds|
|
67
|
+
predicate_factory.conjunction(amount_preds)
|
68
|
+
end
|
69
|
+
|
70
|
+
predicate_factory.disjunction(conjunctions)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def name_and_attributes
|
75
|
+
"#{name}(#{amount}, [#{attributes_string}])"
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def new_from_list(preds)
|
81
|
+
self.class.new(amount, preds)
|
82
|
+
end
|
83
|
+
|
84
|
+
def do_method_missing(name, *args, &block)
|
85
|
+
to_disjunction.do_method_missing(name, *args, &block)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Logica
|
2
|
+
module Predicates
|
3
|
+
module Compounds
|
4
|
+
class Base < Predicates::Base
|
5
|
+
attr_reader :predicates
|
6
|
+
|
7
|
+
def self.new_from_pair(first_predicate, second_predicate)
|
8
|
+
new([first_predicate]).with_extra_predicate_last(second_predicate)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.new_from_list(predicates)
|
12
|
+
predicates.reduce(internal_binary_operation) || neutral_element
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(predicates)
|
16
|
+
@predicates = predicates
|
17
|
+
validate_composability
|
18
|
+
end
|
19
|
+
|
20
|
+
def with_extra_predicates(extra_predicates)
|
21
|
+
extra_predicates.reduce(self) do |compound, extra_predicate|
|
22
|
+
return compound if compound == absorbing_element
|
23
|
+
compound.with_extra_predicate_last(extra_predicate)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def with_extra_predicate_last(extra_predicate)
|
28
|
+
with_extra_predicate(extra_predicate) do |subsumed, extra|
|
29
|
+
predicates - subsumed + [extra]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def arity
|
34
|
+
predicates.first.arity
|
35
|
+
end
|
36
|
+
|
37
|
+
def name_and_attributes
|
38
|
+
"#{name}(#{attributes_string})"
|
39
|
+
end
|
40
|
+
|
41
|
+
def without(predicate_or_predicates, options = {})
|
42
|
+
without_predicates([predicate_or_predicates].flatten(1), options)
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def without_predicates(preds, options = {})
|
48
|
+
default_options = {
|
49
|
+
recursive: false
|
50
|
+
}
|
51
|
+
options = default_options.merge(options)
|
52
|
+
|
53
|
+
difference = predicates - preds
|
54
|
+
|
55
|
+
remaining = if options[:recursive]
|
56
|
+
difference.flat_map { |pred| pred.without_predicates(preds, options) }
|
57
|
+
else
|
58
|
+
difference
|
59
|
+
end
|
60
|
+
|
61
|
+
new_from_list(remaining)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def validate_composability
|
67
|
+
raise_composability_error unless predicates_are_composable?
|
68
|
+
end
|
69
|
+
|
70
|
+
def raise_composability_error
|
71
|
+
raise ArgumentError, composability_error_message
|
72
|
+
end
|
73
|
+
|
74
|
+
def composability_error_message
|
75
|
+
"cannot compose predicates #{predicates} (arity mismatch)"
|
76
|
+
end
|
77
|
+
|
78
|
+
def predicates_are_composable?
|
79
|
+
predicates.map(&:arity).uniq.size <= 1
|
80
|
+
end
|
81
|
+
|
82
|
+
def annihilated_by?(other)
|
83
|
+
predicates.any? { |own_predicate| annihilation?(own_predicate, other) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def subsumes?(other)
|
87
|
+
predicates.any? { |own_predicate| subsumption?(own_predicate, other) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def with_extra_predicate_first(extra_predicate)
|
91
|
+
with_extra_predicate(extra_predicate) do |subsumed, extra|
|
92
|
+
[extra] + predicates - subsumed
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def with_extra_predicate(extra_predicate)
|
97
|
+
return absorbing_element if annihilated_by?(extra_predicate)
|
98
|
+
|
99
|
+
candidates = if subsumes?(extra_predicate)
|
100
|
+
predicates
|
101
|
+
else
|
102
|
+
predicates_subsumed = predicates.select { |pred| subsumption?(extra_predicate, pred) }
|
103
|
+
yield predicates_subsumed, extra_predicate
|
104
|
+
end
|
105
|
+
candidates.one? ? candidates.first : new_with_predicates(candidates)
|
106
|
+
end
|
107
|
+
|
108
|
+
def predicates_satisfied_by(*arguments)
|
109
|
+
predicates.select { |predicate| predicate.satisfied_by?(*arguments) }
|
110
|
+
end
|
111
|
+
|
112
|
+
def predicates_unsatisfied_by(*arguments)
|
113
|
+
predicates.select { |predicate| predicate.unsatisfied_by?(*arguments) }
|
114
|
+
end
|
115
|
+
|
116
|
+
def new_with_predicates(new_predicates)
|
117
|
+
self.class.new(new_predicates)
|
118
|
+
end
|
119
|
+
|
120
|
+
def new_from_list(preds)
|
121
|
+
self.class.new_from_list(preds)
|
122
|
+
end
|
123
|
+
|
124
|
+
def absorbing_element
|
125
|
+
self.class.absorbing_element
|
126
|
+
end
|
127
|
+
|
128
|
+
def attributes_string
|
129
|
+
predicates.map(&:name_and_attributes).join(', ')
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Logica
|
2
|
+
module Predicates
|
3
|
+
module Compounds
|
4
|
+
class Conjunction < Base
|
5
|
+
class << self
|
6
|
+
def internal_binary_operation
|
7
|
+
:and
|
8
|
+
end
|
9
|
+
|
10
|
+
def neutral_element
|
11
|
+
predicate_factory.tautology
|
12
|
+
end
|
13
|
+
|
14
|
+
def absorbing_element
|
15
|
+
predicate_factory.contradiction
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def satisfied_by?(*arguments)
|
20
|
+
predicates.all? { |predicate| predicate.satisfied_by?(*arguments) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def and(other)
|
24
|
+
other.and_with_conjunction(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def and_with_other(other)
|
28
|
+
with_extra_predicate_first(other)
|
29
|
+
end
|
30
|
+
|
31
|
+
def specialization_of?(other)
|
32
|
+
other.generalization_of_conjunction?(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def specialization_of_other?(other)
|
36
|
+
predicates.any? { |predicate| predicate.specialization_of?(other) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def portion_satisfied_by(*arguments)
|
40
|
+
satisfied_predicates = predicates_satisfied_by(*arguments)
|
41
|
+
predicate_factory.conjunction(satisfied_predicates)
|
42
|
+
end
|
43
|
+
|
44
|
+
def remainder_unsatisfied_by(*arguments)
|
45
|
+
unsatisfied_predicates = predicates_unsatisfied_by(*arguments)
|
46
|
+
if unsatisfied_predicates.empty?
|
47
|
+
predicate_factory.contradiction
|
48
|
+
else
|
49
|
+
predicate_factory.conjunction(unsatisfied_predicates)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def and_with_conjunction(conjunction)
|
56
|
+
conjunction.with_extra_predicates(predicates)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def name
|
62
|
+
'AND'
|
63
|
+
end
|
64
|
+
|
65
|
+
def subsumption?(first, second)
|
66
|
+
first.specialization_of?(second)
|
67
|
+
end
|
68
|
+
|
69
|
+
def annihilation?(first, second)
|
70
|
+
first.disjoint_with?(second)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Logica
|
2
|
+
module Predicates
|
3
|
+
module Compounds
|
4
|
+
class Disjunction < Base
|
5
|
+
class << self
|
6
|
+
def internal_binary_operation
|
7
|
+
:or
|
8
|
+
end
|
9
|
+
|
10
|
+
def neutral_element
|
11
|
+
predicate_factory.contradiction
|
12
|
+
end
|
13
|
+
|
14
|
+
def absorbing_element
|
15
|
+
predicate_factory.tautology
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def satisfied_by?(*arguments)
|
20
|
+
predicates.any? { |predicate| predicate.satisfied_by?(*arguments) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def or(other)
|
24
|
+
other.or_with_disjunction(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def or_with_other(other)
|
28
|
+
with_extra_predicate_first(other)
|
29
|
+
end
|
30
|
+
|
31
|
+
def specialization_of?(other)
|
32
|
+
other.generalization_of_disjunction?(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def generalization_of_other?(other)
|
36
|
+
predicates.any? { |predicate| predicate.generalization_of?(other) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def portion_satisfied_by(*arguments)
|
40
|
+
if satisfied_by?(*arguments)
|
41
|
+
self
|
42
|
+
else
|
43
|
+
portions = predicates.map { |predicate| predicate.portion_satisfied_by(*arguments) }
|
44
|
+
predicate_factory.conjunction(portions)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def remainder_unsatisfied_by(*arguments)
|
49
|
+
if satisfied_by?(*arguments)
|
50
|
+
predicate_factory.contradiction
|
51
|
+
else
|
52
|
+
remainders = predicates.map { |predicate| predicate.remainder_unsatisfied_by(*arguments) }
|
53
|
+
predicate_factory.disjunction(remainders)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def or_with_disjunction(disjunction)
|
60
|
+
disjunction.with_extra_predicates(predicates)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def name
|
66
|
+
'OR'
|
67
|
+
end
|
68
|
+
|
69
|
+
def subsumption?(first, second)
|
70
|
+
first.generalization_of?(second)
|
71
|
+
end
|
72
|
+
|
73
|
+
def annihilation?(first, second)
|
74
|
+
first.exhaustive_with?(second)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Logica
|
2
|
+
module Predicates
|
3
|
+
class Contradiction < Base
|
4
|
+
def satisfied_by?(*arguments)
|
5
|
+
false
|
6
|
+
end
|
7
|
+
|
8
|
+
def negated
|
9
|
+
predicate_factory.tautology
|
10
|
+
end
|
11
|
+
|
12
|
+
def specialization_of?(other)
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def generalization_of_other?(other)
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
'FALSE |n|'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Logica
|
2
|
+
module Predicates
|
3
|
+
class Negation < Base
|
4
|
+
attr_reader :predicate
|
5
|
+
|
6
|
+
def initialize(predicate)
|
7
|
+
@predicate = predicate
|
8
|
+
end
|
9
|
+
|
10
|
+
def satisfied_by?(*arguments)
|
11
|
+
predicate.unsatisfied_by?(*arguments)
|
12
|
+
end
|
13
|
+
|
14
|
+
def negated
|
15
|
+
predicate
|
16
|
+
end
|
17
|
+
|
18
|
+
def specialization_of?(other)
|
19
|
+
other.generalization_of_negation_of?(predicate)
|
20
|
+
end
|
21
|
+
|
22
|
+
def generalization_of_negation_of?(other)
|
23
|
+
predicate.specialization_of?(other)
|
24
|
+
end
|
25
|
+
|
26
|
+
def disjoint_with_other?(other)
|
27
|
+
predicate.generalization_of?(other)
|
28
|
+
end
|
29
|
+
|
30
|
+
def arity
|
31
|
+
predicate.arity
|
32
|
+
end
|
33
|
+
|
34
|
+
def name_and_attributes
|
35
|
+
"#{name}(#{predicate.name_and_attributes})"
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def name
|
41
|
+
'NOT'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Logica
|
2
|
+
module Predicates
|
3
|
+
class PartialApplication < Base
|
4
|
+
attr_reader :predicate, :first_arguments
|
5
|
+
|
6
|
+
def initialize(predicate, first_arguments)
|
7
|
+
@predicate = predicate
|
8
|
+
@first_arguments = first_arguments
|
9
|
+
end
|
10
|
+
|
11
|
+
def satisfied_by?(*arguments)
|
12
|
+
predicate.satisfied_by?(*(first_arguments + arguments))
|
13
|
+
end
|
14
|
+
|
15
|
+
def arity
|
16
|
+
predicate.arity - first_arguments.size
|
17
|
+
end
|
18
|
+
|
19
|
+
def name_and_attributes
|
20
|
+
"#{predicate.name_and_attributes}(#{first_arguments.join(', ')})"
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def do_partially_applied_with(more_arguments)
|
26
|
+
predicate_factory.partial_application(predicate, first_arguments + more_arguments)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Logica
|
2
|
+
module Predicates
|
3
|
+
class Tautology < Base
|
4
|
+
def satisfied_by?(*arguments)
|
5
|
+
true
|
6
|
+
end
|
7
|
+
|
8
|
+
def negated
|
9
|
+
predicate_factory.contradiction
|
10
|
+
end
|
11
|
+
|
12
|
+
def specialization_of?(other)
|
13
|
+
other.generalization_of_tautology?(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
def generalization_of_other?(other)
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def generalization_of_negation_of?(other)
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
'TRUE |n|'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/logica.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'openhouse'
|
2
|
+
|
3
|
+
require 'logica/version'
|
4
|
+
|
5
|
+
require 'logica/comparable_by_state'
|
6
|
+
|
7
|
+
require 'logica/predicates/base'
|
8
|
+
require 'logica/predicates/ad_hoc'
|
9
|
+
require 'logica/predicates/partial_application'
|
10
|
+
require 'logica/predicates/negation'
|
11
|
+
require 'logica/predicates/tautology'
|
12
|
+
require 'logica/predicates/contradiction'
|
13
|
+
|
14
|
+
require 'logica/predicates/compounds/base'
|
15
|
+
require 'logica/predicates/compounds/conjunction'
|
16
|
+
require 'logica/predicates/compounds/disjunction'
|
17
|
+
require 'logica/predicates/compounds/at_least'
|
18
|
+
|
19
|
+
require 'logica/predicate_factory'
|
20
|
+
|
21
|
+
module Logica
|
22
|
+
def self.predicate_factory
|
23
|
+
@predicate_factory ||= PredicateFactory.new
|
24
|
+
end
|
25
|
+
end
|
data/logica.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'logica/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'logica'
|
9
|
+
spec.version = Logica::VERSION
|
10
|
+
spec.date = '2017-06-20'
|
11
|
+
spec.authors = ['Eugenio Bruno']
|
12
|
+
spec.email = ['eugeniobruno@gmail.com']
|
13
|
+
|
14
|
+
spec.summary = 'Logica is a framework to reify and work with predicates.'
|
15
|
+
spec.homepage = 'https://github.com/eugeniobruno/logica'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
19
|
+
f.match(%r{^(test|spec|features)/})
|
20
|
+
end
|
21
|
+
spec.bindir = 'bin'
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
24
|
+
spec.require_paths = ['lib']
|
25
|
+
spec.extra_rdoc_files = %w[LICENSE.txt README.md CODE_OF_CONDUCT.md CHANGELOG.md]
|
26
|
+
|
27
|
+
spec.required_ruby_version = '>= 2.0.0'
|
28
|
+
|
29
|
+
spec.add_runtime_dependency 'openhouse', '~> 0.0', '>= 0.0.2'
|
30
|
+
|
31
|
+
spec.add_development_dependency 'bundler', '~> 1.15'
|
32
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
33
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
34
|
+
spec.add_development_dependency 'minitest-bender', '~> 0.0', '>= 0.0.2'
|
35
|
+
spec.add_development_dependency 'simplecov', '~> 0.14'
|
36
|
+
spec.add_development_dependency 'coveralls', '~> 0.8'
|
37
|
+
spec.add_development_dependency 'pry-byebug', '~> 3.4'
|
38
|
+
end
|