logica 0.0.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 +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
|