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.
@@ -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
@@ -0,0 +1,3 @@
1
+ module Logica
2
+ VERSION = '0.0.1'.freeze
3
+ 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