activerdf_rules 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ 0.0.1 - Initial version of the project. Pretty much everything is functional,
2
+ but not necessarily perfect.
@@ -1,13 +1,20 @@
1
+ CHANGELOG.txt
1
2
  History.txt
2
3
  Manifest.txt
3
4
  README.txt
4
5
  Rakefile
5
- setup.rb
6
+ TODO.txt
6
7
  examples/person_example.rb
7
8
  lib/activerdf_rules.rb
8
9
  lib/activerdf_rules/rule_base.rb
9
- lib/activerdf_rules/rule_engine_mixin.rb
10
10
  lib/activerdf_rules/rule_engine.rb
11
+ lib/activerdf_rules/rule_engine_mixin.rb
11
12
  lib/activerdf_rules/version.rb
12
- test/test_helper.rb
13
+ test/activerdf_rules/owl_rule_base_test.rb
14
+ test/activerdf_rules/rdfs_ext_rule_base_test.rb
15
+ test/activerdf_rules/rdfs_rule_base_test.rb
16
+ test/activerdf_rules/rule_base_test.rb
17
+ test/activerdf_rules/rule_engine_mixin_test.rb
18
+ test/activerdf_rules/rule_engine_test.rb
13
19
  test/activerdf_rules_test.rb
20
+ test/test_helper.rb
data/README.txt CHANGED
@@ -2,22 +2,17 @@
2
2
  ActiveRDF Rules is a rule base and forward chaining production system for
3
3
  ActiveRDF. It can be used to process rules programatically at will, or using a
4
4
  mix-in class you can have it process rules every time a triple is added to the
5
- data store. The rules are defined using a DSL, and stored in memory. It comes
5
+ data store. The rules are stored in memory, and can be defined using a DSL. It comes
6
6
  with a couple of prebuilt rule bases for RDF Schema and OWL reasoning.
7
7
 
8
8
  =What it is not?
9
9
  * It is not high performance.
10
10
  * The rules are stored in memory, so don't add a lot of them.
11
- * It supports *some* basic RDF Schema and OWL reasoning through the built-in
11
+ * It supports RDF Schema and *some* basic OWL reasoning through the built-in
12
12
  rule bases, but they are *basic*. Feel free to not use them and just define
13
13
  your own.
14
14
  * It does not support restrictions. I'm probably going to add restriction
15
- rules at some point, but I haven't thought through it all just yet. Also, I'm
16
- not sure how to handle some cases. Right now, for instance, if a property has
17
- a domain specified, then it will conclude that any object that has that
18
- property must be of the type specified as the domain, as opposed to restricting
19
- only that type to be able to have that property. That's the way I was told to
20
- do it!
15
+ rules at some point, but I haven't thought through it all just yet.
21
16
  * It is not stable. I mean it's stable; I have tested it pretty thoroghly as
22
17
  far as its current features go, but I reserve the right to change the
23
18
  implementation, interface, and anything else. I might even change the name of
@@ -0,0 +1 @@
1
+ Add the RDF Semantics Extensional rules
@@ -3,7 +3,7 @@
3
3
  # base.
4
4
  #
5
5
  # Example:
6
- # rb = RuleBase.new do
6
+ # rb = RuleBase.new 'DSL RuleBase' do
7
7
  # rule 'Rule1' do
8
8
  # condition :x, :y, :z
9
9
  #
@@ -21,7 +21,7 @@
21
21
  # the +rules+ array manually
22
22
  #
23
23
  # Example:
24
- # rb = RuleBase.new 'DSLRuleBase'
24
+ # rb = RuleBase.new 'Manual RuleBase'
25
25
  # r = RuleBase::Rule.new 'Rule 1'
26
26
  # r.condition :x, :y, :z
27
27
  # r.conclusion :z, :y, :x
@@ -85,7 +85,7 @@ class RuleBase
85
85
  # the conditions are satisfied, then the conclusion is introduced into the
86
86
  # database substituting for any variables that may appear in the conclusion.
87
87
  #
88
- # Variables are simply a ruby symbols and they can be used within both the
88
+ # Variables are simply ruby symbols and they can be used within both the
89
89
  # conditions and the conclusion with the following caveat: any variable that
90
90
  # appears in the conclusion must also appear in at least one condition.
91
91
  #
@@ -154,19 +154,24 @@ class RuleBase
154
154
 
155
155
  change = false
156
156
  q.execute.each do |row|
