Rete.Compiler.BetaGraph (Rete v0.2.0)

Copy Markdown View Source

The beta side of the network: a graph of Rete.Network.Node descriptions.

Internal. The compiler walks each production's sorted left hand side in order. It adds one node per condition, under the nodes the previous condition produced. Node 0 is an artificial root, so the graph has a single entry point.

Parents are a list. A disjunction adds each branch as its own chain, and hands the union of the branch terminals to the next condition. So the branches re-converge on it. That is why the compiler never flattens the LHS to disjunctive normal form: whole-LHS DNF costs work exponential in the number of disjunctions, while fanning out per condition costs only linear work.

Sharing requires equality and the same parent set. Equality alone is a correctness bug. In a({:customer, cid}, {:order, cid, amt}) and b({:vendor, cid}, {:order, cid, amt}), the two order conditions are equal, but they sit under different parents. Sharing them would let a :vendor token join a :customer's elements. A terminal keys on the production's identity instead, so two rules with an identical LHS fire independently.

{:or, []} is false, and the compiler builds nothing for a path through one. Otherwise, a keyless condition after a false element would become an entry point the alpha index feeds. An unsatisfiable rule would then fire on every fact. See docs/design/network.md ยง4.

Summary

Types

Node ids, allocated in insertion order.

t()

Functions

Adds one production, sharing nodes with everything already in the graph.

Adds every production to a new graph, in order.

The children of a node, in the order they were added.

Every node satisfying a predicate, by ascending id.

An empty graph containing only the root.

The node under an id, or nil.

The parents of a node.

The id of the artificial root every rule hangs from.

Every node whose only parent is the root: the entry points of the beta side.

Types

id()

@type id() :: non_neg_integer()

Node ids, allocated in insertion order.

t()

@type t() :: %Rete.Compiler.BetaGraph{
  backward: %{required(id()) => MapSet.t(id())},
  forward: %{required(id()) => [id()]},
  next_id: id(),
  nodes: %{required(id()) => Rete.Network.Node.t()}
}

Functions

add_production(graph, production)

@spec add_production(t(), Rete.IR.Production.t()) :: t()

Adds one production, sharing nodes with everything already in the graph.

A production whose left hand side is unsatisfiable โ€” one that contains a {:or, []} outside every branch that could avoid it โ€” adds nothing at all.

build(productions)

@spec build([Rete.IR.Production.t()]) :: t()

Adds every production to a new graph, in order.

Order matters only for id allocation, and therefore only for readability. The same productions, in the same order, always produce the same ids.

children(beta_graph, id)

@spec children(t(), id()) :: [id()]

The children of a node, in the order they were added.

filter(beta_graph, predicate)

@spec filter(t(), (Rete.Network.Node.t() -> boolean())) :: [Rete.Network.Node.t()]

Every node satisfying a predicate, by ascending id.

new()

@spec new() :: t()

An empty graph containing only the root.

node(beta_graph, id)

@spec node(t(), id()) :: Rete.Network.Node.t() | nil

The node under an id, or nil.

parents(beta_graph, id)

@spec parents(t(), id()) :: MapSet.t(id())

The parents of a node.

root_id()

@spec root_id() :: id()

The id of the artificial root every rule hangs from.

roots(graph)

@spec roots(t()) :: [id()]

Every node whose only parent is the root: the entry points of the beta side.