class RDF::N3::Reasoner

A Notation-3/Turtle reasoner in Ruby

Takes either a parsed formula or an ‘RDF::Queryable` and updates it by reasoning over formula defined within the queryable.

@author [Gregg Kellogg](greggkellogg.net/)

Attributes

formula[R]

The top-level parsed formula, including builtins and variables. @return [RDF::N3::Algebra::Formula]

Public Class Methods

new(input, **options, &block) click to toggle source

Initializes a new reasoner. If input is an IO or string, it is taken as n3 source and parsed first. Otherwise, it is a parsed formula.

It returns the evaluated formula, or yields triples.

@example Initializing from a reader

reader = RDF::N3::Reader.new(":a :b :c .")
reasoner = RDF::N3::Reasoner.new(reader)
reasoner.each_triple {}

@example Initializing as a mutable

reasoner = RDF::N3::Reasoner.new do |r|
  r << RDF::N3::Reader.new(":a :b :c .")
end
reasoner.each_triple {}

@example Initializing with multiple inputs

reasoner = RDF::N3::Reasoner.new
RDF::NTriples::Reader.open("example.nt") {|r| reasoner << r}
RDF::N3::Reader.open("rules.n3") {|r| reasoner << r}
reasoner.each_triple {}

@param [RDF::Mutable] input (nil)

Input should be parsed N3 using native lists (see `:list_terms` option to {RDF::N3::Reader#initialize})

@param [Hash{Symbol => Object}] options @option options [#to_s] :base_uri (nil)

the base URI to use when resolving relative URIs (for acessing intermediate parser productions)

@yield [reasoner] ‘self` @yieldparam [RDF::N3::Reasoner] reasoner @yieldreturn [void] ignored @return [RDF::N3::Reasoner]

# File lib/rdf/n3/reasoner.rb, line 62
def initialize(input, **options, &block)
  @options = options.merge(strings: {}) # for --strings and log:outputString
  @mutable = case input
  when RDF::Mutable then input
  when RDF::Enumerable then RDF::N3::Repository.new {|r| r << input}
  else RDF::N3::Repository.new
  end

  @formula = input if input.is_a?(RDF::N3::Algebra::Formula)

  log_debug("reasoner: expression") {SXP::Generator.string(formula.to_sxp_bin)}

  if block_given?
    case block.arity
      when 0 then instance_eval(&block)
      else block.call(self)
    end
  end
end
open(file) click to toggle source

Opens a Notation-3 file, and parses it to initialize the reasoner

@param [String, to_s] file @yield [reasoner] ‘self` @yieldparam [RDF::N3::Reasoner] reasoner @yieldreturn [void] ignored @return [RDF::N3::Reasoner]

# File lib/rdf/n3/reasoner.rb, line 25
def self.open(file)
  RDF::N3::Reader.open(file, **options) do |reader|
    RDF::N3::Reasoner.new(reader, **options, &block)
  end
end

Public Instance Methods

conclusions(&block) click to toggle source

Yields conclusions, excluding formulae and those statements in the original dataset, or returns an enumerator over the conclusions

@overload conclusions

@yield  [statement]
  each statement
@yieldparam  [RDF::Statement] statement
@yieldreturn [void] ignored
@return [void]

@overload conclusions

@return [Enumerator<RDF::Statement>]

@return [RDF::Enumerator] @yield [statement] @yieldparam [RDF::Statement] statement

# File lib/rdf/n3/reasoner.rb, line 226
def conclusions(&block)
  if block_given?
    # Invoke {#each} in the containing class:
    each_statement {|s| block.call(s) if s.inferred?}
  end
  enum_conclusions
end
Also aliased as: each_conclusion
data(&block) click to toggle source

Yields data, excluding formulae or variables and statements referencing formulae or variables

@overload data

@yield  [statement]
  each statement
@yieldparam  [RDF::Statement] statement
@yieldreturn [void] ignored
@return [void]

@overload data

@return [Enumerator<RDF::Statement>]

@return [RDF::Enumerator] @yield [statement] @yieldparam [RDF::Statement] statement

# File lib/rdf/n3/reasoner.rb, line 184
def data(&block)
  if block_given?
    project_graph(nil) do |statement|
      block.call(statement) unless statement.variable? ||
                                  has_graph?(statement.subject) ||
                                  has_graph?(statement.object)
    end
  end
  enum_data
end
Also aliased as: each_datum
dup() click to toggle source

Returns a copy of this reasoner

# File lib/rdf/n3/reasoner.rb, line 84
def dup
  repo = RDF::N3::Repository.new {|r| r << @mutable}
  self.class.new(repo) do |reasoner|
    reasoner.instance_variable_set(:@options, @options.dup)
    reasoner.instance_variable_set(:@formula, @formula.dup) if @formula
  end
end
each(&block) click to toggle source

Yields each statement in the datastore

@yieldparam  [RDF::Statement] statement
@yieldreturn [void] ignored
@return [void]
# File lib/rdf/n3/reasoner.rb, line 165
def each(&block)
  @mutable.each(&block)
end
each_conclusion(&block)
Alias for: conclusions
each_datum(&block)
Alias for: data
enum_conclusions() click to toggle source

Returns an enumerator for {#conclusions}. FIXME: enum_for doesn’t seem to be working properly in JRuby 1.7, so specs are marked pending

@return [Enumerator<RDF::Statement>] @see each_statement

# File lib/rdf/n3/reasoner.rb, line 242
def enum_conclusions
  # Ensure that statements are queryable, countable and enumerable
  this = self
  RDF::Queryable::Enumerator.new do |yielder|
    this.send(:each_conclusion) {|y| yielder << y}
  end
end
enum_data() click to toggle source

Returns an enumerator for {#data}. FIXME: enum_for doesn’t seem to be working properly in JRuby 1.7, so specs are marked pending

@return [Enumerator<RDF::Statement>] @see each_statement

# File lib/rdf/n3/reasoner.rb, line 203
def enum_data
  # Ensure that statements are queryable, countable and enumerable
  this = self
  RDF::Queryable::Enumerator.new do |yielder|
    this.send(:each_datum) {|y| yielder << y}
  end
end
execute(**options, &block) click to toggle source

Updates the datastore by reasoning over the formula, optionally yielding each conclusion; uses triples from the graph associated with this formula as the dataset over which to reason.

@param [Hash{Symbol => Object}] options @option options [Boolean] :apply @option options [Boolean] :rules @option options [Boolean] :think @yield [statement] @yieldparam [RDF::Statement] statement @return [RDF::N3::Reasoner] ‘self`

