Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: ruby-rdf/rdfs
base: master
...
head fork: pius/rdfs
compare: master
Checking mergeability… Don't worry, you can still create the pull request.
  • 5 commits
  • 7 files changed
  • 0 commit comments
  • 1 contributor
View
3  README.md 100644 → 100755
@@ -71,10 +71,11 @@ Resources
* <http://rubyforge.org/projects/rdfs/>
* <http://raa.ruby-lang.org/project/rdfs/>
-Author
+Authors
------
* [Arto Bendiken](mailto:arto.bendiken@gmail.com) - <http://ar.to/>
+* [Pius Uzamere](mailto:pius@alum.mit.edu) - <http://pius.me/>
License
-------
View
11 Rakefile
@@ -1,15 +1,16 @@
require 'rubygems'
-require 'spec'
+require 'rspec'
+require 'rspec/core/rake_task'
require 'rake/clean'
-require 'spec/rake/spectask'
require 'pathname'
task :default => [ :spec ]
desc 'Run specifications'
-Spec::Rake::SpecTask.new(:spec) do |t|
- t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
- t.spec_files = Pathname.glob(Pathname.new(__FILE__).dirname + 'spec/**/*_spec.rb')
+
+RSpec::Core::RakeTask.new(:rcov) do |t|
+ t.rspec_opts = ["-c", "-f progress", "-r ./spec/spec_helper.rb"]
+ t.pattern = 'spec/**/*_spec.rb'
begin
t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
View
2  VERSION
@@ -1 +1 @@
-0.1.0
+0.3.0
View
168 lib/rdfs/rule.rb
@@ -1,10 +1,40 @@
+module RDF
+ class Statement
+ PLACEHOLDERS = (p = [:aaa, :bbb, :ccc, :ddd, :uuu, :vvv, :xxx, :yyy, :zzz]) + p.collect {|pl| RDF::Literal.new(pl)} + p.collect {|pl| RDF::Node.new(pl)}
+
+ #TODO: consider moving these methods into the RDF gem instead of reopening RDF::Statement here
+ def with_substitutions(assignment_hash)
+ return self unless assignment_hash
+ statement_hash = to_hash
+ [:subject, :object, :predicate].each { |place_in_statement|
+ bound_variables, variable = assignment_hash.keys, statement_hash[place_in_statement]
+ statement_hash[place_in_statement] = assignment_hash[variable] if bound_variables.collect(&:to_s).include?(variable.to_s)
+ #TODO: fix node equality so I don't need to use to_s above
+ }
+ Statement.new(statement_hash)
+ end
+
+ def generality
+ to_hash.values.select {|k| PLACEHOLDERS.include? k}.size
+ end
+
+ def has_placeholder?
+ to_hash.values.detect {|k| PLACEHOLDERS.include? k}
+ end
+
+ def specificity
+ 3-generality
+ end
+ end
+end
+
module RDFS
##
# An RDFS entailment rule.
class Rule
include RDF
- PLACEHOLDERS = (p = [:aaa, :bbb, :ccc, :ddd, :uuu, :vvv, :xxx, :yyy, :zzz]) + p.collect {|pl| RDF::Literal.new(pl)}
+ PLACEHOLDERS = (p = [:aaa, :bbb, :ccc, :ddd, :uuu, :vvv, :xxx, :yyy, :zzz]) + p.collect {|pl| RDF::Literal.new(pl)} + p.collect {|pl| RDF::Node.new(pl)}
# @return [Array<Statement>]
attr_reader :antecedents
@@ -33,103 +63,62 @@ def initialize(options = {}, &block)
end
end
end
-
-
- ##
- # Evaluates whether a rule pattern matches a set of statements.
- #
- # @param Statement statement1
- # @param Statement statement2
- #
- # All of the RDFS entailment rules are either pairwise or unitary on antecedents,
- # so Rule#match takes exactly one or two statements.
- #
- # @return [Array<Statement>], :consequents ([]) or nil
+
def match(statement1, statement2=nil, noisy = false)
- if (ss = [statement1, statement2].compact.size) != @antecedents.size
- if noisy
- return [nil, "antecedent size (#{@antecedents.size}) doesn't match the arguments size #{ss}"]
- else
- return nil
- end
- end
-
- if @antecedents.size == 1
- antecedent = @antecedents.first
- pattern, assignments, slots = antecedent.to_hash, {}, {}
+ statements = [statement1, statement2].compact
- pattern.each {|k,v| slots[k] = pattern.delete(k) if PLACEHOLDERS.include?(v) }
+ return false unless antecedents.size == statements.size
+ if antecedents.size == 1
+ return false unless (@subs = self.class.unitary_match(antecedents.first, statements.first))
+ return Rule.substitute(consequents, @subs)
- [:subject, :object, :predicate].select {|k| pattern[k].nil?}.each {|k|
- msv = slots[k]
- assignments[msv] = statement1.send(k)
- }
-
- pattern = Statement.new(pattern)
- ad_hoc_repo = RDF::Repository.new.insert(statement1)
- if ad_hoc_repo.query(pattern).empty?
- if noisy
- return [nil, "pattern was #{pattern.inspect} and did not match #{statement1.inspect}"]
- else
- return nil
- end
- end
- return consequents_from(assignments)
+ elsif (implied_assignments = Rule.unitary_match(antecedents_ordered_by_decreasing_specificity.first, statements.first))
+ q = Rule.unitary_match(antecedents_ordered_by_decreasing_specificity.last.with_substitutions(implied_assignments),
+ statements.last.with_substitutions(implied_assignments))
+ assignments = q ? q.merge(implied_assignments) : q
+ return Rule.substitute(consequents, assignments)
+ elsif implied_assignments = Rule.unitary_match(antecedents_ordered_by_decreasing_specificity.first, statements.last)
+ q = Rule.unitary_match(antecedents_ordered_by_decreasing_specificity.last.with_substitutions(implied_assignments),
+ statements.first.with_substitutions(implied_assignments))
+ assignments = q ? q.merge(implied_assignments) : q
+ return Rule.substitute(consequents, assignments)
else
- pattern1, pattern2 = @antecedents.collect(&:to_hash)
- slots, assignments, statement1_assignments, statement2_assignments = {}, {}, {}, {}
-
- pattern1.each {|k,v| (slots.merge!({"#{k}_1" => pattern1.delete(k)})) if PLACEHOLDERS.include?(v) }
- pattern2.each {|k,v| (slots.merge!({"#{k}_2" => pattern2.delete(k)})) if PLACEHOLDERS.include?(v) }
-
- pattern1, pattern2 = Statement.new(pattern1), Statement.new(pattern2)
- if (pattern1 === statement1) && (pattern2 === statement2)
-
- [:subject, :object, :predicate].select {|k| pattern1.to_hash[k].nil?}.each {|k|
- msv = slots["#{k.to_s}_1"]
- assignments[msv] = statement1.send k
- }
-
- [:subject, :object, :predicate].select {|k| pattern2.to_hash[k].nil?}.each {|k|
- msv = slots["#{k.to_s}_2"]
- assignments[msv] = statement2.send k
- }
- return consequents_from(assignments)
- elsif (pattern1 === statement2) && (pattern2 === statement1)
-
- [:subject, :object, :predicate].select {|k| pattern1.to_hash[k].nil?}.each {|k|
- msv = slots["#{k.to_s}_1"]
- assignments[msv] = statement2.send(k)
- }
-
- [:subject, :object, :predicate].select {|k| pattern2.to_hash[k].nil?}.each {|k|
- msv = slots["#{k.to_s}_2"]
- assignments[msv] = statement1.send(k)
- }
- return consequents_from(assignments)
- else
- if noisy
- return [nil, "pattern was #{pattern.inspect} and did not match #{[statement1, statement2].inspect}"]
- else
- return nil
- end
- end
+ return false
end
end
alias_method :[], :match
-
-
- def consequents_from(assignments)
- consequent_patterns = consequents.collect(&:to_hash)
- output = []
- consequent_patterns.each_with_index {|c,i|
- c.each {|k,v|
- (c[k] = assignments[v]; output << RDF::Statement.new(c)) if PLACEHOLDERS.include?(v) }
- }
- return output
+
+
+ #returns either false or the assignment hash of the match
+ def self.unitary_match(antecedent, statement)
+ a, s = antecedent.to_hash, statement.to_hash
+ #may need to exclude context
+ bound = {}
+ a.values.zip(s.values) {|antecedent_value, statement_value|
+ if PLACEHOLDERS.include?(antecedent_value) and !bound[antecedent_value]
+ bound[antecedent_value] = statement_value
+ elsif PLACEHOLDERS.include?(antecedent_value) and bound[antecedent_value]
+ return false unless bound[antecedent_value] == statement_value
+ else
+ return false unless antecedent_value == statement_value
+ end
+ }
+ return bound
end
+ def antecedents_ordered_by_decreasing_specificity
+ a ||= antecedents.sort_by(&:generality)
+ end
+
+ def self.substitute(consequents, assignment_hash)
+ return nil if assignment_hash.nil?
+ c = consequents.collect{|c| c.with_substitutions(assignment_hash)}
+ return c.detect(&:has_placeholder?) ? false : c
+
+ #perhaps add an integrity check to Rule to make sure that the consequents are fully substituted by the antecedents
+ end
+
##
# Defines an antecedent for this rule.
#
@@ -204,6 +193,5 @@ def self.constraint(types = {})
def self.consequent(s, p, o)
@@consequents[self] << RDF::Statement.new(s, p, o)
end
-
end
-end
+end
View
3  spec/spec.opts
@@ -1,3 +0,0 @@
---format specdoc
---colour
---backtrace
View
16 spec/spec_helper.rb
@@ -1,6 +1,8 @@
require 'rubygems'
require 'pathname'
require 'rdf'
+require 'rdfs'
+require 'rdfs/rule'
class Pathname
def /(path)
@@ -11,9 +13,21 @@ def /(path)
spec_dir_path = Pathname(__FILE__).dirname.expand_path
require spec_dir_path.parent + 'lib/rdfs'
-
+# # require fixture resources
+Dir[spec_dir_path + "lib/rdfs/*.rb"].each do |fixture_file|
+ require fixture_file
+end
# # require fixture resources
# Dir[spec_dir_path + "fixtures/*.rb"].each do |fixture_file|
# require fixture_file
# end
+
+
+require 'rspec'
+# optionally add autorun support
+#require 'rspec/autorun'
+
+Rspec.configure do |c|
+ c.mock_with :rspec
+end
View
110 spec/unit/rule_spec.rb
@@ -1,50 +1,104 @@
-require 'spec_helper'
+#require 'spec_helper'
require 'rdf'
+
+require 'rdfs'
+require 'rdfs/rule'
include RDF
-#include RDFS
+include ::RDFS::Semantics
+
+describe ::RDF::Statement do
+ it "should be able to substitute a mapping into itself" do
+ statement = Statement.new(:aaa, :xxx, FOAF.person)
+ mapping = {RDF::Node.new(:aaa) => 'rdf:friend', RDF::Node.new(:xxx) => 'rdf:knows'}
+ a = statement.with_substitutions(mapping)
+ a.should eql Statement.new('rdf:friend', 'rdf:knows', FOAF.person)
+ end
+
+ it "should know its specificity" do
+ a1 = Statement.new(:aaa, RDFS.domain, :xxx)
+ a2 = Statement.new(:uuu, :aaa, :yyy)
+
+ [a1, a2].collect(&:specificity).should == [1,0]
+ [a1, a2].sort_by(&:specificity).should == [a2,a1]
+ end
+
+end
+
describe ::RDFS::Rule do
+
before(:each) do
-
- class RDF1 < RDFS::Rule
- antecedent :uuu, :aaa, :yyy
- consequent :aaa, RDF.type, RDF.Property
- end
-
- class RDFS2 < RDFS::Rule
- antecedent :aaa, RDFS.domain, :xxx
- antecedent :uuu, :aaa, :yyy
- consequent :uuu, RDF.type, :xxx
- end
-
@rule1 = RDF1.new
@statement1 = Statement.new('joe:shmoe', 'rdf:jerk', 'schmuck')
- @matching_statements_1 = [@statement1]
+ @matching_statements_1 = [Statement.new('rdf:jerk', RDF.type, RDF.Property)]
@rule2 = RDFS2.new
- @statement2 = Statement.new('rdf:jerk', RDFS.domain, FOAF.person)
- @dummy_statement2 = Statement.new('rdf:jerk', RDF.type, 'schmuck')
- @matching_statements_2 = [@statement1, @statement2]
- @dummy_statements_2 = [@statement1, @dummy_statement2]
-
-
+ @statement2 = Statement.new('rdf:annoys', RDFS.domain, FOAF.person)
+ @statement3 = Statement.new('tom', 'rdf:annoys', 'jerry')
+ @statement4 = Statement.new('tom', RDF.type, FOAF.person)
+ end
+
+ it "should know its antecedents" do
+ @rule1.antecedents.should eql([Statement.new(:uuu, :aaa, :yyy)])
end
+ it "should know its consequents" do
+ @rule1.consequents.should eql([Statement.new(:aaa, RDF.type, RDF.Property)])
+ end
+
+ it "should be able to substitute a mapping into consequents" do
+ consequents = [Statement.new(:aaa, :xxx, FOAF.person)]
+ mapping = {RDF::Node.new(:aaa) => 'rdf:friend', RDF::Node.new(:xxx) => 'rdf:knows'}
+ a = ::RDFS::Rule.substitute(consequents, mapping)
+ a.should eql [Statement.new('rdf:friend', 'rdf:knows', FOAF.person)]
+ end
+
+ it "should be able to do unitary matches" do
+ antecedent = Statement.new :uuu, :aaa, :yyy
+ @statement1 = Statement.new('joe:shmoe', 'rdf:jerk', 'schmuck')
+ ::RDFS::Rule.unitary_match(antecedent, @statement1).should be_true
+ end
+
+ it "should not unitary match if non-placeholders are different" do
+ a1 = Statement.new(:aaa, RDFS.domain, :xxx)
+ s1 = Statement.new('rdf:annoys', RDFS.subPropertyOf, FOAF.person)
+ ::RDFS::Rule.unitary_match(a1, s1).should be_false
+ end
+
+
context "should generate consequents from pairs of statements that match the antecedents" do
- it "with just one antecedent" do
- @rule1[@statement1].should.eql? [Statement.new('rdf:jerk', RDF.type, RDF.property)]
+
+ it "with just one antecedent and one consequent" do
+ @rule1[@statement1].should eql(@matching_statements_1)
+
+ @rule_rdfs4a = RDFS4a.new
+ @statements_matching_rule_rdfs4a = [Statement.new('rdf:annoys', RDF.type, RDFS.Resource)]
+ @rule_rdfs4a[@statement2].should eql @statements_matching_rule_rdfs4a
+ end
+
+ it "matching should be commutative" do
+ @rule2[@statement2,@statement3].should eql @rule2[@statement3,@statement2]
end
+ it "with multiple antecedents and one consequent" do
+ @rule2 = RDFS2.new
+ @rule2[@statement2,@statement3].should eql([@statement4])
+ end
+
it "with multiple antecedents" do
- @rule2[*@matching_statements_2].should.eql? [Statement.new('joe:shmoe', RDF.type, FOAF.person)]
- @rule2[*(@matching_statements_2.reverse)].should.eql? [Statement.new('joe:shmoe', RDF.type, FOAF.person)]
+ @rule2[@statement2,@statement3].should eql [Statement.new('tom', RDF.type, FOAF.person)]
+ @rule2[@statement3,@statement2].should eql [Statement.new('tom', RDF.type, FOAF.person)]
end
+
end
-
+
context "should not generate consequents from pairs of statements that don't match the antecedents" do
it "with multiple antecedents" do
- @rule2[*@dummy_statements_2].should.eql? nil
- @rule2[*(@dummy_statements_2.reverse)].should.eql? nil
+ @rule2 = RDFS2.new
+
+ @statement2 = Statement.new('rdf:annoys', RDFS.domain, FOAF.person)
+ d = Statement.new('foo', 'bar', 'baz')
+ @rule2[@statement2,d].should be_false
end
end
end

No commit comments for this range

Something went wrong with that request. Please try again.