rdf-reasoner 0.1.0 → 0.2.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.
data/README.md CHANGED
@@ -6,7 +6,10 @@ Reasons over RDFS/OWL vocabularies and schema.org to generate statements which a
6
6
 
7
7
  * Entail `rdfs:subClassOf` generating an array of terms which are ancestors of the subject.
8
8
  * Entail `rdfs:subPropertyOf` generating an array of terms which are ancestors of the subject.
9
+ * Entail `rdfs:domain` and `rdfs:range` adding `rdf:type` assertions on the subject or object.
9
10
  * Inverse `rdfs:subClassOf` entailment, to find descendant classes of the subject term.
11
+ * Entail `owl:equivalentClass` generating an array of terms equivalent to the subject.
12
+ * Entail `owl:equivalentProperty` generating an array of terms equivalent to the subject.
10
13
  * `domainCompatible?` determines if a particular resource is compatible with the domain definition of a given predicate, based on the intersection of entailed subclasses with the property domain.
11
14
  * `rangeCompatible?` determines if a particular resource is compatible with the range definition of a given predicate, based on the intersection of entailed subclasses or literal types with the property domain.
12
15
 
@@ -17,6 +20,67 @@ Domain and Range entailment include specific rules for schema.org vocabularies.
17
20
  * If `resource` is of type `schema:Role`, it is range acceptable if it has the same property with an acceptable value.
18
21
  * If `resource` is of type `rdf:List` (must be previously entailed), it is range acceptable if all members of the list are otherwise range acceptable on the same property.
19
22
 
23
+ ## Examples
24
+ ### Determine super-classes of a class
25
+
26
+ require 'rdf/reasoner'
27
+
28
+ RDF::Reasoner.apply(:rdfs)
29
+ term = RDF::Vocabulary.find_term("http://xmlns.com/foaf/0.1/Person")
30
+ term.entail(:subClassOf)
31
+ # => [
32
+ foaf:Agent,
33
+ http://www.w3.org/2000/10/swap/pim/contact#Person,
34
+ geo:SpatialThing,
35
+ foaf:Person
36
+ ]
37
+
38
+ ### Determine sub-classes of a class
39
+
40
+ require 'rdf/reasoner'
41
+
42
+ RDF::Reasoner.apply(:rdfs)
43
+ term = RDF::FOAF.Person
44
+ term.entail(:subClass) # => [foaf:Person, mo:SoloMusicArtist]
45
+
46
+ ### Determine if a resource is compatible with the domains of a property
47
+
48
+ require 'rdf/reasoner'
49
+ require 'rdf/turtle'
50
+
51
+ RDF::Reasoner.apply(:rdfs)
52
+ graph = RDF::Graph.load("etc/doap.ttl")
53
+ subj = RDF::URI("http://rubygems.org/gems/rdf-reasoner")
54
+ RDF::DOAP.name.domain_compatible?(subj, graph) # => true
55
+
56
+ ### Determine if a resource is compatible with the ranges of a property
57
+
58
+ require 'rdf/reasoner'
59
+ require 'rdf/turtle'
60
+
61
+ RDF::Reasoner.apply(:rdfs)
62
+ graph = RDF::Graph.load("etc/doap.ttl")
63
+ obj = RDF::Literal(Date.new)
64
+ RDF::DOAP.created.range_compatible?(obj, graph) # => true
65
+
66
+ ### Perform equivalentClass entailment on a graph
67
+
68
+ require 'rdf/reasoner'
69
+ require 'rdf/turtle'
70
+
71
+ RDF::Reasoner.apply(::owl)
72
+ graph = RDF::Graph.load("etc/doap.ttl")
73
+ graph.entail!(:equivalentClass)
74
+
75
+ ### Yield all entailed statements for all entailment methods
76
+
77
+ require 'rdf/reasoner'
78
+ require 'rdf/turtle'
79
+
80
+ RDF::Reasoner.apply(:rdfs, :owl)
81
+ graph = RDF::Graph.load("etc/doap.ttl")
82
+ graph.enum_statement.entail.count # >= graph.enum_statement.count
83
+
20
84
  ## Dependencies
21
85
 
