Class: RDF::N3::Reasoner

Inherits:
Object
  • Object
show all
Includes:
Enumerable, Mutable, Util::Logger
Defined in:
lib/rdf/n3/reasoner.rb

Overview

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:

Instance Attribute Summary collapse

Attributes included from Enumerable

#existentials, #universals

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input, **options) {|reasoner| ... } ⇒ RDF::N3::Reasoner

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.

Examples:

Initializing from a reader

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

Initializing as a mutable

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

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 {}

Parameters:

  • input (RDF::Mutable)

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

  • options (Hash{Symbol => Object})

Options Hash (**options):

  • :base_uri (#to_s) — default: nil

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

Yields:

  • (reasoner)

    ‘self`

Yield Parameters:

Yield Returns:

  • (void)

    ignored



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 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

Instance Attribute Details

#formulaRDF::N3::Algebra::Formula (readonly)

Returns the top-level formula for this file.

Transforms an RDF dataset into a recursive formula structure.



16
17
18
# File 'lib/rdf/n3/reasoner.rb', line 16

def formula
  @formula
end

Class Method Details

.open(file) {|reasoner| ... } ⇒ RDF::N3::Reasoner

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

Parameters:

  • file (String, #to_s)

Yields:

  • (reasoner)

    ‘self`

Yield Parameters:

Yield Returns:

  • (void)

    ignored

Returns:



25
26
27
28
29
# 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

Instance Method Details

#conclusions {|statement| ... } ⇒ void #conclusionsEnumerator<RDF::Statement> Also known as: each_conclusion

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

Overloads:

  • #conclusions {|statement| ... } ⇒ void

    This method returns an undefined value.

    Yields:

    • (statement)

      each statement

    Yield Parameters:

    Yield Returns:

    • (void)

      ignored

  • #conclusionsEnumerator<RDF::Statement>

    Returns:

Yields:

  • (statement)

Yield Parameters:

Returns:

  • (RDF::Enumerator)


226
227
228
229
230
231
232
# 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

#data {|statement| ... } ⇒ void #dataEnumerator<RDF::Statement> Also known as: each_datum

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

Overloads:

  • #data {|statement| ... } ⇒ void

    This method returns an undefined value.

    Yields:

    • (statement)

      each statement

    Yield Parameters:

    Yield Returns:

    • (void)

      ignored

  • #dataEnumerator<RDF::Statement>

    Returns:

Yields:

  • (statement)

Yield Parameters:

Returns:

  • (RDF::Enumerator)


184
185
186
187
188
189
190
191
192
193
# 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

#dupObject

Returns a copy of this reasoner



84
85
86
87
88
89
90
# 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) ⇒ Object

Yields each statement in the datastore

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


165
166
167
# File 'lib/rdf/n3/reasoner.rb', line 165

def each(&block)
  @mutable.each(&block)
end

#enum_conclusionsEnumerator<RDF::Statement>

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

Returns:

See Also:

  • #each_statement


242
243
244
245
246
247
248
# 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_dataEnumerator<RDF::Statement>

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

Returns:

See Also:

  • #each_statement


203
204
205
206
207
208
209
# 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) {|statement| ... } ⇒ RDF::N3::Reasoner Also known as: reason!

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.

Parameters:

  • options (Hash{Symbol => Object})

Options Hash (**options):

  • :apply (Boolean)
  • :rules (Boolean)
  • :think (Boolean)

Yields:

  • (statement)

Yield Parameters:

Returns:



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# 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

#insert_statement(statement) ⇒ void

This method returns an undefined value.

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

Parameters:



97
98
99
100
# File 'lib/rdf/n3/reasoner.rb', line 97

def insert_statement(statement)
  @formula = nil
  @mutable.insert_statement(statement)
end

#reason(**options, &block) ⇒ Object

Reason with results in a duplicate datastore

See Also:



155
156
157
# File 'lib/rdf/n3/reasoner.rb', line 155

def reason(**options, &block)
  self.dup.reason!(**options, &block)
end

#stringsString

Returns the concatenated strings from log:outputString

Returns:

  • (String)


254
255
256
257
258
259
# 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_binArray

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

Returns:

  • (Array)

    ‘self`

See Also:



277
278
279
# File 'lib/rdf/n3/reasoner.rb', line 277

def to_sxp_bin
  formula.to_sxp_bin
end