# Class: RDF::N3::Algebra::Formula

Inherits:
SPARQL::Algebra::Operator
show all
Includes:
Enumerable, Builtin, Term, SPARQL::Algebra::Query, SPARQL::Algebra::Update
Defined in:
vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb

## Overview

A Notation3 Formula combines a graph with a BGP query.

## Constant Summary collapse

NAME =
`:formula`

### Constants included from Util::Logger

Util::Logger::IOWrapper

### Constants included from SPARQL::Algebra::Expression

SPARQL::Algebra::Expression::PATTERN_PARENTS

## Instance Attribute Summary collapse

• Query to run against a queryable to determine if the formula matches the queryable.

#solutions

#operands

## Class Method Summary collapse

• Create a formula from an RDF::Enumerable (such as RDF::N3::Repository).

## Instance Method Summary collapse

• Duplicate this formula, recursively, renaming graph names using hash function.

• Distinguished vars in this formula.

• Yields each statement from this formula bound to previously determined solutions.

• Yields each pattern which is not a builtin.

• Evaluates the formula using the given variable `bindings` by cloning the formula replacing variables with their bindings recursively.

• Yields solutions from patterns and other operands.

• Existential vars in this formula.

• Returns `true` if `self` is a Formula.

• #graph_name ⇒ RDF::Resource (also: #to_uri)

Graph name associated with this formula.

• Assign a graph name to this formula.

• The formula hash is the hash of it's operands and graph_name.

• Statements memoizer, from the operands which are statements.

• Patterns memoizer, from the operands which are statements and not builtins.

• Non-statement operands memoizer.

• Undistinguished vars in this formula.

• Universal vars in this formula and sub-formulae.

• Return the variables contained within this formula.

#alias_method

## Constructor Details

This class inherits a constructor from SPARQL::Algebra::Operator

## Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class RDF::Enumerable

## Instance Attribute Details

### #query ⇒ RDF::Query

Query to run against a queryable to determine if the formula matches the queryable.

Returns:

 ``` 17 18 19``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 17 def query @query end```

## Class Method Details

### .from_enumerable(enumerable, **options) ⇒ RDF::N3::Algebra::Formula

Create a formula from an RDF::Enumerable (such as RDF::N3::Repository)

Parameters:

• enumerable
• options (Hash{Symbol => Object})

Returns:

 ``` 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 28 def self.from_enumerable(enumerable, **options) # SPARQL used for SSE and algebra functionality require 'sparql' unless defined?(:SPARQL) # Create formulae from statement graph_names formulae = {} enumerable.graph_names.unshift(nil).each do |graph_name| formulae[graph_name] = Formula.new(graph_name: graph_name, formulae: formulae, **options) end # Add patterns to appropiate formula based on graph_name, # and replace subject and object bnodes which identify # named graphs with those formula enumerable.each_statement do |statement| # A graph name indicates a formula. graph_name = statement.graph_name form = formulae[graph_name] # Map statement components to formulae, if necessary. statement = RDF::Statement.from(statement.to_a.map do |term| case term when RDF::Node term = if formulae[term] # Transform blank nodes denoting formulae into those formulae formulae[term] elsif graph_name # If we're in a quoted graph, transform blank nodes into undistinguished existential variables. term.to_ndvar(graph_name) else term end when RDF::N3::List # Transform blank nodes denoting formulae into those formulae term = term.transform {|t| t.node? ? formulae.fetch(t, t) : t} # If we're in a quoted graph, transform blank node components into existential variables if graph_name && term.has_nodes? term = term.to_ndvar(graph_name) end end term end) pattern = statement.variable? ? RDF::Query::Pattern.from(statement) : statement # Formulae may be the subject or object of a known operator if klass = RDF::N3::Algebra.for(pattern.predicate) form.operands << klass.new(pattern.subject, pattern.object, formulae: formulae, parent: form, predicate: pattern.predicate, **options) else pattern.graph_name = nil form.operands << pattern end end # Formula is that without a graph name this = formulae[nil] # If assigned a graph name, add it here this.graph_name = options[:graph_name] if options[:graph_name] this end```

## Instance Method Details

### #deep_dup ⇒ RDF::N3::Algebra::Formula

Duplicate this formula, recursively, renaming graph names using hash function.

Returns:

 ``` 99 100 101 102 103 104 105 106 107``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 99 def deep_dup #new_ops = operands.map(&:dup) new_ops = operands.map do |op| op.deep_dup end graph_name = RDF::Node.intern(new_ops.hash) log_debug("formula") {"dup: #{self.graph_name} to #{graph_name}"} self.class.new(*new_ops, **@options.merge(graph_name: graph_name, formulae: formulae)) end```

### #distinguished_vars ⇒ Array<RDF::Query::Variable]

Distinguished vars in this formula

Returns:

• Array<RDF::Query::Variable]

 ``` 402 403 404``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 402 def distinguished_vars @distinguished ||= vars.vars.select(&:distinguished?) end```

### #each(solutions: RDF::Query::Solutions(RDF::Query::Solution.new)) {|statement| ... } ⇒ Object