157
- triple = @conclusion.collect {|t|
158
- instantiate(t, row, lookup)
159
- }
160
- old = ConnectionPool.write_adapter.size
161
- $activerdflog.debug("concluding #{triple.inspect}")
162
- ConnectionPool.write_adapter.add(*triple)
163
- # TODO HACK This should be changed
164
- # What I should be able to do is something like below, but the below is
165
- # broken for redland, and the above condition query is broken in
166
- # rdflite (because of ambiguous column names). For now this works.
167
- # Query.new.ask.where(*triple).execute
168
- if old < ConnectionPool.write_adapter.size
169
- change = true
157
+ triple = nil
158
+ begin
159
+ triple = @conclusion.collect {|t|
160
+ instantiate(t, row, lookup)
161
+ }
162
+ old = ConnectionPool.write_adapter.size
163
+ $activerdflog.debug("concluding #{triple.inspect}")
164
+ ConnectionPool.write_adapter.add(*triple)
165
+ # TODO HACK This should be changed
166
+ # What I should be able to do is something like below, but the below is
167
+ # broken for redland, and the above condition query is broken in
168
+ # rdflite (because of ambiguous column names). For now this works.
169
+ # Query.new.ask.where(*triple).execute
170
+ if old < ConnectionPool.write_adapter.size
171
+ change = true
172
+ end
173
+ rescue => e
174
+ $activerdflog.debug("could not add #{triple.inspect} because of #{e}")
170
175
  end
171
176
  end
172
177
  change
@@ -1,44 +1,40 @@
1
- # A RuleEngine will use a RuleBase to add new data to an ActiveRDF data store
2
- # by processing the rules.
3
- # TODO
1
+ # A RuleEngine will use a collection of RuleBases to add new data to an
2
+ # ActiveRDF data store by processing the rules.
4
3
  class RuleEngine
5
- attr_accessor :rule_base
4
+ attr_reader :rule_bases
6
5
 
7
6
  # A RuleBase can be specified using the hash key :rule_base.
8
7
  #
9
8
  # Example:
10
9
  # RuleEngine.new :rule_base => RuleEngine::RDFSRuleBase
11
10
  def initialize(values = {})
12
- @rule_base = values[:rule_base]
11
+ @rule_bases = []
12
+ @rule_bases << values[:rule_base] if values[:rule_base]
13
13
  end
14
14
 
15
+ # Creates a new RuleBase, adds it to the array of rule bases, and passes the
16
+ # block to the RuleBase constructor. This allows use of the RuleBase DSL.
15
17
  def define_rules(&b)
16
18
  rb = RuleBase.new(&b)
17
- self.rule_base = rb
18
- end
19
-
20
- def rule_base=(rb)
21
- if @rule_base.nil?
22
- @rule_base = rb.dup
23
- else
24
- if rb.nil?
25
- @rule_base = nil
26
- else
27
- @rule_base.rules.push(*rb.rules)
28
- end
29
- end
19
+ self.rule_bases << rb
30
20
  end
31
21
 
22
+ # Loops through all of the rule bases and processes each rule in each rule
23
+ # base. Returns true if any of the rules have added information to the
24
+ # database.
32
25
  def process_rules
33
26
  $activerdflog.debug("processing '#{@rule_base}'")
34
27
  change = false
35
- @rule_base.rules.each do |r|
36
- change |= r.process
28
+ @rule_bases.each do |rb|
29
+ rb.rules.each do |r|
30
+ change |= r.process
31
+ end
37
32
  end
38
33
  change
39
34
  end
40
35
 
