Current section

Files

Jump to
retex lib retex.ex
Raw

lib/retex.ex

defmodule Retex do
@moduledoc false
alias Retex.{Node, Protocol, Fact, Token}
alias Node.{
Type,
Test,
Select,
PNode,
BetaMemory
}
@type action :: %{given: list(Retex.Wme.t()), then: list(Retex.Wme.t())}
@type network_node :: Type.t() | Test.t() | Select.t() | PNode.t() | BetaMemory.t()
defstruct graph: Graph.new(),
wmes: %{},
agenda: [],
activations: %{},
wme_activations: %{},
tokens: %{},
bindings: %{},
pending_activation: []
@spec root_vertex :: Retex.Root.t()
def root_vertex(), do: Retex.Root.new()
@spec new :: Retex.t()
def new() do
%{graph: graph} = %Retex{}
graph = Graph.add_vertex(graph, Retex.Root.new())
%Retex{graph: graph}
end
@spec add_wme(Retex.t(), Retex.Wme.t()) :: Retex.t()
def add_wme(%Retex{} = network, %Retex.Wme{} = wme) do
wme = Map.put(wme, :timestamp, :os.system_time(:seconds))
network = %{network | wmes: Map.put(network.wmes, wme.id, wme)}
{network, bindings} = propagate_activations(network, root_vertex(), wme, network.bindings)
%{network | bindings: Map.merge(network.bindings, bindings)}
end
defp propagate_activation(neighbor, rete, wme, bindings, tokens \\ []) do
Protocol.Activation.activate(neighbor, rete, wme, bindings, tokens)
end
@spec add_production(Retex.t(), %{given: list(Retex.Wme.t()), then: action()}) :: Retex.t()
def add_production(%{graph: graph} = network, %{given: given, then: action}) do
given = compile_given(%{}, given)
{graph, alphas} =
given |> Enum.reverse() |> Enum.reduce({graph, []}, &build_alpha_network(&1, &2))
{beta_memory, graph} = build_beta_network(graph, alphas)
graph = add_p_node(graph, beta_memory, action)
%{network | graph: graph}
end
@spec build_beta_network(Graph.t(), list(network_node())) :: {list(network_node()), Graph.t()}
def build_beta_network(graph, disjoint_beta_network) do
create_beta_nodes(graph, disjoint_beta_network)
end
@spec create_beta_nodes(Graph.t(), list(network_node())) :: {list(network_node()), Graph.t()}
def create_beta_nodes(graph, [first | [second | list]]) do
{beta_memory, _} = Node.BetaMemory.new(first, second)
graph
|> Graph.add_vertex(beta_memory)
|> Graph.add_edge(first, beta_memory)
|> Graph.add_edge(second, beta_memory)
|> create_beta_nodes([beta_memory | list])
end
def create_beta_nodes(graph, [beta_memory]) do
{beta_memory, graph}
end
@spec add_p_node(Graph.t(), BetaMemory.t(), action()) :: Graph.t()
def add_p_node(graph, beta_memory, action) do
{pnode, _} = Node.PNode.new(action)
graph |> Graph.add_vertex(pnode) |> Graph.add_edge(beta_memory, pnode)
end
@spec build_alpha_network(
Fact.Isa.t() | Fact.HasAttribute.t(),
{Graph.t(), list(network_node())}
) :: {Graph.t(), list(network_node())}
def build_alpha_network(%Fact.Isa{} = condition, {graph, test_nodes}) do
%{variable: _, type: type} = condition
{type_node, _} = Node.Type.new(type)
new_graph =
graph
|> Graph.add_vertex(type_node)
|> Graph.add_edge(root_vertex(), type_node)
{new_graph, [type_node | test_nodes]}
end
def build_alpha_network(%Fact.HasAttribute{} = condition, {graph, test_nodes}) do
%{attribute: attribute, owner: class, predicate: predicate, value: value} = condition
condition_id = hash(condition)
{type_node, _} = Node.Type.new(class)
{select_node, _} = Node.Select.new(class, attribute)
{test_node, _} = Node.Test.new([predicate, value], condition_id)
new_graph =
graph
|> Graph.add_vertex(type_node)
|> Graph.add_edge(root_vertex(), type_node)
|> Graph.add_vertex(select_node)
|> Graph.add_edge(type_node, select_node)
|> Graph.add_vertex(test_node)
|> Graph.add_edge(select_node, test_node)
{new_graph, [test_node | test_nodes]}
end
@spec print(%{graph: Graph.t()}) :: Retex.t()
def print(%{graph: graph} = network) do
with {:ok, graph} <- Graph.to_dot(graph) do
IO.write("\n")
IO.write("\n")
IO.puts(graph)
IO.write("\n")
IO.write("\n")
end
network
end
@spec hash(any) :: String.t()
def hash(:uuid4), do: UUIDTools.uuid4()
def hash(data) do
:crypto.hash(:sha256, inspect(data))
|> Base.encode16()
|> String.downcase()
end
@spec replace_bindings(PNode.t(), map) :: PNode.t()
def replace_bindings(%_{action: actions} = pnode, bindings) when is_map(bindings) do
new_actions =
Enum.map(actions, fn action ->
case action do
action when is_tuple(action) ->
List.to_tuple(
for element <- Tuple.to_list(action) do
if is_binary(element), do: Map.get(bindings, element, element), else: element
end
)
anything ->
anything
end
end)
%{pnode | action: new_actions, bindings: bindings}
end
def replace_bindings(%_{action: actions} = pnode, bindings) when is_map(bindings) do
new_actions =
Enum.map(actions, fn action ->
case action do
action when is_tuple(action) ->
List.to_tuple(
for element <- Tuple.to_list(action) do
if is_binary(element), do: Map.get(bindings, element, element), else: element
end
)
anything ->
anything
end
end)
%{pnode | action: new_actions, bindings: bindings}
end
def replace_bindings(%_{action: actions} = pnode, {_, _, bindings}) when is_map(bindings) do
new_actions =
Enum.map(actions, fn action ->
case action do
action when is_tuple(action) ->
List.to_tuple(
for element <- Tuple.to_list(action) do
if is_binary(element), do: Map.get(bindings, element, element), else: element
end
)
anything ->
anything
end
end)
%{pnode | action: new_actions, bindings: bindings}
end
@spec add_token(Retex.t(), network_node(), Retex.Wme.t(), map, list(Retex.Token.t())) ::
Retex.t()
def add_token(
%Retex{tokens: rete_tokens} = rete,
current_node,
_wme,
_bindings,
[_ | _] = tokens
) do
node_tokens = Map.get(rete_tokens, current_node.id, [])
all_tokens = Enum.uniq(node_tokens ++ tokens)
new_tokens = Map.put(rete_tokens, current_node.id, all_tokens)
%{rete | tokens: new_tokens}
end
def add_token(%Retex{tokens: rete_tokens} = rete, current_node, wme, bindings, tokens) do
node_tokens = Map.get(rete_tokens, current_node.id, [])
token = Token.new()
token = %{
token
| wmem: wme,
node: current_node.id,
bindings: bindings
}
all_tokens = [token | node_tokens] ++ tokens
new_tokens = Map.put(rete_tokens, current_node.id, Enum.uniq(all_tokens))
%{rete | tokens: new_tokens}
end
@spec create_activation(Retex.t(), network_node(), Retex.Wme.t()) :: Retex.t()
def create_activation(
%__MODULE__{activations: activations, wme_activations: wme_activations} = rete,
current_node,
wme
) do
node_activations = Map.get(activations, current_node.id, [])
new_activations = [wme.id | node_activations]
new_rete = %{rete | activations: Map.put(activations, current_node.id, new_activations)}
previous_wme_activations = Map.get(wme_activations, wme.id, [])
new_wme_activations =
Map.put(wme_activations, wme.id, [current_node.id | previous_wme_activations])
%{new_rete | wme_activations: new_wme_activations}
end
@spec propagate_activations(
Retex.t(),
network_node(),
Retex.Wme.t(),
map,
list(Retex.Token.t())
) :: {Retex.t(), map}
def propagate_activations(
%Retex{} = rete,
%{} = current_node,
%Retex.Wme{} = wme,
bindings,
new_tokens
) do
%{graph: graph} = rete
children = Graph.out_neighbors(graph, current_node)
Enum.reduce(children, {rete, bindings}, fn vertex, {network, bindings} ->
propagate_activation(vertex, network, wme, bindings, new_tokens)
end)
end
@spec propagate_activations(Retex.t(), network_node(), Retex.Wme.t(), map) :: {Retex.t(), map}
def propagate_activations(
%Retex{} = rete,
%{} = current_node,
%Retex.Wme{} = wme,
bindings
) do
%{graph: graph} = rete
children = Graph.out_neighbors(graph, current_node)
Enum.reduce(children, {rete, bindings}, fn vertex, {network, bindings} ->
propagate_activation(vertex, network, wme, bindings)
end)
end
@spec continue_traversal(Retex.t(), map, network_node(), Retex.Wme.t(), list(Retex.Token.t())) ::
{Retex.t(), map}
def continue_traversal(
%Retex{} = new_rete,
%{} = new_bindings,
%_{} = current_node,
%Retex.Wme{} = wme,
tokens
) do
{new_rete, new_bindings}
propagate_activations(new_rete, current_node, wme, new_bindings, tokens)
end
@spec continue_traversal(Retex.t(), map, network_node(), Retex.Wme.t()) :: {Retex.t(), map}
def continue_traversal(
%Retex{} = new_rete,
%{} = new_bindings,
%_{} = current_node,
%Retex.Wme{} = wme
) do
{new_rete, new_bindings}
propagate_activations(new_rete, current_node, wme, new_bindings)
end
@spec stop_traversal(Retex.t(), map) :: {Retex.t(), map}
def stop_traversal(%Retex{} = rete, %{} = bindings) do
{rete, bindings}
end
defp compile_given(_acc, []), do: []
defp compile_given(acc, conditions) do
{_, new_conditions} =
Enum.reduce(conditions, {acc, []}, fn condition, {acc, conds} ->
case condition do
%Fact.Isa{type: type, variable: variable} = condition ->
acc = Map.put_new(acc, variable, type)
{acc, [condition | conds]}
%Fact.HasAttribute{owner: "$" <> _variable_name = var} = condition ->
type = Map.get(acc, var) || raise("#{var} is not defined")
{acc, [%{condition | owner: type} | conds]}
%Fact.Relation{} = condition ->
%{from: from, name: _rel_name, to: to, via: via} = condition
var = "$" <> to_string(via)
has_attribute_owner = %Fact.HasAttribute{
attribute: :id,
owner: from,
predicate: :==,
value: var
}
has_attribute_child = %Fact.HasAttribute{
attribute: via,
owner: to,
predicate: :==,
value: var
}
{acc, [has_attribute_child | [has_attribute_owner | conds]]}
condition ->
{acc, [condition | conds]}
end
end)
new_conditions
end
end