Yields each statement from this formula bound to previously determined solutions.

Yields:

• (statement)

each matching statement

Yield Parameters:

• statement

Yield Returns:

• (void)

ignored

 ``` 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 236 def each(solutions: RDF::Query::Solutions(RDF::Query::Solution.new), &block) log_debug("(formula each)") {SXP::Generator.string([self, solutions].to_sxp_bin)} # Yield statements by binding variables solutions.each do |solution| # Bind blank nodes to the solution when it doesn't contain a solution for an existential variable existential_vars.each do |var| solution[var.name] ||= RDF::Node.intern(var.name.to_s.sub(/^\\$+/, '')) end log_debug("(formula apply)") {solution.to_sxp} # Yield each variable statement which is constant after applying solution log_depth do n3statements.each do |statement| terms = {} [:subject, :predicate, :object].each do |part| terms[part] = case o = statement.send(part) when RDF::Query::Variable if solution[o] && solution[o].formula? log_info("(formula from var form)") {solution[o].graph_name.to_sxp} form_statements(solution[o], solution: solution, &block) else solution[o] || o end when RDF::N3::List o.variable? ? o.evaluate(solution.bindings, formulae: formulae) : o when RDF::N3::Algebra::Formula # uses the graph_name of the formula, and yields statements from the formula. No solutions are passed in. log_info("(formula from form)") {o.graph_name.to_sxp} form_statements(o, solution: solution, &block) else o end end statement = RDF::Statement.from(terms) log_debug("(formula add)") {statement.to_sxp} block.call(statement) end # statements from sub-operands sub_ops.each do |op| log_debug("(formula sub_op)") {SXP::Generator.string [op, solution].to_sxp_bin} op.each(solutions: RDF::Query::Solutions(solution)) do |stmt| log_debug("(formula add from sub_op)") {stmt.to_sxp} block.call(stmt) # Add statements for any term which is a formula stmt.to_a.select(&:node?).map {|n| formulae[n]}.compact.each do |ef| log_debug("(formula from form)") {ef.graph_name.to_sxp} form_statements(ef, solution: solution, &block) end end end end end end```

### #each_pattern {|pattern| ... } ⇒ Object

Yields each pattern which is not a builtin

Yields:

• (pattern)

each matching pattern

Yield Parameters:

• pattern

Yield Returns:

• (void)

ignored

 ``` 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 301 def each_pattern(&block) n3statements.each do |statement| terms = {} [:subject, :predicate, :object].each do |part| terms[part] = case o = statement.send(part) when RDF::N3::Algebra::Formula form_statements(o, solution: RDF::Query::Solution.new(), &block) else o end end pattern = RDF::Query::Pattern.from(terms) block.call(pattern) end end```

### #evaluate(bindings, formulae:, **options) ⇒ RDF::N3::List

Evaluates the formula using the given variable `bindings` by cloning the formula replacing variables with their bindings recursively.

Parameters:

Returns:

• SPARQL::Algebra::Expression.evaluate
 ``` 200 201 202 203 204 205 206 207 208 209 210 211``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 200 def evaluate(bindings, formulae:, **options) return self if bindings.empty? this = dup # Maintain formula relationships formulae {|k, v| this.formulae[k] ||= v} # Replace operands with bound operands this.operands = operands.map do |op| op.evaluate(bindings, formulae: formulae, **options) end this end```

### #execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new), **options) ⇒ RDF::Solutions

Yields solutions from patterns and other operands. Solutions are created by evaluating each pattern and other sub-operand against `queryable`.

When executing, blank nodes are turned into non-distinguished existential variables, noted with `\$\$`. These variables are removed from the returned solutions, as they can't be bound outside of the formula.

Parameters:

• queryable

the graph or repository to query

• solutions (defaults to: RDF::Query::Solutions(RDF::Query::Solution.new))

initial solutions for chained queries (RDF::Query::Solutions(RDF::Query::Solution.new))

• options (Hash{Symbol => Object})

Returns:

• (RDF::Solutions)

