activerdf_rules 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,9 @@
1
+ module ActiveRDFRules #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ require 'activerdf_rules/rule_base'
2
+ require 'activerdf_rules/rule_engine'
3
+ require 'activerdf_rules/rule_engine_mixin'