41
- RDFSRuleBase = RuleBase.new 'RDFSRuleBase' do
36
+ # A rule base including rules for RDFS reasoning.
37
+ RDFSRuleBase = RuleBase.new('RDFSRuleBase') {
42
38
  type = Namespace.lookup(:rdf, 'type')
43
39
  rdfclass = Namespace.lookup(:rdfs, 'Class')
44
40
  subClassOf = Namespace.lookup(:rdfs, 'subClassOf')
@@ -46,64 +42,188 @@ class RuleEngine
46
42
  subPropertyOf = Namespace.lookup(:rdfs, 'subPropertyOf')
47
43
  range = Namespace.lookup(:rdfs, 'range')
48
44
  domain = Namespace.lookup(:rdfs, 'domain')
45
+ resource = Namespace.lookup(:rdfs, 'Resource')
46
+ container = Namespace.lookup(:rdfs, 'ContainerMembershipProperty')
47
+ member = Namespace.lookup(:rdfs, 'member')
48
+ datatype = Namespace.lookup(:rdfs, 'Datatype')
49
+ literal = Namespace.lookup(:rdfs, 'Literal')
49
50
 
50
- # The following reasoning rules where taken from
51
- # http://www-ksl.stanford.edu/software/jtp/doc/owl-reasoning.html
51
+ # The following reasoning rules were taken from the RDF Semantics document
52
+ # http://www.w3.org/TR/rdf-mt/#RDFRules
53
+ # http://www.w3.org/TR/rdf-mt/#RDFSRules
52
54
 
53
- # Type inheritance through rdfs:subclassOf
54
- rule do
55
- condition :Morris, type, :Cat
56
- condition :Cat, subClassOf, :Mammal
55
+ rule "rdf1" do
56
+ condition :s, :p, :o
57
57
 
58
- conclusion :Morris, type, :Mammal
58
+ conclusion :p, type, property
59
59
  end
60
60
 
61
- # Reflexivity of rdfs:subPropertyOf and rdfs:subclassOf
62
- rule do
61
+ rule 'rdfs2' do
62
+ condition :teaches, domain, :Teacher
63
+ condition :Bob, :teaches, :Scooter
64
+
65
+ conclusion :Bob, type, :Teacher
66
+ end
67
+
68
+ rule 'rdfs3' do
69
+ condition :teaches, range, :Student
70
+ condition :Bob, :teaches, :Scooter
71
+
72
+ conclusion :Scooter, type, :Student
73
+ end
74
+
75
+ rule 'rdfs4a' do
76
+ condition :s, :p, :o
77
+
78
+ conclusion :s, type, resource
79
+ end
80
+
81
+ rule 'rdfs4b' do
82
+ condition :s, :p, :o
83
+
84
+ conclusion :o, type, resource
85
+ end
86
+
87
+ rule 'rdfs5' do
88
+ condition :parent, subPropertyOf, :ancestor
89
+ condition :ancestor, subPropertyOf, :relative
90
+
91
+ conclusion :parent, subPropertyOf, :relative
92
+ end
93
+
94
+ rule 'rdfs6' do
63
95
  condition :p, type, property
64
96
 
65
97
  conclusion :p, subPropertyOf, :p
66
98
  end
67
99
 
68
- rule do
69
- condition :p, type, rdfclass
100
+ rule 'rdfs7' do
101
+ condition :parent, subPropertyOf, :ancestor
102
+ condition :Paul, :parent, :Lester
70
103
 
71
- conclusion :p, subClassOf, :p
104
+ conclusion :Paul, :ancestor, :Lester
72
105
  end
73
106
 
74
- # Type inference through rdfs:range and rdfs:domain constraints
75
- rule do
76
- condition :teaches, domain, :Teacher
77
- condition :Bob, :teaches, :Scooter
107
+ rule 'rdfs8' do
108
+ condition :c, type, rdfclass
78
109
 
79
- conclusion :Bob, type, :Teacher
110
+ conclusion :c, subClassOf, resource
80
111
  end
81
112
 
82
- rule do
83
- condition :teaches, range, :Student
84
- condition :Bob, :teaches, :Scooter
113
+ rule 'rdfs9' do
114
+ condition :Morris, type, :Cat
115
+ condition :Cat, subClassOf, :Mammal
85
116
 
86
- conclusion :Scooter, type, :Student
117
+ conclusion :Morris, type, :Mammal
87
118
  end
88
119
 
89
- # Transitivity of rdfs:subClassOf and rdfs:subPropertyOf
90
- rule do
120
+ rule 'rdfs10' do
121
+ condition :p, type, rdfclass
122
+
123
+ conclusion :p, subClassOf, :p
124
+ end
125
+
126
+ rule 'rdfs11' do
91
127
  condition :Dog, subClassOf, :Mammal
92
128
  condition :Mammal, subClassOf, :Animal
93
129
 
94
130
  conclusion :Dog, subClassOf, :Animal
95
131
  end
96
132
 
97
- rule do
98
- condition :parent, subPropertyOf, :ancestor
99
- condition :ancestor, subPropertyOf, :relative
133
+ rule 'rdfs12' do
134
+ condition :p, type, container
100
135
 
101
- conclusion :parent, subPropertyOf, :relative
136
+ conclusion :p, subClassOf, member
102
137
  end
103
- end.freeze
104
- RDFSRuleBase.rules.freeze
138
+
139
+ rule 'rdfs13' do
140
+ condition :p, type, datatype
141
+
142
+ conclusion :p, subClassOf, literal
143
+ end
144
+ }
145
+
146
+ # A rule base including rules for "stronger extensional semantic conditions"
147
+ # according to the RDF Semantics document.
148
+ RDFSExtRuleBase = RuleBase.new('RDFSExtRuleBase') {
149
+ domain = Namespace.lookup(:rdfs, 'domain')
150
+ subClassOf = Namespace.lookup(:rdfs, 'subClassOf')
151
+ range = Namespace.lookup(:rdfs, 'range')
152
+ subPropertyOf = Namespace.lookup(:rdfs, 'subPropertyOf')
153
+ type = Namespace.lookup(:rdf, 'type')
154
+ resource = Namespace.lookup(:rdfs, 'Resource')
155
+ property = Namespace.lookup(:rdf, 'Property')
156
+ rdfsclass = Namespace.lookup(:rdfs, 'Class')
157
+
158
+ # The following reasoning rules were taken from the RDF Semantics document
159
+ # http://www.w3.org/TR/rdf-mt/#RDFSExtRules
160
+
161
+ rule 'ext1' do
162
+ condition :uuu, domain, :vvv
163
+ condition :vvv, subClassOf, :zzz
164
+
165
+ conclusion :uuu, domain, :zzz
166
+ end
167
+
168
+ rule 'ext2' do
169
+ condition :uuu, range, :vvv
170
+ condition :vvv, subClassOf, :zzz
171
+
172
+ conclusion :uuu, range, :zzz
173
+ end
174
+
175
+ rule 'ext3' do
176
+ condition :uuu, domain, :vvv
177
+ condition :www, subPropertyOf, :uuu
178
+
179
+ conclusion :www, domain, :vvv
180
+ end
181
+
182
+ rule 'ext4' do
183
+ condition :uuu, range, :vvv
184
+ condition :www, subPropertyOf, :uuu
185
+
186
+ conclusion :www, range, :vvv
187
+ end
188
+
189
+ rule 'ext5' do
190
+ condition type, subPropertyOf, :www
191
+ condition :www, domain, :vvv
192
+
193
+ conclusion resource, subClassOf, :vvv
194
+ end
195
+
196
+ rule 'ext6' do
197
+ condition subClassOf, subPropertyOf, :www
198
+ condition :www, domain, :vvv
199
+
200
+ conclusion rdfsclass, subClassOf, :vvv
201
+ end
202
+
203
+ rule 'ext7' do
204
+ condition subPropertyOf, subPropertyOf, :www
205
+ condition :www, domain, :vvv
206
+
207
+ conclusion property, subClassOf, :vvv
208
+ end
209
+
210
+ rule 'ext8' do
211
+ condition subClassOf, subPropertyOf, :www
212
+ condition :www, range, :vvv
213
+
214
+ conclusion rdfsclass, subClassOf, :vvv
215
+ end
216
+
217
+ rule 'ext9' do
218
+ condition subPropertyOf, subPropertyOf, :www
219
+ condition :www, range, :vvv
220
+
221
+ conclusion property, subClassOf, :vvv
222
+ end
223
+ }
105
224
 