22
86
  * [Ruby](http://ruby-lang.org/) (>= 1.9.2)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -18,7 +18,7 @@ module RDF
18
18
  #
19
19
  # @param [Array<:owl, :rdfs, :schema>] regime
20
20
  def apply(*regime)
21
- regime.each {|r| require "rdf/reasoner/#{r.downcase}"}
21
+ regime.each {|r| require "rdf/reasoner/#{r.to_s.downcase}"}
22
22
  end
23
23
  module_function :apply
24
24
 
@@ -1,55 +1,171 @@
1
1
  # Extensions to RDF core classes to support reasoning
2
2
  require 'rdf'
3
3
 
4
- class RDF::Vocabulary::Term
5
- class << self
6
- @@entailments = {}
4
+ module RDF
5
+ class Vocabulary::Term
6
+ class << self
7
+ @@entailments = {}
8
+
9
+ ##
10
+ # Add an entailment method. The method accepts no arguments, and returns or yields an array of values associated with the particular entailment method
11
+ # @param [Symbol] method
12
+ # @param [Proc] proc
13
+ def add_entailment(method, proc)
14
+ @@entailments[method] = proc
15
+ end
16
+ end
17
+
18
+ ##
19
+ # Perform an entailment on this term.
20
+ #
21
+ # @param [Symbol] method A registered entailment method
22
+ # @yield term
23
+ # @yieldparam [Term] term
24
+ # @return [Array<Term>]
25
+ def entail(method, &block)
26
+ self.send(@@entailments.fetch(method), &block)
27
+ end
28
+
29
+ ##
30
+ # Determine if the domain of a property term is consistent with the specified resource in `queryable`.
31
+ #
32
+ # @param [RDF::Resource] resource
33
+ # @param [RDF::Queryable] queryable
34
+ # @param [Hash{Symbol => Object}] options ({})
35
+ # @option options [Array<RDF::Vocabulary::Term>] :types
36
+ # Fully entailed types of resource, if not provided, they are queried
37
+ def domain_compatible?(resource, queryable, options = {})
38
+ %w(owl rdfs schema).map {|r| "domain_compatible_#{r}?".to_sym}.all? do |meth|
39
+ !self.respond_to?(meth) || self.send(meth, resource, queryable, options)
40
+ end
41
+ end
7
42
 
8
43
  ##
9
- # Add an entailment method. The method accepts no arguments, and returns an array of values associated with the particular entailment method
10
- # @param [Symbol] method
11
- # @param [Proc] proc
12
- def add_entailment(method, proc)
13
- @@entailments[method] = proc
44
+ # Determine if the range of a property term is consistent with the specified resource in `queryable`.
45
+ #
46
+ # Specific entailment regimes should insert themselves before this to apply the appropriate semantic condition
47
+ #
48
+ # @param [RDF::Resource] resource
49
+ # @param [RDF::Queryable] queryable
50
+ # @param [Hash{Symbol => Object}] options ({})
51
+ # @option options [Array<RDF::Vocabulary::Term>] :types
52
+ # Fully entailed types of resource, if not provided, they are queried
53
+ def range_compatible?(resource, queryable, options = {})
54
+ %w(owl rdfs schema).map {|r| "range_compatible_#{r}?".to_sym}.all? do |meth|
55
+ !self.respond_to?(meth) || self.send(meth, resource, queryable, options)
56
+ end
14
57
  end
15
58
  end
16
59
 
17
- ##
18
- # Perform an entailment on this term. Entailments defined within this module are `:subClassOf`, `:subPropertyOf`, and `:subClass`.
19
- #
20
- # @param [Symbol] method A registered entailment method
21
- # @return [Array<Term>]
22
- def entail(method)
23
- self.send @@entailments.fetch(method)
60
+ class Statement
61
+ class << self
62
+ @@entailments = {}
63
+
64
+ ##
65
+ # Add an entailment method. The method accepts no arguments, and returns or yields an array of values associated with the particular entailment method
66
+ # @param [Symbol] method
67
+ # @param [Proc] proc
68
+ def add_entailment(method, proc)
69
+ @@entailments[method] = proc
70
+ end
71
+ end
72
+
73
+ ##
74
+ # Perform an entailment on this term.
75
+ #
76
+ # @param [Symbol] method A registered entailment method
77
+ # @yield term
78
+ # @yieldparam [Term] term
79
+ # @return [Array<Term>]
80
+ def entail(method, &block)
81
+ self.send(@@entailments.fetch(method), &block)
82
+ end
24
83
  end
25
84
 
26
- ##
27
- # Determine if the domain of a property term is consistent with the specified resource in `queryable`.
28
- #
29
- # @param [RDF::Resource] resource
30
- # @param [RDF::Queryable] queryable
31
- # @param [Hash{Symbol => Object}] options ({})
32
- # @option options [Array<RDF::Vocabulary::Term>] :types
33
- # Fully entailed types of resource, if not provided, they are queried
34
- def domain_compatible?(resource, queryable, options = {})
35
- %w(owl rdfs schema).map {|r| "domain_compatible_#{r}?".to_sym}.all? do |meth|
36
- !self.respond_to?(meth) || self.send(meth, resource, queryable, options)
85
+ module Enumerable
86
+ class << self
87
+ @@entailments = {}
88
+
89
+ ##
90
+ # Add an entailment method. The method accepts no arguments, and returns or yields an array of values associated with the particular entailment method
91
+ # @param [Symbol] method
92
+ # @param [Proc] proc
93
+ def add_entailment(method, proc)
94
+ @@entailments[method] = proc
95
+ end
96
+ end
97
+
98
+ ##
99
+ # Perform entailments on this enumerable in a single pass, yielding entailed statements.
100
+ #
101
+ # For best results, either run rules separately expanding the enumberated graph, or run repeatedly until no new statements are added to the enumerable containing both original and entailed statements. As `:subClassOf` and `:subPropertyOf` entailments are implicitly recursive, this may not be necessary except for extreme cases.
102
+ #
103
+ # @overload entail
104
+ # @param [Array<Symbol>] *rules Registered entailment method(s)
105
+ # @yield statement
106
+ # @yieldparam [RDF::Statement] statement
107
+ # @return [void]
108
+ #
109
+ # @overload entail
110
+ # @param [Array<Symbol>] *rules Registered entailment method(s)
111
+ # @return [Enumerator]
112
+ def entail(*rules, &block)
113
+ if block_given?
114
+ rules = @@entailments.keys if rules.empty?
115
+
116
+ self.each do |statement|
117
+ rules.each {|rule| statement.entail(rule, &block)}
118
+ end
119
+ else
120
+ # Otherwise, return an Enumerator with the entailed statements
121
+ this = self
122
+ RDF::Queryable::Enumerator.new do |yielder|
123
+ this.entail(*rules) {|y| yielder << y}
124
+ end
125
+ end
37
126
  end
38
127
  end
39
128
 
40
- ##
41
- # Determine if the range of a property term is consistent with the specified resource in `queryable`.
42
- #
43
- # Specific entailment regimes should insert themselves before this to apply the appropriate semantic condition
44
- #
45
- # @param [RDF::Resource] resource
46
- # @param [RDF::Queryable] queryable
47
- # @param [Hash{Symbol => Object}] options ({})
48
- # @option options [Array<RDF::Vocabulary::Term>] :types
49
- # Fully entailed types of resource, if not provided, they are queried
50
- def range_compatible?(resource, queryable, options = {})
51
- %w(owl rdfs schema).map {|r| "range_compatible_#{r}?".to_sym}.all? do |meth|
52
- !self.respond_to?(meth) || self.send(meth, resource, queryable, options)
129
+ module Mutable
130
+ class << self
131
+ @@entailments = {}
132
+
133
+ ##
134
+ # Add an entailment method. The method accepts no arguments, and returns or yields an array of values associated with the particular entailment method
135
+ # @param [Symbol] method
136
+ # @param [Proc] proc
137
+ def add_entailment(method, proc)
138
+ @@entailments[method] = proc
139
+ end
140
+ end
141
+
142
+ # Return a new mutable, composed of original and entailed statements
143
+ #
144
+ # @param [Array<Symbol>] *rules Registered entailment method(s)
145
+ # @return [RDF::Mutable]
146
+ # @see [RDF::Enumerable#entail]
147
+ def entail(*rules, &block)
148
+ self.dup.entail!(*rules)
149
+ end
150
+
151
+ # Add entailed statements to the mutable
152
+ #
153
+ # @param [Array<Symbol>] *rules Registered entailment method(s)
154
+ # @return [RDF::Mutable]
155
+ # @see [RDF::Enumerable#entail]
156
+ def entail!(*rules, &block)
157
+ rules = @@entailments.keys if rules.empty?
158
+ statements = []
159
+
160
+ self.each do |statement|
161
+ rules.each do |rule|
162
+ statement.entail(rule) do |st|
163
+ statements << st
164
+ end
165
+ end
166
+ end
167
+ self.insert *statements
168
+ self
53
169
  end
54
170
  end
55
- end
171
+ end
@@ -4,344 +4,139 @@ module RDF::Reasoner
4
4
  ##
5
5
  # Rules for generating OWL entailment triples
6
6
  #
7
- # Create instances for each owl:Class, owl:ObjectProperty, owl:DatatypeProperty, owl:DataType and owl:Restriction. This allows querying for querying specific entailed relationships of each instance.
8
- class OWL
9
- # Base class for OWL classes
10
- class Base
11
- # resource attribute, the IRI or BNode subject of the class
12
- # #!attribute [r] resource
13
- # @return [RDF::Resource]
14
- attr_reader :resource
15
-
16
- # RDF::Enumerable containing entity definition
17
- #
18
- # #!attribute [r] enumerable
19
- # @return [RDF::Enumerable]
20
- attr_reader :enumerable
21
-
22
- class << self
23
- # Class reader for all defined entities
24
- # @!attribute [r] all
25
- # @return [Array<Property>]
26
- attr_reader :all
27
- end
28
-
29
- # Find instance based on this resource
30
- # @param [RDF::Resource] resource
31
- # @return [Base]
32
- def self.find(resource)
33
- all.detect {|r| r.resource == resource}
34
- end
35
-
36
- ##
37
- # Create a new entity based on resource within enumerable
38
- #
39
- # @param [RDF::Resource] resource
40
- # @param [RDF::Enumerable] enumerable
41
- def initialize(resource, enumerable)
42
- @resource, @enumerable = resource, enumerable
43
- (Base.all ||= []) << self
44
- end
45
-
46
- # Human label of this class
47
- # @return [String]
48
- def label
49
- @label || @resource.split(/\/\#/).last
50
- end
51
-
52
- # Infered ranges of the {Restriction} or {Property}
53
- # FIXME: This does not account for intersection/union
54
- # @return [Array<OwlClass,DataType>]
55
- def ranges
56
- values = @on_class || @all_values_from || @some_values_from || @on_data_range || @range
57
- return [] unless values
58
- values.map do |v|
59
- v.one_of || v.union_of || ([v] + v.descendant_classes)
60
- end.flatten
61
- end
62
-
63
- # Is this entity the same as `cls`, or is it a union containing `cls`?
64
- # @param [OwlClass] cls
65
- # @return [TrueClass, FalseClass]
66
- def class_of?(cls)
67
- cls == self or union_of.include?(cls)
68
- end
69
-
70
- # Override for actual descendant classes
71
- # @return [Array]
72
- def descendant_classes; []; end
73
-
74
- # Is this a named entity (i.e., not some OWL construction)
75
- # @return [TrueClass, FalseClass]
76
- def named?
77
- @resource.iri?
78
- end
79
-
80
- ##
81
- # Accessors for entity fields
82
- #
83
- # @overload all_values_from
84
- # @return [Array<Base>] reflects owl:allValuesFrom
85
- # @overload _cardinality
86
- # @return [Integer]
87
- # reflects owl:cardinality and owl:qualifiedCardinality.
88
- # @see {Restriction#cardinality}
89
- # @overload equivalent_property
90
- # @return [Array<Property>] reflects owl:equivalentProperty
91
- # ...
92
- def method_missing(meth, *args)
93
- def _access(preds, how)
94
- @access ||= {}
95
- # Memoize result
96
- @access[meth] ||= begin
97
- values = []
98
- preds.each do |pred|
99
- enumerable.query(:subject => resource, :predicate => pred) do |statement|
100
- values << statement.object
101
- end
102
- end
103
- case how
104
- when true
105
- # Value is simply true
106
- true
107
- when :ary
108
- # Translate IRIs into instances of Base
109
- values.map {|v| Base.find(v) || v}
110
- when :list
111
- # Access as list and translate IRIs into instances of Base
112
- RDF::List(values.first, enumerable).to_a.map {|v| Base.find(v) || v}
113
- when :obj, :int
114
- # Take first element of array, and optionally translate to integer
115
- how == :int ? values.first.to_i : values.first
116
- end
117
- end
118
- end
119
-
120
- case :meth
121
- when :all_values_from then _access([RDF::OWL::allValuesFrom], :ary)
122
- when :_cardinality then _access([RDF::OWL::cardinality, RDF::OWL::qualifiedCardinality], :int)
123
- when :domain then _access([RDF::RDFS::domain], :ary)
124
- when :equivalent_property then _access([RDF::OWL::equivalentProperty], :ary)
125
- when :has_self then _access([RDF::OWL::hasSelf], true)
126
- when :has_value then _access([RDF::OWL::hasValue], :ary)
127
- when :intersection_of then _access([RDF::OWL::hasSelf], true)
128
- when :inverse_of then _access([RDF::OWL::inverseOf], :obj)
129
- when :max_cardinality then _access([RDF::OWL::maxCardinality, RDF::OWL::maxQualifiedCardinality], :int)
130
- when :min_cardinality then _access([RDF::OWL::minCardinality, RDF::OWL::minQualifiedCardinality], :int)
131
- when :one_of then _access([RDF::OWL::oneOf], :list)
132
- when :on_class then _access([RDF::OWL::onClass], :obj)
133
- when :on_datarange then _access([RDF::OWL::onDatatype], :obj)
134
- when :on_property then _access([RDF::OWL::onProperty], :obj)
135
- when :range then _access([RDF::RDFS::range], :ary)
136
- when :some_values_from then _access([RDF::OWL::someValuesFrom], :ary)
137
- when :sub_class_of then _access([RDF::RDFS::subClassOf], :ary)
138
- when :sub_property_of then _access([RDF::RDFS::subPropertyOf], :ary)
139
- when :union_of then _access([RDF::OWL::unionOf], :list)
140
- when :with_restrictions then _access([RDF::OWL::withRestrictions], :list)
141
- else
142
- super
143
- end
144
- end
145
- end
146
-
147
- # Entries for owl objects which are Object or Datatype Properties
148
- class Property < Base
149
- class << self
150
- # Class reader for all defined properties
151
- # @!attribute [r] all
152
- # @return [Array<Property>]
153
- attr_reader :all
154
- end
155
-
156
- ##
157
- # Create a new property based on resource within enumerable
158
- #
159
- # @param [RDF::Resource] resource
160
- # @param [RDF::Enumerable] enumerable
161
- def initialize(resource, enumerable)
162
- super
163
- (Property.all ||= []) << self
164
- end
165
-
166
- # Infered domains of this property
167
- # FIXME: does not account for intersection/union, which is uncomon in domains
168
- # @return [Array<OwlClass>]
169
- def domains
170
- self.domain.map do |v|
171
- v.one_of || v.union_of || ([v] + v.descendant_classes)
172
- end.flatten.uniq
173
- end
174
-
175
-
176
- # Does this property have a domain including cls?
177
- # The JSON is defined to always represent domain as an array
178
- # FIXME: this doesn't deal with intersection
179
- # @param [OwlClass] cls
180
- # @return [TrueClass, FalseClass]
181
- def domain_of?(cls)
182
- domain.any? {|dom| dom.class_of?(cls)}
183
- end
7
+ # Extends `RDF::Vocabulary::Term` and `RDF::Statement` with specific entailment capabilities
8
+ module OWL
9
+ ##
10
+ # @return [RDF::Util::Cache]
11
+ # @private
12
+ def equivalentClass_cache
13
+ @@subPropertyOf_cache ||= {}
184
14
  end
185
-
186
-
187
- # OWL Restrictions are similar to classes. They impose a restriction on
188
- # values of some property, such as cardinality
189
- class Restriction < Base
190
- # For restrictions, return any defined cardinality
191
- # as an array of \[min, max\] where either max may be `nil`.
192
- # Min will always be an integer.
193
- # @return [Array<(Integer, Integer)>]
194
- def cardinality
195
- [(@cardinality || @min_cardinality || 0), (@cardinality || @max_cardinality)]
196
- end
15
+ ##
16
+ # @return [RDF::Util::Cache]
17
+ # @private
18
+ def equivalentProperty_cache
19
+ @@equivalentProperty_cache ||= {}
197
20
  end
198
21
 
199
- # Entries for owl objects wich are classes
200
- class OwlClass < Base
201
- class << self
202
- # Class accessor for all defined classes
203
- # @!attribute [r] all
204
- # @return [Array<OwlClass>]
205
- attr_reader :all
206
- end
207
22
 
208
- ##
209
- # Create a new class based on resource within enumerable
210
- #
211
- # @param [RDF::Resource] resource
212
- # @param [RDF::Enumerable] enumerable
213
- def initialize(resource, enumerable)
214
- super
215
- (OwlClass.all ||= []) << self
216
- end
217
-
218
- # Return super classes as an S-Expression
219
- # @return [Array<OwlClass>]
220
- def super_classes
221
- @super_class_cache ||= begin
222
- # Note in super-class, that this is a direct sub-class
223
- sup = self.sub_class_of
23
+ ##
24
+ # For a Term: yield or return inferred equivalentClass relationships
25
+ # For a Statement: if predicate is `rdf:types`, yield or return inferred statements having a equivalentClass relationship to the type of this statement
26
+ # @private
27
+ def _entail_equivalentClass
28
+ case self
29
+ when RDF::Vocabulary::Term
30
+ unless class? && respond_to?(:equivalentClass)
31
+ yield self if block_given?
32
+ return Array(self)
33
+ end
224
34
 
225
- anded_classes = sup.map do |cls|
226
- if cls.is_a?(OwlClass) and cls.named?
227
- cls
228
- elsif cls.unionOf
229
- ored_classes = cls.union_of.select {|c2| c2.is_a?(OwlClass)}.compact
230
- case ored_classes.length
231
- when 0 then nil
232
- when 1 then ored_classes.first
233
- else (%w(|) + ored_classes).freeze
234
- end
35
+ # Initialize @equivalentClass_cache by iterating over all defined property terms having an `owl:equivalentClass` attribute and adding the source class as an equivalent of the destination class
36
+ if equivalentClass_cache.empty?
37
+ RDF::Vocabulary.each do |v|
38
+ v.each do |term|
39
+ term.equivalentClass.each do |equiv|
40
+ (equivalentClass_cache[equiv] ||= []) << term
41
+ end if term.class?
235
42
  end
236
- end.compact
237
-
238
- case anded_classes.length
239
- when 0 then [].freeze
240
- when 1 then anded_classes.first
241
- else (%w(&) + anded_classes).freeze
242
43
  end
243
44
  end
244
- end
245
-
246
- # Return all direct sub-classes
247
- # This counts on each class having had superClasses calculated to
248
- # inject the sub-class relation
249
- # @return [Array<OwlClass>]
250
- def sub_classes
251
- @sub_classes_cache ||= OwlClass.all.select do |c|
252
- c.sub_class_of.any? {|cl| cl.class_of?(self)}
253
- end
254
- end
255
-
256
- # Return all descendant classes
257
- # @return [Array<OwlClass>]
258
- def descendant_classes
259
- @descendant_classes ||= begin
260
- (sub_classes + sub_classes.map {|cls| cls.descendant_classes}.flatten).compact.freeze
261
- end
262
- end
263
-
264
- # Return a list of all property restrictions on this class and super-classes
265
- # @return [Array<Restriction>]
266
- def property_restrictions
267
- @property_restrictions_cache ||= begin
268
- restrictions = evaluate_sexp(self.super_classes) {|c| c.property_restrictions}.dup
269
- restrictions = [restrictions].compact unless restrictions.is_a?(Array)
270
- # Add restrictions defined on this class
271
- self.sub_class_of.select {|r| r.is_a?(Restriction)}.each do |restriction|
272
- prop = restriction.on_property
273
- # Remove any existing restriction on the same property
274
- restrictions.reject! {|r| r.on_property == prop}
275
- #puts "#{resource}: add local restriction: #{restriction.on_property.resource}"
276
- restrictions << restriction
45
+ terms = (self.equivalentClass + Array(equivalentClass_cache[self])).uniq
46
+ terms.each {|t| yield t} if block_given?
47
+ terms
48
+ when RDF::Statement
49
+ statements = []
50
+ if self.predicate == RDF.type
51
+ if term = (RDF::Vocabulary.find_term(self.object) rescue nil)
52
+ term._entail_equivalentClass do |t|
53
+ statements << RDF::Statement.new(self.to_hash.merge(object: t))
54
+ end
277
55
  end
278
-
279
- restrictions.freeze
280
56
  end
57
+ statements.each {|s| yield s} if block_given?
58
+ statements
59
+ else []
281
60
  end
61
+ end
282
62
 
283
- # Return a list of all properties on this class and super-classes
284
- # @return [Array<Property>]
285
- def properties
286
- @properties_cache ||= begin
287
- # Inherited properties
288
- props = evaluate_sexp(super_classes) {|c| c.properties}
63
+ ##
64
+ # For a Term: yield or return return inferred equivalentProperty relationships
65
+ # For a Statement: yield or return inferred statements having a equivalentProperty relationship to predicate of this statement
66
+ # @private
67
+ def _entail_equivalentProperty
68
+ case self
69
+ when RDF::Vocabulary::Term
70
+ unless property? && respond_to?(:equivalentProperty)
71
+ yield self if block_given?
72
+ return Array(self)
73
+ end
289
74
 
290
- # Add properties directly referencing this class
291
- direct_props = Property.all.select do |prop|
292
- prop.domain_of?(self) and
293
- !props.include?(prop)
75
+ # Initialize equivalentProperty_cache by iterating over all defined property terms having an `owl:equivalentProperty` attribute and adding the source class as an equivalent of the destination class
76
+ if equivalentProperty_cache.empty?
77
+ RDF::Vocabulary.each do |v|
78
+ v.each do |term|
79
+ term.equivalentProperty.each do |equiv|
80
+ (equivalentProperty_cache[equiv] ||= []) << term
81
+ end if term.property?
82
+ end
294
83
  end
295
-
296
- (props + direct_props).sort.freeze
297
84
  end
298
- end
299
-
300
- private
301
- # Evaluate a subclass S-Expression
302
- def evaluate_sexp(classes)
303
- case classes
304
- when OWL then yield classes
305
- when Array
306
- case classes.first
307
- when '|' # Union of classes
308
- cls = yield(classes[1]).dup
309
- classes[2..-1].each {|cls2| cls = cls | yield(cls2)}
310
- cls
311
- when '&' # Intersection of classes
312
- cls = yield(classes[1]).dup
313
- classes[2..-1].each {|cls2| cls = cls & yield(cls2)}
314
- cls
315
- else
316
- $logger.error "Unexpected operator in S-Exp for #{@resource}: #{classes.inspect}" unless classes.empty?
317
- classes.dup
85
+ terms = (self.equivalentProperty + Array(equivalentProperty_cache[self])).uniq
86
+ terms.each {|t| yield t} if block_given?
87
+ terms
88
+ when RDF::Statement
89
+ statements = []
90
+ if term = (RDF::Vocabulary.find_term(self.predicate) rescue nil)
91
+ term._entail_equivalentProperty do |t|
92
+ statements << RDF::Statement.new(self.to_hash.merge(predicate: t))
318
93
  end
319
- else
320
- $logger.error "Unexpected value in S-Exp for #{@resource}: #{classes.inspect}" if classes
321
- nil
322
94
  end
95
+ statements.each {|s| yield s} if block_given?
96
+ statements
97
+ else []
323
98
  end
324
99
  end
325
100
 
326
- # Entries for XSD and RDF datatypes
327
- class DataType < Base
328
- attr_accessor :rdf_literal_class
329
-
330
- def initialize(object)
331
- super
332
- @rdf_literal_class = RDF::Literal
333
- end
101
+ ##
102
+ # EquivalentClass of this term, also populates reverse equivalents.
103
+ #
104
+ # When first called, this initializes a cache of reverse terms to terms where the the reverse term is listed as an equivalent of the original term.
105
+ #
106
+ # It returns the list of terms which are equivalent to this term however defined.
107
+ # @return [Array<RDF::Vocabulary::Term>]
108
+ def equivalentClass
109
+ raise RDF::Reasoner::Error, "#{self} Can't entail equivalentClass" unless class?
110
+ Array(self.attributes["owl:equivalentClass"]).map {|t| RDF::Vocabulary.expand_pname(t)}
111
+ end
334
112
 
335
- def validate(value)
336
- if (rdf_literal_class.new(value).valid? rescue false)
337
- case
338
- when one_of
339
- "#{value.inspect} must be one of #{one_of.inspect}" unless one_of.include?(value)
340
- end
341
- else
342
- "#{value.inspect} is not a valid #{label}(#{resource})"
343
- end
344
- end
113
+ ##
114
+ # EquivalentProperty of this term, also populates reverse equivalents.
115
+ #
116
+ # When first called, this initializes a cache of reverse terms to terms where the the reverse term is listed as an equivalent of the original term.
117
+ #
118
+ # It returns the list of terms which are equivalent to this term however defined.
119
+ # @return [Array<RDF::Vocabulary::Term>]
120
+ def equivalentProperty
121
+ raise RDF::Reasoner::Error, "#{self} Can't entail equivalentProperty" unless property?
122
+ Array(self.attributes["owl:equivalentProperty"]).map {|t| RDF::Vocabulary.expand_pname(t)}
123
+ end
124
+
125
+ def self.included(mod)
126
+ mod.add_entailment :equivalentClass, :_entail_equivalentClass
127
+ mod.add_entailment :equivalentProperty, :_entail_equivalentProperty
345
128
  end
346
129
  end
130
+
131
+ # Extend Term with these methods
132
+ ::RDF::Vocabulary::Term.send(:include, OWL)
133
+
134
+ # Extend Statement with these methods
135
+ ::RDF::Statement.send(:include, OWL)
136
+
137
+ # Extend Enumerable with these methods
138
+ ::RDF::Enumerable.send(:include, OWL)
139
+
140
+ # Extend Mutable with these methods
141
+ ::RDF::Mutable.send(:include, OWL)
347
142
  end
@@ -4,7 +4,7 @@ module RDF::Reasoner
4
4
  ##
5
5
  # Rules for generating RDFS entailment triples
6
6
  #
7
- # Extends `RDF::Vocabulary::Term` with specific entailment capabilities
7
+ # Extends `RDF::Vocabulary::Term` and `RDF::Statement` with specific entailment capabilities
8
8
  module RDFS
9
9
  ##
10
10
  # @return [RDF::Util::Cache]
@@ -35,22 +35,59 @@ module RDF::Reasoner
35
35
  end
36
36
 
37
37
  ##
38
- # Return inferred subClassOf relationships by recursively applying to named super classes to get a complete set of classes in the ancestor chain of this class
38
+ # For a Term: yield or return inferred subClassOf relationships by recursively applying to named super classes to get a complete set of classes in the ancestor chain of this class
39
+ # For a Statement: if predicate is `rdf:types`, yield or return inferred statements having a subClassOf relationship to the type of this statement
39
40
  # @private
40
41
  def _entail_subClassOf
41
- return Array(self) unless class? && respond_to?(:subClassOf)
42
- subClassOf_cache[self] ||= begin
43
- (Array(self.subClassOf).map {|c| c._entail_subClassOf rescue c}.flatten + Array(self)).compact
42
+ case self
43
+ when RDF::Vocabulary::Term
44
+ unless class? && respond_to?(:subClassOf)
45
+ yield self if block_given?
46
+ return Array(self)
47
+ end
48
+ terms = subClassOf_cache[self] ||= (
49
+ Array(self.subClassOf).
50
+ map {|c| c._entail_subClassOf rescue c}.
51
+ flatten +
52
+ Array(self)
53
+ ).compact
54
+ terms.each {|t| yield t} if block_given?
55
+ terms
56
+ when RDF::Statement
57
+ statements = []
58
+ if self.predicate == RDF.type
59
+ if term = (RDF::Vocabulary.find_term(self.object) rescue nil)
60
+ term._entail_subClassOf do |t|
61
+ statements << RDF::Statement.new(self.to_hash.merge(object: t))
62
+ end
63
+ end
64
+ end
65
+ statements.each {|s| yield s} if block_given?
66
+ statements
67
+ else []
44
68
  end
45
69
  end
46
70
 
47
71
  ##
48
- # Return inferred subClass relationships by recursively applying to named sub classes to get a complete set of classes in the descendant chain of this class
72
+ # For a Term: yield or return inferred subClass relationships by recursively applying to named sub classes to get a complete set of classes in the descendant chain of this class
73
+ # For a Statement: this is a no-op, as it's not useful in this context
49
74
  # @private
50
75
  def _entail_subClass
51
- return Array(self) unless class?
52
- descendant_cache[self] ||= begin
53
- (Array(self.subClass).map {|c| c._entail_subClass rescue c}.flatten + Array(self)).compact
76
+ case self
77
+ when RDF::Vocabulary::Term
78
+ unless class?
79
+ yield self if block_given?
80
+ return Array(self)
81
+ end
82
+ terms = descendant_cache[self] ||= (
83
+ Array(self.subClass).
84
+ map {|c| c._entail_subClass rescue c}.
85
+ flatten +
86
+ Array(self)
87
+ ).compact
88
+ terms.each {|t| yield t} if block_given?
89
+ terms
90
+ else []
54
91
  end
55
92
  end
56
93
 
@@ -58,7 +95,6 @@ module RDF::Reasoner
58
95
  # Get the immediate subclasses of this class.
59
96
  #
60
97
  # This iterates over terms defined in the vocabulary of this term, as well as the vocabularies imported by this vocabulary.
61
-
62
98
  # @return [Array<RDF::Vocabulary::Term>]
63
99
  def subClass
64
100
  raise RDF::Reasoner::Error, "#{self} Can't entail subClass" unless class?
@@ -68,12 +104,70 @@ module RDF::Reasoner
68
104
  end
69
105
 
70
106
  ##
71
- # Return inferred subPropertyOf relationships by recursively applying to named super classes to get a complete set of classes in the ancestor chain of this class
107
+ # For a Term: yield or return inferred subPropertyOf relationships by recursively applying to named super classes to get a complete set of classes in the ancestor chain of this class
108
+ # For a Statement: yield or return inferred statements having a subPropertyOf relationship to predicate of this statement
72
109
  # @private
73
110
  def _entail_subPropertyOf
74
- return Array(self) unless property? && respond_to?(:subPropertyOf)
75
- subPropertyOf_cache[self] ||= begin
76
- (Array(self.subPropertyOf).map {|c| c._entail_subPropertyOf rescue c}.flatten + Array(self)).compact
111
+ case self
112
+ when RDF::Vocabulary::Term
113
+ unless property? && respond_to?(:subPropertyOf)
114
+ yield self if block_given?
115
+ return Array(self)
116
+ end
117
+ terms = subPropertyOf_cache[self] ||= (
118
+ Array(self.subPropertyOf).
119
+ map {|c| c._entail_subPropertyOf rescue c}.
120
+ flatten +
121
+ Array(self)
122
+ ).compact
123
+ terms.each {|t| yield t} if block_given?
124
+ terms
125
+ when RDF::Statement
126
+ statements = []
127
+ if term = (RDF::Vocabulary.find_term(self.predicate) rescue nil)
128
+ term._entail_subPropertyOf do |t|
129
+ statements << RDF::Statement.new(self.to_hash.merge(predicate: t))
130
+ end
131
+ end
132
+ statements.each {|s| yield s} if block_given?
133
+ statements
134
+ else []
135
+ end
136
+ end
137
+
138
+ ##
139
+ # For a Statement: yield or return inferred statements having an rdf:type of the domain of the statement predicate
140
+ # @private
141
+ def _entail_domain
142
+ case self
143
+ when RDF::Statement
144
+ statements = []
145
+ if term = (RDF::Vocabulary.find_term(self.predicate) rescue nil)
146
+ term.domain.each do |t|
147
+ statements << RDF::Statement.new(self.to_hash.merge(predicate: RDF.type, object: t))
148
+ end
149
+ end
150
+ statements.each {|s| yield s} if block_given?
151
+ statements
152
+ else []
153
+ end
154
+ end
155
+
156
+ ##
157
+ # For a Statement: if object is a resource, yield or return inferred statements having an rdf:type of the range of the statement predicate
158
+ # @private
159
+ def _entail_range
160
+ case self
161
+ when RDF::Statement
162
+ statements = []
163
+ if object.resource? && term = (RDF::Vocabulary.find_term(self.predicate) rescue nil)
164
+ term.range.each do |t|
165
+ statements << RDF::Statement.new(self.to_hash.merge(subject: self.object, predicate: RDF.type, object: t))
166
+ end
167
+ end
168
+ statements.each {|s| yield s} if block_given?
169
+ statements
170
+ else []
77
171
  end
78
172
  end
79
173
 
@@ -95,7 +189,7 @@ module RDF::Reasoner
95
189
  # Fully entailed types of the resource
96
190
  types = options.fetch(:types) do
97
191
  queryable.query(:subject => resource, :predicate => RDF.type).
98
- map {|s| (t = RDF::Vocabulary.find_term(s.object)) && t.entail(:subClassOf)}.
192
+ map {|s| (t = (RDF::Vocabulary.find_term(s.object)) rescue nil) && t.entail(:subClassOf)}.
99
193
  flatten.
100
194
  uniq.
101
195
  compact
@@ -138,7 +232,7 @@ module RDF::Reasoner
138
232
  # Fully entailed types of the resource
139
233
  types = options.fetch(:types) do
140
234
  queryable.query(:subject => resource, :predicate => RDF.type).
141
- map {|s| (t = RDF::Vocabulary.find_term(s.object)) && t.entail(:subClassOf)}.
235
+ map {|s| (t = (RDF::Vocabulary.find_term(s.object) rescue nil)) && t.entail(:subClassOf)}.
142
236
  flatten.
143
237
  uniq.
144
238
  compact
@@ -161,9 +255,20 @@ module RDF::Reasoner
161
255
  mod.add_entailment :subClassOf, :_entail_subClassOf
162
256
  mod.add_entailment :subClass, :_entail_subClass
163
257
  mod.add_entailment :subPropertyOf, :_entail_subPropertyOf
258
+ mod.add_entailment :domain, :_entail_domain
259
+ mod.add_entailment :range, :_entail_range
164
260
  end
165
261
  end
166
262
 
167
- # Extend the Term with this methods
263
+ # Extend Term with these methods
168
264
  ::RDF::Vocabulary::Term.send(:include, RDFS)
265
+
266
+ # Extend Statement with these methods
267
+ ::RDF::Statement.send(:include, RDFS)
268
+
269
+ # Extend Enumerable with these methods
270
+ ::RDF::Enumerable.send(:include, RDFS)
271
+
272
+ # Extend Mutable with these methods
273
+ ::RDF::Mutable.send(:include, RDFS)
169
274
  end
@@ -47,7 +47,7 @@ module RDF::Reasoner
47
47
  # Fully entailed types of the resource
48
48
  types = options.fetch(:types) do
49
49
  queryable.query(:subject => resource, :predicate => RDF.type).
50
- map {|s| (t = RDF::Vocabulary.find_term(s.object)) && t.entail(:subClassOf)}.
50
+ map {|s| (t = (RDF::Vocabulary.find_term(s.object) rescue nil)) && t.entail(:subClassOf)}.
51
51
  flatten.
52
52
  uniq.
53
53
  compact
@@ -147,7 +147,7 @@ module RDF::Reasoner
147
147
  # Fully entailed types of the resource
148
148
  types = options.fetch(:types) do
149
149
  queryable.query(:subject => resource, :predicate => RDF.type).
150
- map {|s| (t = RDF::Vocabulary.find_term(s.object)) && t.entail(:subClassOf)}.
150
+ map {|s| (t = (RDF::Vocabulary.find_term(s.object) rescue nil)) && t.entail(:subClassOf)}.
151
151
  flatten.
152
152
  uniq.
153
153
  compact
metadata CHANGED
@@ -1,117 +1,132 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf-reasoner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Gregg Kellogg
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2014-08-27 00:00:00.000000000 Z
12
+ date: 2014-11-11 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: rdf
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
- - - "~>"
19
+ - - ~>
18
20
  - !ruby/object:Gem::Version
19
21
  version: '1.1'
20
- - - ">="
22
+ - - ! '>='
21
23
  - !ruby/object:Gem::Version
22
24
  version: 1.1.4.2
23
25
  type: :runtime
24
26
  prerelease: false
25
27
  version_requirements: !ruby/object:Gem::Requirement
28
+ none: false
26
29
  requirements:
27
- - - "~>"
30
+ - - ~>
28
31
  - !ruby/object:Gem::Version
29
32
  version: '1.1'
30
- - - ">="
33
+ - - ! '>='
31
34
  - !ruby/object:Gem::Version
32
35
  version: 1.1.4.2
33
36
  - !ruby/object:Gem::Dependency
34
37
  name: rdf-xsd
35
38
  requirement: !ruby/object:Gem::Requirement
39
+ none: false
36
40
  requirements:
37
- - - "~>"
41
+ - - ~>
38
42
  - !ruby/object:Gem::Version
39
43
  version: '1.1'
40
44
  type: :runtime
41
45
  prerelease: false
42
46
  version_requirements: !ruby/object:Gem::Requirement
47
+ none: false
43
48
  requirements:
44
- - - "~>"
49
+ - - ~>
45
50
  - !ruby/object:Gem::Version
46
51
  version: '1.1'
47
52
  - !ruby/object:Gem::Dependency
48
53
  name: rdf-turtle
49
54
  requirement: !ruby/object:Gem::Requirement
55
+ none: false
50
56
  requirements:
51
- - - "~>"
57
+ - - ~>
52
58
  - !ruby/object:Gem::Version
53
59
  version: '1.1'
54
60
  type: :runtime
55
61
  prerelease: false
56
62
  version_requirements: !ruby/object:Gem::Requirement
63
+ none: false
57
64
  requirements:
58
- - - "~>"
65
+ - - ~>
59
66
  - !ruby/object:Gem::Version
60
67
  version: '1.1'
61
68
  - !ruby/object:Gem::Dependency
62
69
  name: linkeddata
63
70
  requirement: !ruby/object:Gem::Requirement
71
+ none: false
64
72
  requirements:
65
- - - "~>"
73
+ - - ~>
66
74
  - !ruby/object:Gem::Version
67
75
  version: '1.1'
68
76
  type: :development
69
77
  prerelease: false
70
78
  version_requirements: !ruby/object:Gem::Requirement
79
+ none: false
71
80
  requirements:
72
- - - "~>"
81
+ - - ~>
73
82
  - !ruby/object:Gem::Version
74
83
  version: '1.1'
75
84
  - !ruby/object:Gem::Dependency
76
85
  name: equivalent-xml
77
86
  requirement: !ruby/object:Gem::Requirement
87
+ none: false
78
88
  requirements:
79
- - - "~>"
89
+ - - ~>
80
90
  - !ruby/object:Gem::Version
81
91
  version: '0.4'
82
92
  type: :development
83
93
  prerelease: false
84
94
  version_requirements: !ruby/object:Gem::Requirement
95
+ none: false
85
96
  requirements:
86
- - - "~>"
97
+ - - ~>
87
98
  - !ruby/object:Gem::Version
88
99
  version: '0.4'
89
100
  - !ruby/object:Gem::Dependency
90
101
  name: rspec
91
102
  requirement: !ruby/object:Gem::Requirement
103
+ none: false
92
104
  requirements:
93
- - - "~>"
105
+ - - ~>
94
106
  - !ruby/object:Gem::Version
95
107
  version: '3.0'
96
108
  type: :development
97
109
  prerelease: false
98
110
  version_requirements: !ruby/object:Gem::Requirement
111
+ none: false
99
112
  requirements:
100
- - - "~>"
113
+ - - ~>
101
114
  - !ruby/object:Gem::Version
102
115
  version: '3.0'
103
116
  - !ruby/object:Gem::Dependency
104
117
  name: yard
105
118
  requirement: !ruby/object:Gem::Requirement
119
+ none: false
106
120
  requirements:
107
- - - "~>"
121
+ - - ~>
108
122
  - !ruby/object:Gem::Version
109
123
  version: '0.8'
110
124
  type: :development
111
125
  prerelease: false
112
126
  version_requirements: !ruby/object:Gem::Requirement
127
+ none: false
113
128
  requirements:
114
- - - "~>"
129
+ - - ~>
115
130
  - !ruby/object:Gem::Version
116
131
  version: '0.8'
117
132
  description: Reasons over RDFS/OWL vocabularies to generate statements which are entailed
@@ -127,35 +142,36 @@ files:
127
142
  - README.md
128
143
  - UNLICENSE
129
144
  - VERSION
130
- - lib/rdf/reasoner.rb
131
145
  - lib/rdf/reasoner/extensions.rb
132
146
  - lib/rdf/reasoner/owl.rb
133
147
  - lib/rdf/reasoner/rdfs.rb
134
148
  - lib/rdf/reasoner/schema.rb
135
149
  - lib/rdf/reasoner/version.rb
150
+ - lib/rdf/reasoner.rb
136
151
  homepage: http://github.com/gkellogg/rdf-reasoner
137
152
  licenses:
138
153
  - Public Domain
139
- metadata: {}
140
154
  post_install_message:
141
155
  rdoc_options: []
142
156
  require_paths:
143
157
  - lib
144
158
  required_ruby_version: !ruby/object:Gem::Requirement
159
+ none: false
145
160
  requirements:
146
- - - ">="
161
+ - - ! '>='
147
162
  - !ruby/object:Gem::Version
148
163
  version: 1.9.3
149
164
  required_rubygems_version: !ruby/object:Gem::Requirement
165
+ none: false
150
166
  requirements:
151
- - - ">="
167
+ - - ! '>='
152
168
  - !ruby/object:Gem::Version
153
169
  version: '0'
154
170
  requirements: []
155
171
  rubyforge_project:
156
- rubygems_version: 2.2.2
172
+ rubygems_version: 1.8.23.2
157
173
  signing_key:
158
- specification_version: 4
174
+ specification_version: 3
159
175
  summary: RDFS/OWL Reasoner for RDF.rb
160
176
  test_files: []
161
177
  has_rdoc: false
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: f6a98adc5516082d31f711b399c152f140a02bb5
4
- data.tar.gz: 39fa2fc036816834e1bdf676ed4d7ad0898cce28
5
- SHA512:
6
- metadata.gz: 729ae98dd83b486197f0eb73d9465116dd40aa16d385b05c94c1228fa6ed0f882aeb85f84b0b492a0c1ba5931d231147cc36cce2a9c2c166d5af547971324ea3
7
- data.tar.gz: 3c0a07eefe32d2e5e4afd983546e667028c48c050fde90cbd67ddaf9ea46ad6bc64ef143ea73db2078840eafbb27b4bd7ca5b6f7ff566ad5852705e605f71e79