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 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'