# File lib/rdf/n3/reasoner.rb, line 112
def execute(**options, &block)
  @options[:logger] = options[:logger] if options.has_key?(:logger)

  # The knowledge base is the non-variable portions of formula
  knowledge_base = RDF::N3::Repository.new {|r| r << formula}
  log_debug("reasoner: knowledge_base") {SXP::Generator.string(knowledge_base.statements.to_sxp_bin)}

  # If thinking, continuously execute until results stop growing
  count = -1
  log_info("reasoner: start") { "count: #{count}"}
  solutions = RDF::Query::Solutions(RDF::Query::Solution.new)
  while knowledge_base.count > count
    log_info("reasoner: do") { "count: #{count}"}
    count = knowledge_base.count
    log_depth {formula.execute(knowledge_base, solutions: solutions, **options)}
    knowledge_base << formula
    solutions = RDF::Query::Solutions(RDF::Query::Solution.new) if solutions.empty?
    log_debug("reasoner: solutions") {SXP::Generator.string solutions.to_sxp_bin}
    log_debug("reasoner: datastore") {SXP::Generator.string knowledge_base.statements.to_sxp_bin}
    log_info("reasoner: inferred") {SXP::Generator.string knowledge_base.statements.select(&:inferred?).to_sxp_bin}
    log_info("reasoner: formula") do
      SXP::Generator.string RDF::N3::Algebra::Formula.from_enumerable(knowledge_base).to_sxp_bin
    end
    @formula = nil # cause formula to be re-calculated from knowledge-base
    unless options[:think]
      count = knowledge_base.count
      break
    end
  end
  log_info("reasoner: end") { "count: #{count}"}

  # Add updates back to mutable, containg builtins and variables.
  @mutable << knowledge_base

  each(&block) if block_given?
  self
end
Also aliased as: reason!
insert_statement(statement) click to toggle source

Inserts an RDF statement the datastore, resets ‘formula`.

@param [RDF::Statement] statement @return [void]

# File lib/rdf/n3/reasoner.rb, line 97
def insert_statement(statement)
  @formula = nil
  @mutable.insert_statement(statement)
end
reason(**options, &block) click to toggle source

Reason with results in a duplicate datastore

@see execute

# File lib/rdf/n3/reasoner.rb, line 155
def reason(**options, &block)
  self.dup.reason!(**options, &block)
end
reason!(**options, &block)
Alias for: execute
strings() click to toggle source

Returns the concatenated strings from log:outputString

@return [String]

# File lib/rdf/n3/reasoner.rb, line 254
def strings
  @options[:strings].
    sort_by {|k, v| k}.
    map {|(k,v)| v.join("")}.
    join("")
end
to_sxp_bin() click to toggle source

Returns the SPARQL S-Expression (SSE) representation of the parsed formula. Formulae are represented as subjects and objects in the containing graph, along with their universals and existentials

@return [Array] ‘self` @see openjena.org/wiki/SSE

# File lib/rdf/n3/reasoner.rb, line 277
def to_sxp_bin
  formula.to_sxp_bin
end