activerdf_rules 0.0.1
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/History.txt +0 -0
- data/Manifest.txt +13 -0
- data/README.txt +33 -0
- data/Rakefile +49 -0
- data/examples/person_example.rb +67 -0
- data/lib/activerdf_rules/rule_base.rb +195 -0
- data/lib/activerdf_rules/rule_engine.rb +267 -0
- data/lib/activerdf_rules/rule_engine_mixin.rb +34 -0
- data/lib/activerdf_rules/version.rb +9 -0
- data/lib/activerdf_rules.rb +3 -0
- data/setup.rb +1585 -0
- data/test/activerdf_rules/owl_rulebase_test.rb +234 -0
- data/test/activerdf_rules/rdfs_rulebase_test.rb +95 -0
- data/test/activerdf_rules/rule_base_test.rb +103 -0
- data/test/activerdf_rules/rule_engine_mixin_test.rb +19 -0
- data/test/activerdf_rules/rule_engine_test.rb +59 -0
- data/test/activerdf_rules_test.rb +11 -0
- data/test/test_helper.rb +11 -0
- metadata +71 -0
data/History.txt
ADDED
File without changes
|
data/Manifest.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.txt
|
4
|
+
Rakefile
|
5
|
+
setup.rb
|
6
|
+
examples/person_example.rb
|
7
|
+
lib/activerdf_rules.rb
|
8
|
+
lib/activerdf_rules/rule_base.rb
|
9
|
+
lib/activerdf_rules/rule_engine_mixin.rb
|
10
|
+
lib/activerdf_rules/rule_engine.rb
|
11
|
+
lib/activerdf_rules/version.rb
|
12
|
+
test/test_helper.rb
|
13
|
+
test/activerdf_rules_test.rb
|
data/README.txt
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
=What it is?
|
2
|
+
ActiveRDF Rules is a rule base and forward chaining production system for
|
3
|
+
ActiveRDF. It can be used to process rules programatically at will, or using a
|
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
|
6
|
+
with a couple of prebuilt rule bases for RDF Schema and OWL reasoning.
|
7
|
+
|
8
|
+
=What it is not?
|
9
|
+
* It is not high performance.
|
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
|
12
|
+
rule bases, but they are *basic*. Feel free to not use them and just define
|
13
|
+
your own.
|
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!
|
21
|
+
* It is not stable. I mean it's stable; I have tested it pretty thoroghly as
|
22
|
+
far as its current features go, but I reserve the right to change the
|
23
|
+
implementation, interface, and anything else. I might even change the name of
|
24
|
+
the project. If you're that concerned, then fork the code, and keep a copy for
|
25
|
+
yourself. However, I will do my best to make sure that the implementation and
|
26
|
+
interface evolve in a way that it breaks as little existing code as possible.
|
27
|
+
|
28
|
+
=What next?
|
29
|
+
Take a look at the documentation. Especially look at the person_example.rb
|
30
|
+
file. It contains a pretty heavily annotated example. If you have (or will)
|
31
|
+
install this as a gem, then look for that in the gem directory
|
32
|
+
(/usr/lib/ruby/gems/1.8/gems/activerdf-rules-X.X/examples) or some such
|
33
|
+
nonsense.
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/rubyforgepublisher'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'hoe'
|
11
|
+
include FileUtils
|
12
|
+
require File.join(File.dirname(__FILE__), 'lib', 'activerdf_rules', 'version')
|
13
|
+
|
14
|
+
AUTHOR = "pjstadig" # can also be an array of Authors
|
15
|
+
EMAIL = "paul@stadig.name"
|
16
|
+
DESCRIPTION = "A rulebase and forward chaining production system for activerdf databases"
|
17
|
+
GEM_NAME = "activerdf_rules" # what ppl will type to install your gem
|
18
|
+
RUBYFORGE_PROJECT = "activerdf-rules" # The unix name for your project
|
19
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
20
|
+
RELEASE_TYPES = %w( gem ) # can use: gem, tar, zip
|
21
|
+
|
22
|
+
|
23
|
+
NAME = "activerdf_rules"
|
24
|
+
REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
25
|
+
VERS = ENV['VERSION'] || (ActiveRDFRules::VERSION::STRING + (REV ? ".#{REV}" : ""))
|
26
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
27
|
+
RDOC_OPTS = ['--quiet', '--title', "activerdf_rules documentation",
|
28
|
+
"--opname", "index.html",
|
29
|
+
"--line-numbers",
|
30
|
+
"--main", "README",
|
31
|
+
"--inline-source"]
|
32
|
+
|
33
|
+
# Generate all the Rake tasks
|
34
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
35
|
+
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
36
|
+
p.author = AUTHOR
|
37
|
+
p.description = DESCRIPTION
|
38
|
+
p.email = EMAIL
|
39
|
+
p.summary = DESCRIPTION
|
40
|
+
p.url = HOMEPATH
|
41
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
42
|
+
p.test_globs = ["test/**/*_test.rb"]
|
43
|
+
p.clean_globs = CLEAN #An array of file patterns to delete on clean.
|
44
|
+
|
45
|
+
# == Optional
|
46
|
+
#p.changes - A description of the release's latest changes.
|
47
|
+
#p.extra_deps - An array of rubygem dependencies.
|
48
|
+
#p.spec_extras - A hash of extra values to set in the gemspec.
|
49
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#=== Initial Setup Code
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_rdf'
|
4
|
+
require 'activerdf_rules'
|
5
|
+
|
6
|
+
adapter = ConnectionPool.add_data_source :type => :redland
|
7
|
+
Namespace.register(:test, 'http://test.com/')
|
8
|
+
adapter.add(Namespace.lookup(:test, 'Person'), Namespace.lookup(:rdf, 'type'), Namespace.lookup(:rdfs, 'Class'))
|
9
|
+
adapter.add(Namespace.lookup(:test, 'eye'), Namespace.lookup(:rdf, 'type'), Namespace.lookup(:rdf, 'Property'))
|
10
|
+
adapter.add(Namespace.lookup(:test, 'eye'), Namespace.lookup(:rdfs, 'domain'), Namespace.lookup(:test, 'Person'))
|
11
|
+
adapter.add(Namespace.lookup(:test, 'eye'), Namespace.lookup(:rdfs, 'range'), Namespace.lookup(:test, 'Color'))
|
12
|
+
ObjectManager.construct_classes
|
13
|
+
|
14
|
+
#=== Here is the real example
|
15
|
+
# First we add couple of resources to the database.
|
16
|
+
paul = TEST::Person.new Namespace.expand(:test, 'Paul')
|
17
|
+
paul.save
|
18
|
+
|
19
|
+
blue = Namespace.lookup(:test, 'blue')
|
20
|
+
blue.save
|
21
|
+
|
22
|
+
# Now we set paul's eye property to blue.
|
23
|
+
paul.eye = blue
|
24
|
+
|
25
|
+
# The next line should simple read '[RDFS::Resource]'.
|
26
|
+
p blue.type
|
27
|
+
|
28
|
+
# Now we'll see how you can use the rule engine to manually process rules.
|
29
|
+
re = RuleEngine.new :rule_base => RuleEngine::RDFSRuleBase
|
30
|
+
|
31
|
+
# Calling process_rules once will try every rule once.
|
32
|
+
re.process_rules
|
33
|
+
|
34
|
+
# Calling process_rules in a loop will process all of the rules until nothing
|
35
|
+
# more can be concluded. This works because process_rules returns true if it
|
36
|
+
# added new information.
|
37
|
+
while re.process_rules; end
|
38
|
+
|
39
|
+
# Now we can see that blue's type will include TEST::Color because one of the
|
40
|
+
# RDFS rules fired.
|
41
|
+
p blue.type
|
42
|
+
|
43
|
+
#=== Example two
|
44
|
+
# Say you wanted to have the rule engine automatically process all of the rules
|
45
|
+
# every time a new triple is added to the database. There are two ways of
|
46
|
+
# doing this: first, you can re-open the class and include the mix-in
|
47
|
+
#
|
48
|
+
# class RedlandAdapter
|
49
|
+
# include RuleEngineMixin
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# Second, you can just add the mix-in to an objects meta-class like below, if
|
53
|
+
# for example you don't want every instance of the adapter to have rule
|
54
|
+
# processing.
|
55
|
+
class << adapter
|
56
|
+
include RuleEngineMixin
|
57
|
+
end
|
58
|
+
adapter.rule_engine = RuleEngine.new :rule_base => RuleEngine::RDFSRuleBase
|
59
|
+
red = Namespace.lookup(:test, 'red')
|
60
|
+
red.save
|
61
|
+
|
62
|
+
p red.type
|
63
|
+
paul.eye = red
|
64
|
+
|
65
|
+
# Now by just adding the triple to the database, we automatically have our
|
66
|
+
# rules fire.
|
67
|
+
p red.type
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# An instance of the RuleBase class contains a set of rules for processing with
|
2
|
+
# the RuleEngine. It has a built in DSL that can be used to create a rule
|
3
|
+
# base.
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
# rb = RuleBase.new do
|
7
|
+
# rule 'Rule1' do
|
8
|
+
# condition :x, :y, :z
|
9
|
+
#
|
10
|
+
# conclusion :z, :y, :x
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# rule 'Rule2' do
|
14
|
+
# condition :x, :y, :z
|
15
|
+
#
|
16
|
+
# conclusion :y, :x, :z
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# Additionally, you can create an instance of RuleBase and just add rules to
|
21
|
+
# the +rules+ array manually
|
22
|
+
#
|
23
|
+
# Example:
|
24
|
+
# rb = RuleBase.new 'DSLRuleBase'
|
25
|
+
# r = RuleBase::Rule.new 'Rule 1'
|
26
|
+
# r.condition :x, :y, :z
|
27
|
+
# r.conclusion :z, :y, :x
|
28
|
+
# rb.rules << r
|
29
|
+
#
|
30
|
+
# As you can see from these examples you can create variables in your rules by
|
31
|
+
# using symbols.
|
32
|
+
#
|
33
|
+
# There is no point in adding more than one rule that has the same conditions
|
34
|
+
# and conclusion (i.e. they do the same thing), because it will just mean that
|
35
|
+
# the RuleEngine will process an extra rule with no extra benefit.
|
36
|
+
#
|
37
|
+
# Currently, RuleBase does not verify that you are adding unique rules.
|
38
|
+
# However, it does verify that the conclusion and all of the conditions are
|
39
|
+
# triples. It also verifies that the subject and predicate of the conclusion
|
40
|
+
# and every condition are either variables or resources. Additionally, it only
|
41
|
+
# allows you to set one conclusion. If any of these conditions is not met,
|
42
|
+
# then it will throw an exception.
|
43
|
+
#
|
44
|
+
# When using the class as a DSL it will also check each rule as it is added to
|
45
|
+
# ensure that all of the variables that appear in the conclusion also appear in
|
46
|
+
# at least one condition. If this condition is not met it will throw an
|
47
|
+
# exception. When adding rules manually to the +rules+ array it will not
|
48
|
+
# perform this check. However, later on when the RuleEngine converts the rule
|
49
|
+
# to a query ActiveRDF will throw an exception if this condition is not met.
|
50
|
+
class RuleBase
|
51
|
+
attr_accessor :name, :rules
|
52
|
+
|
53
|
+
# Any block that is passed in will be instance_eval'ed, which will allow you
|
54
|
+
# to use the built in DSL.
|
55
|
+
def initialize(name = nil, &b)
|
56
|
+
@name = name || 'NoName'
|
57
|
+
@rules = []
|
58
|
+
instance_eval(&b) if b
|
59
|
+
end
|
60
|
+
|
61
|
+
# Used in the DSL method to add a rule to this rule base.
|
62
|
+
def rule(name = nil, &b)
|
63
|
+
r = Rule.new name || "Rule #{@rules.size + 1}"
|
64
|
+
r.instance_eval(&b)
|
65
|
+
r.validate
|
66
|
+
@rules << r
|
67
|
+
$activerdflog.debug("Adding '#{r}' to '#{self}'")
|
68
|
+
end
|
69
|
+
|
70
|
+
# Makes a deep copy of the rule base, including the +rules+ array.
|
71
|
+
def dup
|
72
|
+
rb = super
|
73
|
+
rb.rules = self.rules.dup
|
74
|
+
rb
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the name of the rule base
|
78
|
+
def to_s
|
79
|
+
@name
|
80
|
+
end
|
81
|
+
|
82
|
+
# A rule for adding information to an ActiveRDF store. A rule consists of a
|
83
|
+
# set of conditions and a single conclusion. Each condition consists of a
|
84
|
+
# triple (with variables) that is matched against the database. When all of
|
85
|
+
# the conditions are satisfied, then the conclusion is introduced into the
|
86
|
+
# database substituting for any variables that may appear in the conclusion.
|
87
|
+
#
|
88
|
+
# Variables are simply a ruby symbols and they can be used within both the
|
89
|
+
# conditions and the conclusion with the following caveat: any variable that
|
90
|
+
# appears in the conclusion must also appear in at least one condition.
|
91
|
+
#
|
92
|
+
# Example:
|
93
|
+
# r = RuleBase::Rule.new
|
94
|
+
# r.condition :x, :y, :z
|
95
|
+
# r.conclusion :z, :y, :x
|
96
|
+
class Rule
|
97
|
+
attr_reader :name, :conditions
|
98
|
+
|
99
|
+
def initialize(name)
|
100
|
+
@name = name
|
101
|
+
@conditions = []
|
102
|
+
@conclusion = nil
|
103
|
+
end
|
104
|
+
|
105
|
+
# Adds a condition to the rule where s, p, and o are the subject,
|
106
|
+
# predicate, and object (respectively).
|
107
|
+
def condition(s, p, o)
|
108
|
+
raise "Condition subject #{s} must be a Symbol or an RDFS::Resource" unless [Symbol, RDFS::Resource].include?(s.class)
|
109
|
+
raise "Condition subject #{p} must be a Symbol or an RDFS::Resource" unless [Symbol, RDFS::Resource].include?(p.class)
|
110
|
+
c = [s, p, o]
|
111
|
+
@conditions << c
|
112
|
+
$activerdflog.debug("Adding condition #{c.inspect} to '#{self}'")
|
113
|
+
end
|
114
|
+
|
115
|
+
# Sets the conclusion of the rule where s, p, and o are the subject,
|
116
|
+
# predicate, and object (respectively).
|
117
|
+
def conclusion(s, p, o)
|
118
|
+
raise "Conclusion already set" unless @conclusion.nil?
|
119
|
+
raise "Conclusion subject #{s} must be a Symbol or an RDFS::Resource" unless [Symbol, RDFS::Resource].include?(s.class)
|
120
|
+
raise "Conclusion subject #{p} must be a Symbol or an RDFS::Resource" unless [Symbol, RDFS::Resource].include?(p.class)
|
121
|
+
c = [s, p, o]
|
122
|
+
@conclusion = c
|
123
|
+
$activerdflog.debug("Setting conclusion #{c.inspect} for '#{self}'")
|
124
|
+
end
|
125
|
+
|
126
|
+
# Checks that every variable in the conclusion also appears in at least on
|
127
|
+
# condition. If this condition is not satisfied, then an exception is
|
128
|
+
# thrown.
|
129
|
+
def validate
|
130
|
+
# for each conclusion variable verify that it appears in one of the conditions
|
131
|
+
@conclusion.each do |x|
|
132
|
+
if x.is_a?(Symbol)
|
133
|
+
catch(:done) do
|
134
|
+
@conditions.each do |c|
|
135
|
+
throw :done if c.include?(x)
|
136
|
+
end
|
137
|
+
raise "Unbound variable :#{x} in conclusion"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Processes this rule by converting it to a query, if the query is
|
144
|
+
# successful, then the conclusion is introduced into the data store after
|
145
|
+
# substituting for any variables.
|
146
|
+
def process
|
147
|
+
$activerdflog.debug("processing '#{self}'")
|
148
|
+
q = Query.new
|
149
|
+
lookup = @conclusion.select{|c| c.is_a?(Symbol)}.uniq
|
150
|
+
q.select(*lookup)
|
151
|
+
@conditions.each do |c|
|
152
|
+
q.where(*c)
|
153
|
+
end
|
154
|
+
|
155
|
+
change = false
|
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
|
170
|
+
end
|
171
|
+
end
|
172
|
+
change
|
173
|
+
end
|
174
|
+
|
175
|
+
# Returns either the value for +var+ by using +lookup+, the value of +row+,
|
176
|
+
# if the query resulted in a single value, or the value of +var+ since in
|
177
|
+
# this case it must be either an +RDFS::Resource+ or a literal.
|
178
|
+
def instantiate(var, row, lookup)
|
179
|
+
if var.is_a?(Symbol)
|
180
|
+
if lookup.size == 1 && !row.is_a?(Array)
|
181
|
+
row
|
182
|
+
else
|
183
|
+
row[lookup.index(var)]
|
184
|
+
end
|
185
|
+
else
|
186
|
+
var
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Returns the name of this rule
|
191
|
+
def to_s
|
192
|
+
@name
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,267 @@
|
|
1
|
+
# A RuleEngine will use a RuleBase to add new data to an ActiveRDF data store
|
2
|
+
# by processing the rules.
|
3
|
+
# TODO
|
4
|
+
class RuleEngine
|
5
|
+
attr_accessor :rule_base
|
6
|
+
|
7
|
+
# A RuleBase can be specified using the hash key :rule_base.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
# RuleEngine.new :rule_base => RuleEngine::RDFSRuleBase
|
11
|
+
def initialize(values = {})
|
12
|
+
@rule_base = values[:rule_base]
|
13
|
+
end
|
14
|
+
|
15
|
+
def define_rules(&b)
|
16
|
+
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
|
30
|
+
end
|
31
|
+
|
32
|
+
def process_rules
|
33
|
+
$activerdflog.debug("processing '#{@rule_base}'")
|
34
|
+
change = false
|
35
|
+
@rule_base.rules.each do |r|
|
36
|
+
change |= r.process
|
37
|
+
end
|
38
|
+
change
|
39
|
+
end
|
40
|
+
|
41
|
+
RDFSRuleBase = RuleBase.new 'RDFSRuleBase' do
|
42
|
+
type = Namespace.lookup(:rdf, 'type')
|
43
|
+
rdfclass = Namespace.lookup(:rdfs, 'Class')
|
44
|
+
subClassOf = Namespace.lookup(:rdfs, 'subClassOf')
|
45
|
+
property = Namespace.lookup(:rdf, 'Property')
|
46
|
+
subPropertyOf = Namespace.lookup(:rdfs, 'subPropertyOf')
|
47
|
+
range = Namespace.lookup(:rdfs, 'range')
|
48
|
+
domain = Namespace.lookup(:rdfs, 'domain')
|
49
|
+
|
50
|
+
# The following reasoning rules where taken from
|
51
|
+
# http://www-ksl.stanford.edu/software/jtp/doc/owl-reasoning.html
|
52
|
+
|
53
|
+
# Type inheritance through rdfs:subclassOf
|
54
|
+
rule do
|
55
|
+
condition :Morris, type, :Cat
|
56
|
+
condition :Cat, subClassOf, :Mammal
|
57
|
+
|
58
|
+
conclusion :Morris, type, :Mammal
|
59
|
+
end
|
60
|
+
|
61
|
+
# Reflexivity of rdfs:subPropertyOf and rdfs:subclassOf
|
62
|
+
rule do
|
63
|
+
condition :p, type, property
|
64
|
+
|
65
|
+
conclusion :p, subPropertyOf, :p
|
66
|
+
end
|
67
|
+
|
68
|
+
rule do
|
69
|
+
condition :p, type, rdfclass
|
70
|
+
|
71
|
+
conclusion :p, subClassOf, :p
|
72
|
+
end
|
73
|
+
|
74
|
+
# Type inference through rdfs:range and rdfs:domain constraints
|
75
|
+
rule do
|
76
|
+
condition :teaches, domain, :Teacher
|
77
|
+
condition :Bob, :teaches, :Scooter
|
78
|
+
|
79
|
+
conclusion :Bob, type, :Teacher
|
80
|
+
end
|
81
|
+
|
82
|
+
rule do
|
83
|
+
condition :teaches, range, :Student
|
84
|
+
condition :Bob, :teaches, :Scooter
|
85
|
+
|
86
|
+
conclusion :Scooter, type, :Student
|
87
|
+
end
|
88
|
+
|
89
|
+
# Transitivity of rdfs:subClassOf and rdfs:subPropertyOf
|
90
|
+
rule do
|
91
|
+
condition :Dog, subClassOf, :Mammal
|
92
|
+
condition :Mammal, subClassOf, :Animal
|
93
|
+
|
94
|
+
conclusion :Dog, subClassOf, :Animal
|
95
|
+
end
|
96
|
+
|
97
|
+
rule do
|
98
|
+
condition :parent, subPropertyOf, :ancestor
|
99
|
+
condition :ancestor, subPropertyOf, :relative
|
100
|
+
|
101
|
+
conclusion :parent, subPropertyOf, :relative
|
102
|
+
end
|
103
|
+
end.freeze
|
104
|
+
RDFSRuleBase.rules.freeze
|
105
|
+
|
106
|
+
OWLRuleBase = RuleBase.new 'OWLRuleBase' do
|
107
|
+
type = Namespace.lookup(:rdf, 'type')
|
108
|
+
transProp = Namespace.lookup(:owl, 'TransitiveProperty')
|
109
|
+
symProp = Namespace.lookup(:owl, 'SymmetricProperty')
|
110
|
+
inverseOf = Namespace.lookup(:owl, 'inverseOf')
|
111
|
+
disjointWith = Namespace.lookup(:owl, 'disjointWith')
|
112
|
+
subClassOf = Namespace.lookup(:rdfs, 'subClassOf')
|
113
|
+
sameAs = Namespace.lookup(:owl, 'sameAs')
|
114
|
+
rdfclass = Namespace.lookup(:rdfs, 'Class')
|
115
|
+
equivClass = Namespace.lookup(:owl, 'equivalentClass')
|
116
|
+
complementOf = Namespace.lookup(:owl, 'complementOf')
|
117
|
+
funcProp = Namespace.lookup(:owl, 'FunctionalProperty')
|
118
|
+
invFuncProp = Namespace.lookup(:owl, 'InverseFunctionalProperty')
|
119
|
+
onProperty = Namespace.lookup(:owl, 'onProperty')
|
120
|
+
hasValue = Namespace.lookup(:owl, 'hasValue')
|
121
|
+
allValuesFrom = Namespace.lookup(:owl, 'allValuesFrom')
|
122
|
+
someValuesFrom = Namespace.lookup(:owl, 'someValuesFrom')
|
123
|
+
|
124
|
+
# The following reasoning rules where taken from
|
125
|
+
# http://www-ksl.stanford.edu/software/jtp/doc/owl-reasoning.html
|
126
|
+
|
127
|
+
# Enforcing transitivity of owl:TransitiveProperty.
|
128
|
+
rule do
|
129
|
+
condition :ancestor, type, transProp
|
130
|
+
condition :Sue, :ancestor, :Mary
|
131
|
+
condition :Mary, :ancestor, :Anne
|
132
|
+
|
133
|
+
conclusion :Sue, :ancestor, :Anne
|
134
|
+
end
|
135
|
+
|
136
|
+
# Semantics of owl:SymmetricProperty is enforced.
|
137
|
+
rule do
|
138
|
+
condition :relative, type, symProp
|
139
|
+
condition :Sue, :relative, :Mary
|
140
|
+
|
141
|
+
conclusion :Mary, :relative, :Sue
|
142
|
+
end
|
143
|
+
|
144
|
+
# Reasoning with owl:inverseOf.
|
145
|
+
rule do
|
146
|
+
condition :parentOf, inverseOf, :hasParent
|
147
|
+
condition :Goldie, :parentOf, :Kate
|
148
|
+
|
149
|
+
conclusion :Kate, :hasParent, :Goldie
|
150
|
+
end
|
151
|
+
|
152
|
+
# Inheritance of disjointness constraints.
|
153
|
+
rule do
|
154
|
+
condition :Plant, disjointWith, :Animal
|
155
|
+
condition :Mammal, subClassOf, :Animal
|
156
|
+
|
157
|
+
conclusion :Plant, disjointWith, :Mammal
|
158
|
+
end
|
159
|
+
|
160
|
+
# When an owl:sameAs relationship is asserted or inferred between two
|
161
|
+
# entities that are known to be classes, an owl:equivalentClass
|
162
|
+
# relationship is inferred between the classes. Similarly, when an
|
163
|
+
# owl:sameAs relationship is asserted or inferred between two entities that
|
164
|
+
# are known to be properties, an owl:equivalentProperty relationship is
|
165
|
+
# inferred between the classes.
|
166
|
+
rule do
|
167
|
+
condition :Human, sameAs, :Person
|
168
|
+
condition :Human, type, rdfclass
|
169
|
+
condition :Person, type, rdfclass
|
170
|
+
|
171
|
+
conclusion :Human, equivClass, :Person
|
172
|
+
end
|
173
|
+
|
174
|
+
# All the subclasses of a given class are disjoint with the class's
|
175
|
+
# complement.
|
176
|
+
rule do
|
177
|
+
condition :Animal, complementOf, :NonAnimals
|
178
|
+
condition :Mammal, subClassOf, :Animal
|
179
|
+
|
180
|
+
conclusion :Mammal, disjointWith, :NonAnimals
|
181
|
+
end
|
182
|
+
|
183
|
+
# A complicated bit of reasoning about owl:complementOf captured by
|
184
|
+
# following KIF axiom.
|
185
|
+
rule do
|
186
|
+
condition :c1, complementOf, :c2
|
187
|
+
condition :c3, subClassOf, :c1
|
188
|
+
condition :c4, subClassOf, :c2
|
189
|
+
condition :c4, complementOf, :c5
|
190
|
+
|
191
|
+
conclusion :c3, subClassOf, :c5
|
192
|
+
end
|
193
|
+
|
194
|
+
# Inferring owl:sameAs relationships via owl:FunctionalProperty and
|
195
|
+
# owl:InverseFunctionalProperty.
|
196
|
+
rule do
|
197
|
+
condition :mother, type, funcProp
|
198
|
+
condition :Joe, :mother, :Margaret
|
199
|
+
condition :Joe, :mother, :Maggie
|
200
|
+
|
201
|
+
conclusion :Margaret, sameAs, :Maggie
|
202
|
+
end
|
203
|
+
|
204
|
+
rule do
|
205
|
+
condition :motherOf, type, invFuncProp
|
206
|
+
condition :Margaret, :motherOf, :Joe
|
207
|
+
condition :Maggie, :motherOf, :Joe
|
208
|
+
|
209
|
+
conclusion :Margaret, sameAs, :Maggie
|
210
|
+
end
|
211
|
+
|
212
|
+
# TODO If a class A is owl:oneOf a list of objects, say X, Y, and Z, then
|
213
|
+
# each of X, Y, and Z has rdf:type A.
|
214
|
+
|
215
|
+
# If an object is rdf:type an owl:hasValue owl:Restriction, then the object
|
216
|
+
# has the specified value for the specified property.
|
217
|
+
rule do
|
218
|
+
condition :RestrictionOrangeSkin, onProperty, :skinColor
|
219
|
+
condition :RestrictionOrangeSkin, hasValue, :Orange
|
220
|
+
condition :MrOompaLoompa, type, :RestrictionOrangeSkin
|
221
|
+
|
222
|
+
conclusion :MrOompaLoompa, :skinColor, :Orange
|
223
|
+
end
|
224
|
+
|
225
|
+
# If an owl:hasValue owl:Restriction restricts a particular property to a
|
226
|
+
# particular value, and an object has that value for that property, then
|
227
|
+
# the object has the Restriction as a type.
|
228
|
+
rule do
|
229
|
+
condition :RestrictionOrangeSkin, onProperty, :skinColor
|
230
|
+
condition :RestrictionOrangeSkin, hasValue, :Orange
|
231
|
+
condition :MrOompaLoompa, :skinColor, :Orange
|
232
|
+
|
233
|
+
conclusion :MrOompaLoompa, type, :RestrictionOrangeSkin
|
234
|
+
end
|
235
|
+
|
236
|
+
# If an object is a rdf:type an owl:allValuesFrom owl:Restriction, and the
|
237
|
+
# object has values for the specified property, then the values are of the
|
238
|
+
# specified type.
|
239
|
+
rule do
|
240
|
+
condition :RestrictionCatChildren, onProperty, :child
|
241
|
+
condition :RestrictionCatChildren, allValuesFrom, :Cat
|
242
|
+
condition :Fluffy, type, :RestrictionCatChildren
|
243
|
+
condition :Fluffy, :child, :Cupcake
|
244
|
+
|
245
|
+
conclusion :Cupcake, type, :Cat
|
246
|
+
end
|
247
|
+
|
248
|
+
# If an owl:someValuesFrom owl:Restriction restricts a particular property
|
249
|
+
# to a particular type, and if an object has some values of the specificied
|
250
|
+
# type for the specified property, then that object has the Restriction as
|
251
|
+
# a type.
|
252
|
+
rule do
|
253
|
+
condition :RestrictionIvyLeagueDegree, onProperty, :degree
|
254
|
+
condition :RestrictionIvyLeagueDegree, someValuesFrom, :IvyLeagueSchool
|
255
|
+
condition :Mary, :degree, :Harvard
|
256
|
+
condition :Harvard, type, :IvyLeagueSchool
|
257
|
+
|
258
|
+
conclusion :Mary, type, :RestrictionIvyLeagueDegree
|
259
|
+
end
|
260
|
+
|
261
|
+
# TODO If a property Q is owl:inverseOf of a property P, and P is an
|
262
|
+
# owl:TransitiveProperty, then Q is also an owl:TransitiveProperty.
|
263
|
+
|
264
|
+
# TODO All of the elements of an owl:AllDifferent are owl:differentFrom each other.
|
265
|
+
end.freeze
|
266
|
+
OWLRuleBase.rules.freeze
|
267
|
+
end
|
@@ -0,0 +1,34 @@
|
|
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.
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
# class << adapter
|
6
|
+
# include RuleEngineMixin
|
7
|
+
# end
|
8
|
+
# adapter.rule_engine = RuleEngine.new :rule_base => RuleEngine::RDFSRuleBase
|
9
|
+
#
|
10
|
+
# Notice that you must set the +rule_engine+ attribute after the module is
|
11
|
+
# mixed in. If you do not specify a RuleEngine, then automatic rule processing
|
12
|
+
# will NOT take place.
|
13
|
+
#
|
14
|
+
# There are two included RuleBases one for RDF Schema, and one for OWL.
|
15
|
+
module RuleEngineMixin
|
16
|
+
def self.included(base)
|
17
|
+
base.class_eval do
|
18
|
+
attr_accessor :rule_engine
|
19
|
+
attr_accessor :disable_rules
|
20
|
+
alias _add add
|
21
|
+
|
22
|
+
#TODO
|
23
|
+
def add(*a)
|
24
|
+
r = _add(*a)
|
25
|
+
unless @disable_rules || @rule_engine.nil?
|
26
|
+
@disable_rules = true
|
27
|
+
while @rule_engine.process_rules; end
|
28
|
+
@disable_rules = false
|
29
|
+
end
|
30
|
+
r
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|