distinct solutions

 ``` 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 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 121 def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new), **options) log_info("formula #{graph_name}") {SXP::Generator.string operands.to_sxp_bin} log_debug("(formula bindings)") { SXP::Generator.string solutions.to_sxp_bin} @query ||= RDF::Query.new(patterns).optimize! log_info("(formula query)") { SXP::Generator.string(@query.to_sxp_bin)} solutions = if @query.empty? solutions else these_solutions = queryable.query(@query, solutions: solutions, **options) if these_solutions.empty? # Pattern doesn't match, so there can be no solutions log_debug("(formula query solutions)") { SXP::Generator.string([].to_sxp_bin)} RDF::Query::Solutions.new else these_solutions.map! do |solution| RDF::Query::Solution.new(solution.to_h.inject({}) do |memo, (name, value)| # Replace blank node bindings with lists and formula references with formula, where those blank nodes are associated with lists. value = formulae.fetch(value, value) if value.node? l = RDF::N3::List.try_list(value, queryable) value = l if l.constant? memo.merge(name => value) end) end log_debug("(formula query solutions)") { SXP::Generator.string(these_solutions.to_sxp_bin)} solutions.merge(these_solutions) end end return solutions if solutions.empty? # Reject solutions which include variables as values solutions.filter! {|s| s.enum_value.none?(&:variable?)} # Use our solutions for sub-ops # Join solutions from other operands # # * Order operands by those having inputs which are constant or bound. # * Run built-ins with indeterminant inputs (two-way) until any produces non-empty solutions, and then run remaining built-ins until exhasted or finished. # * Re-calculate inputs with bound inputs after each built-in is run. log_depth do # Iterate over sub_ops using evaluation heuristic ops = sub_ops.sort_by {|op| op.rank(solutions)} while !ops.empty? last_op = nil ops.each do |op| log_debug("(formula built-in)") {SXP::Generator.string op.to_sxp_bin} these_solutions = op.execute(queryable, solutions: solutions) # If there are no solutions, try the next one, until we either run out of operations, or we have solutions next if these_solutions.empty? last_op = op solutions = RDF::Query::Solutions(these_solutions) break end # If there is no last_op, there are no solutions. unless last_op solutions = RDF::Query::Solutions.new break end # Remove op from list, and re-order remaining ops. ops = (ops - [last_op]).sort_by {|op| op.rank(solutions)} end end log_info("(formula sub-op solutions)") {SXP::Generator.string solutions.to_sxp_bin} solutions end```

### #existential_vars ⇒ Array<RDF::Query::Variable]

Existential vars in this formula

Returns:

• Array<RDF::Query::Variable]

 ``` 395 396 397``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 395 def existential_vars @existentials ||= vars.select(&:existential?) end```

### #formula? ⇒ Boolean

Returns `true` if `self` is a RDF::N3::Algebra::Formula.

Returns:

• (Boolean)
 ``` 217 218 219``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 217 def formula? true end```

### #graph_name ⇒ RDF::ResourceAlso known as: to_uri

Graph name associated with this formula

Returns:

 ``` 320``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 320 def graph_name; @options[:graph_name]; end```

### #graph_name=(name) ⇒ RDF::Resource

Assign a graph name to this formula

Parameters:

• name

Returns:

 ``` 330 331 332 333``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 330 def graph_name=(name) formulae[name] = self @options[:graph_name] = name end```

### #hash ⇒ Object

The formula hash is the hash of it's operands and graph_name.

• Value#hash
 ``` 225 226 227``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 225 def hash ([graph_name] + operands).hash end```

### #inspect ⇒ Object

 ``` 426 427 428``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 426 def inspect sprintf("#<%s:%s(%d)>", self.class.name, self.graph_name, self.operands.count) end```

### #n3statements ⇒ Object

Statements memoizer, from the operands which are statements.

Statements may include embedded formulae.

 ``` 339 340 341 342 343 344 345 346``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 339 def n3statements # BNodes in statements are existential variables. @n3statements ||= begin # Operations/Builtins are not statements. operands. select {|op| op.is_a?(RDF::Statement)} end end```

### #patterns ⇒ Object

Patterns memoizer, from the operands which are statements and not builtins.

Expands statements containing formulae into their statements.

 ``` 352 353 354 355``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 352 def patterns # BNodes in statements are existential variables. @patterns ||= enum_for(:each_pattern).to_a end```

### #sub_ops ⇒ Object

Non-statement operands memoizer

 ``` 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 359 def sub_ops # operands that aren't statements, ordered by their graph_name @sub_ops ||= operands.reject {|op| op.is_a?(RDF::Statement)}.map do |op| # Substitute nodes for existential variables in operator operands op.operands.map! do |o| case o when RDF::N3::List # Substitute blank node members with existential variables, recusively. graph_name && o.has_nodes? ? o.to_ndvar(graph_name) : o when RDF::Node graph_name ? o.to_ndvar(graph_name) : o else o end end op end end```

### #to_base ⇒ Object

 ``` 422 423 424``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 422 def to_base inspect end```

### #to_s ⇒ Object

 ``` 413 414 415``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 413 def to_s to_sxp end```

### #to_sxp_bin ⇒ Object

 ``` 417 418 419 420``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 417 def to_sxp_bin [:formula, graph_name].compact + operands.map(&:to_sxp_bin) end```

### #undistinguished_vars ⇒ Array<RDF::Query::Variable]

Undistinguished vars in this formula

Returns:

• Array<RDF::Query::Variable]

 ``` 409 410 411``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 409 def undistinguished_vars @undistinguished ||= vars.vars.reject(&:distinguished?) end```

### #universal_vars ⇒ Array<RDF::Query::Variable]

Universal vars in this formula and sub-formulae

Returns:

• Array<RDF::Query::Variable]

 ``` 388 389 390``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 388 def universal_vars @universals ||= vars.reject(&:existential?).uniq end```

### #vars ⇒ Array<RDF::Query::Variable>

Return the variables contained within this formula

Returns:

 ``` 381 382 383``` ```# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-n3-a6ef81a7e1ce/lib/rdf/n3/algebra/formula.rb', line 381 def vars operands.vars.flatten.compact end```