activerdf_rules 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +2 -0
- data/Manifest.txt +10 -3
- data/README.txt +3 -8
- data/TODO.txt +1 -0
- data/lib/activerdf_rules/rule_base.rb +21 -16
- data/lib/activerdf_rules/rule_engine.rb +180 -52
- data/lib/activerdf_rules/rule_engine_mixin.rb +8 -4
- data/lib/activerdf_rules/version.rb +1 -1
- data/test/activerdf_rules/{owl_rulebase_test.rb → owl_rule_base_test.rb} +6 -2
- data/test/activerdf_rules/rdfs_ext_rule_base_test.rb +127 -0
- data/test/activerdf_rules/rdfs_rule_base_test.rb +183 -0
- data/test/activerdf_rules/rule_engine_test.rb +14 -9
- metadata +15 -7
- data/setup.rb +0 -1585
- data/test/activerdf_rules/rdfs_rulebase_test.rb +0 -95
data/CHANGELOG.txt
ADDED
data/Manifest.txt
CHANGED
@@ -1,13 +1,20 @@
|
|
1
|
+
CHANGELOG.txt
|
1
2
|
History.txt
|
2
3
|
Manifest.txt
|
3
4
|
README.txt
|
4
5
|
Rakefile
|
5
|
-
|
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/
|
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
|
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
|
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.
|
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
|
data/TODO.txt
ADDED
@@ -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 '
|
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
|
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 =
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
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
|
-
|
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
|
-
@
|
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.
|
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
|
-
@
|
36
|
-
|
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
|
-
|
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
|
51
|
-
# http://www
|
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
|
-
|
54
|
-
|
55
|
-
condition :Morris, type, :Cat
|
56
|
-
condition :Cat, subClassOf, :Mammal
|
55
|
+
rule "rdf1" do
|
56
|
+
condition :s, :p, :o
|
57
57
|
|
58
|
-
conclusion :
|
58
|
+
conclusion :p, type, property
|
59
59
|
end
|
60
60
|
|
61
|
-
|
62
|
-
|
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 :
|
100
|
+
rule 'rdfs7' do
|
101
|
+
condition :parent, subPropertyOf, :ancestor
|
102
|
+
condition :Paul, :parent, :Lester
|
70
103
|
|
71
|
-
conclusion :
|
104
|
+
conclusion :Paul, :ancestor, :Lester
|
72
105
|
end
|
73
106
|
|
74
|
-
|
75
|
-
|
76
|
-
condition :teaches, domain, :Teacher
|
77
|
-
condition :Bob, :teaches, :Scooter
|
107
|
+
rule 'rdfs8' do
|
108
|
+
condition :c, type, rdfclass
|
78
109
|
|
79
|
-
conclusion :
|
110
|
+
conclusion :c, subClassOf, resource
|
80
111
|
end
|
81
112
|
|
82
|
-
rule do
|
83
|
-
condition :
|
84
|
-
condition :
|
113
|
+
rule 'rdfs9' do
|
114
|
+
condition :Morris, type, :Cat
|
115
|
+
condition :Cat, subClassOf, :Mammal
|
85
116
|
|
86
|
-
conclusion :
|
117
|
+
conclusion :Morris, type, :Mammal
|
87
118
|
end
|
88
119
|
|
89
|
-
|
90
|
-
|
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 :
|
99
|
-
condition :ancestor, subPropertyOf, :relative
|
133
|
+
rule 'rdfs12' do
|
134
|
+
condition :p, type, container
|
100
135
|
|
101
|
-
conclusion :
|
136
|
+
conclusion :p, subClassOf, member
|
102
137
|
end
|
103
|
-
|
104
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
#
|
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?
|