rdf-reasoner 0.1.0 → 0.2.0

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