106
- OWLRuleBase = RuleBase.new 'OWLRuleBase' do
225
+ # A preliminary (alpha) rule base for OWL reasoning.
226
+ OWLRuleBase = RuleBase.new('OWLRuleBase') {
107
227
  type = Namespace.lookup(:rdf, 'type')
108
228
  transProp = Namespace.lookup(:owl, 'TransitiveProperty')
109
229
  symProp = Namespace.lookup(:owl, 'SymmetricProperty')
@@ -262,6 +382,14 @@ class RuleEngine
262
382
  # owl:TransitiveProperty, then Q is also an owl:TransitiveProperty.
263
383
 
264
384
  # TODO All of the elements of an owl:AllDifferent are owl:differentFrom each other.
265
- end.freeze
385
+ }
386
+
387
+ RDFSRuleBase.freeze
388
+ RDFSRuleBase.rules.freeze
389
+
390
+ RDFSExtRuleBase.freeze
391
+ RDFSExtRuleBase.rules.freeze
392
+
393
+ OWLRuleBase.freeze
266
394
  OWLRuleBase.rules.freeze
267
395
  end
@@ -1,5 +1,7 @@
1
1
  # This module may be included in any writeable ActiveRDF adapter in order to
2
- # have automatic rule processing every time a triple is added to the database.
2
+ # have automatic rule processing every time a triple is added to the database,
3
+ # and it will continue to process the rules until none of the rules will add
4
+ # new information to the database.
3
5
  #
4
6
  # Example:
5
7
  # class << adapter
@@ -11,15 +13,17 @@
11
13
  # mixed in. If you do not specify a RuleEngine, then automatic rule processing
12
14
  # will NOT take place.
13
15
  #
14
- # There are two included RuleBases one for RDF Schema, and one for OWL.
16
+ # There are three included RuleBases two for RDF Schema, and one for OWL.
15
17
  module RuleEngineMixin
16
- def self.included(base)
18
+ def self.included(base)#:nodoc:
17
19
  base.class_eval do
18
20
  attr_accessor :rule_engine
19
21
  attr_accessor :disable_rules
20
22
  alias _add add
21
23
 
22
- #TODO
24
+ # Adding a triple to the database will trigger rule processing and it
25
+ # will continue to process rules until none of the rules will add new
26
+ # information to the database.
23
27
  def add(*a)
24
28
  r = _add(*a)
25
29
  unless @disable_rules || @rule_engine.nil?
@@ -2,7 +2,7 @@ module ActiveRDFRules #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- TINY = 1
5
+ TINY = 2
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end