logica 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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