View Source Wongi.Engine (Wongi.Engine v0.9.13)

This is a pure-Elixir forward chaining inference engine based on the classic Rete algorithm.

It's derived from an earlier Ruby library and has a similar interface.

Usage

The following examples will assume a prelude of

import Wongi.Engine
import Wongi.Engine.DSL

which comprises the entire public interface of the library.

First, an engine instance needs to be created:

engine = new()

Knowledge management

All knowledge in the system is represented as a set of triples in the form of {subject, predicate, object}. Any complex properties or relationships between entities can be broken down to this form, in which case the relationship members will take on the role of subjects and objects, and the type of the relationship will be the predicate.

Any Elixir term except the atom :_ can be used as any element of a triple, although predicates naturally tend to be atoms.

References can be used to naturally represent anonymous graph nodes.

Facts are added into the system with assert/2 or assert/4:

engine = engine |> assert(:earth, :satellite, :moon)
# or
engine = engine |> assert({:earth, :satellite, :moon})
# or
engine = engine |> assert([:earth, :satellite, :moon])
# or
engine = engine |> assert(WME.new(:earth, :satellite, :moon))

WME (working memory element) is the standard Rete term for "fact". You would rarely need to construct Wongi.Engine.WME instances by hand, but you might retrieve them from the engine and use in further function calls.

Similarly, retract/2 or retract/4 remove facts from the system.

Searching

select/2 and select/4 can be used to return a set of facts matching a template. A template is a triple where some of the elements can be the special placeholder value :_.

An enumerable of all facts matching the template is returned:

[fact] =
  engine
  |> select(:earth, :satellite, :_)
  |> Enum.to_list()

IO.inspect(fact.object)
# => :moon

Rules

Rules allow expressing more complex conditions than a single template.

A rule is constructed like this:

rule = rule("optional name", forall: [
  matcher1,
  matcher2,
  ...
])

IO.inspect(rule.ref)
# => #Reference<...>

The ref field is going to be used later to retrieve the results of rule execution.

The rule can then be installed into the engine:

engine = engine |> compile(rule)

Alternatively, this form can be used if you don't want an intermediate variable for the rule, although it is less pipeable:

{engine, ref} =
  engine
  |> compile_and_get_ref(rule(forall: [...]))

The forall section of a rule consists of a list of matchers (more fully documented in Wongi.Engine.DSL) that express some sort of condition. The simplest matcher is Wongi.Engine.DSL.has/3 which passes if a fact matching its template exists.

A crucial part of matching is the variable bindings. A variable is specified using Wongi.Engine.DSL.var/1. The first time a variable is encountered, it is bound to the matched value. Subsequent matches will only succeed if the value is the same as the initially bound one.

:_ can be used as a placeholder variable that matches anything and is not bound to any value.

rule = rule(forall: [
  has(:_, :satellite, var(:satellite)),
  has(var(:satellite), :mass, var(:mass))
])

engine =
  new()
  |> compile(rule)
  |> assert(:earth, :satellite, :moon)
  |> assert(:moon, :mass, 7.34767309e22)

The results of rule execution can be retrieved using tokens/2, which returns an enumerable. A token represents a single possible execution of the matcher sequence. Our set of facts satisfies the rule exactly once, so we expect exactly one token. The bound variables can then be inspected on it:

[token] = engine |> tokens(rule.ref) |> Enum.to_list()

IO.inspect(token[:satellite])
# => :moon
IO.inspect(token[:mass])
# => 7.34767309e22

Generation

In addition to passively examining the results, it is also possible for a rule to perform some actions when it is fully satisfied. Generating additional facts is one such action.

For example, we can add a rule that generates a fact about the gravitational pull on the satellite:

rule =
  rule(
    forall: [
      has(var(:planet), :satellite, var(:satellite)),
      has(var(:planet), :mass, var(:planet_mass)),
      has(var(:satellite), :mass, var(:sat_mass)),
      has(var(:satellite), :distance, var(:distance)),
      assign(:pull, &(6.674e-11 * &1[:sat_mass] * &1[:planet_mass] / :math.pow(&1[:distance], 2)))
    ],
    do: [
      gen(var(:satellite), :pull, var(:pull))
    ]
  )

engine =
  new()
  |> compile(rule)
  |> assert(:earth, :satellite, :moon)
  |> assert(:earth, :mass, 5.972e24)
  |> assert(:moon, :mass, 7.34767309e22)
  |> assert(:moon, :distance, 384_400.0e3)

[wme] = engine |> select(:moon, :pull, :_) |> Enum.to_list()
IO.inspect(wme.object)
# => 1.9819334566450407e20

The generated facts keep track of the rule that generated them and get automatically retracted if the conditions are no longer satisfied.

If a fact has been generated by a rule and also asserted manually, it also needs to be retracted by both means to be removed from the system.

Summary

Functions

Returns an engine with the given fact added to the working memory.

Returns an engine with the given fact added to the working memory.

Returns an engine with the given rule installed.

Returns an engine with the given rule installed and the rule reference.

Creates a new engine instance.

Returns all production node references.

Returns an engine with the given fact removed from the working memory.

Returns an engine with the given fact removed from the working memory.

Returns a set of all facts matching the given template.

Returns a set of all facts matching the given template.

Returns a set of all tokens for the given production node reference.

Types

@type fact() :: {any(), any(), any()} | wme()
@type rule() :: Wongi.Engine.DSL.Rule.t()
@type t() :: Wongi.Engine.Rete.t()
@type template() :: {any(), any(), any()} | wme()
@type wme() :: Wongi.Engine.WME.t()

Functions

@spec assert(t(), fact()) :: t()

Returns an engine with the given fact added to the working memory.

Link to this function

assert(rete, subject, predicate, object)

View Source
@spec assert(t(), any(), any(), any()) :: t()

Returns an engine with the given fact added to the working memory.

@spec compile(t(), rule()) :: t()

Returns an engine with the given rule installed.

See Wongi.Engine.DSL for details on the rule definition DSL.

Link to this function

compile_and_get_ref(rete, rule)

View Source
@spec compile_and_get_ref(t(), rule()) :: {t(), reference()}

Returns an engine with the given rule installed and the rule reference.

The rule reference can be used to retrieve production tokens using tokens/2.

See Wongi.Engine.DSL for details on the rule definition DSL.

@spec new() :: t()

Creates a new engine instance.

@spec productions(t()) :: MapSet.t(reference())

Returns all production node references.

@spec retract(t(), fact()) :: t()

Returns an engine with the given fact removed from the working memory.

Link to this function

retract(rete, subject, object, predicate)

View Source
@spec retract(t(), any(), any(), any()) :: t()

Returns an engine with the given fact removed from the working memory.

@spec select(t(), template()) :: MapSet.t(fact())

Returns a set of all facts matching the given template.

Link to this function

select(rete, subject, predicate, object)

View Source
@spec select(t(), any(), any(), any()) :: MapSet.t(fact())

Returns a set of all facts matching the given template.

@spec tokens(t(), reference()) :: MapSet.t(Wongi.Engine.Token.t())

Returns a set of all tokens for the given production node reference.