Skip to content

Commit

Permalink
- EXNN.Nodes.Loader register all node servers iterating through genomes
Browse files Browse the repository at this point in the history
- EXNN.Trainer iteratively signals sensors to start propagation of impulses
- server_for resolution moved to the EXNN.NodeSupervisor.Forwarder
  • Loading branch information
zampino committed Mar 4, 2015
1 parent 64e4b60 commit 2489665
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 37 deletions.
10 changes: 9 additions & 1 deletion lib/exnn/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,19 @@ defmodule EXNN.Config do
Agent.get(__MODULE__, &Map.get(&1, :pattern))
end

def sensors do
filter = fn({type, id, mod, opts})->
type == :sensor
end
mapper = fn({type, id, mod, opts})-> id end
get_remote_nodes
|> Enum.filter_map(filter, mapper)
end

def config_for(identifier) do
search_by_id = fn({type, id, mod, opts})->
(identifier == id) and %{type: type, id: id, mod: mod, opts: opts}
end

get_remote_nodes
|> Enum.find_value(search_by_id)
end
Expand Down
8 changes: 4 additions & 4 deletions lib/exnn/connectome.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,24 @@ defmodule EXNN.Connectome do
[{previous_type, previous_list} | tail] = rest
genomes = EXNN.Genome.collect(type, list)
# FIXME: not sure we want to set ins at all!!!
genomes = EXNN.Genome.set_ins(genomes, previous_list)
|> EXNN.Genome.set_ins(previous_list)
link(rest, [genomes])
end

@doc "and sensors are last"
def link([{first_type, first_list}], acc) do
[outs | rest] = acc
genomes = EXNN.Genome.collect(first_type, first_list)
EXNN.Genome.set_outs(genomes, outs)
|> EXNN.Genome.set_outs(outs)
link([], [genomes | acc])
end

def link([{type, list} | rest], acc) do
[{previous_type, previous_list} | tail] = rest
[outs | tail] = acc
genomes = EXNN.Genome.collect(type, list)
genomes = EXNN.Genome.set_ins(genomes, previous_list)
genomes = EXNN.Genome.set_outs(genomes, outs)
|> EXNN.Genome.set_ins(previous_list)
|> EXNN.Genome.set_outs(outs)
link(rest, [genomes | acc])
end

Expand Down
7 changes: 2 additions & 5 deletions lib/exnn/neuron.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule EXNN.Neuron do
use EXNN.NodeServer

defstruct id: nil, ins: [], outs: [], bias: 0,
activation: &EXNN.Math.tanh/1, acc: [], trigger: []
activation: &EXNN.Math.id/1, acc: [], trigger: []

def initialize(genome) do
Dict.merge(genome, trigger: Dict.keys(genome.ins), acc: [])
Expand All @@ -19,8 +19,6 @@ defmodule EXNN.Neuron do
@doc "broadcast input to registered outs and resets its trigger"
def fire(%__MODULE__{trigger: []} = neuron) do
{neuron, value} = impulse(neuron)

IO.puts "about to cast: #{inspect(neuron.outs)}"
neuron.outs |>
Enum.each &(GenServer.cast(&1, {:signal, {neuron.id, value}}))

Expand All @@ -42,7 +40,6 @@ defmodule EXNN.Neuron do
end

def signal(neuron, {origin, value}) do
IO.puts "fire!"
acc = neuron.acc ++ [{origin, value}]
trigger = List.delete(neuron.trigger, origin)
%__MODULE__{neuron | trigger: trigger, acc: acc}
Expand All @@ -51,7 +48,7 @@ defmodule EXNN.Neuron do

defimpl EXNN.Connection, for: __MODULE__ do
def signal(neuron, {origin, value}) do
IO.puts "signaling neuron: #{neuron.id} -- #{inspect(neuron)}"
IO.puts "---- signaling neuron: #{neuron.id} -- #{inspect(neuron)} ------"
EXNN.Neuron.signal(neuron, {origin, value})
end
end
Expand Down
23 changes: 11 additions & 12 deletions lib/exnn/node_supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,26 @@ defmodule EXNN.NodeSupervisor do
use Supervisor

defmodule Forwarder do
def start_link(server, genome) do
IO.puts "forwarder called with: #{inspect(server)} and #{inspect(genome)}"
def start_link(genome) do
server = server_for(genome.type, genome.id)
server.start_link(genome)
end

def server_for(:neuron, _id) do
EXNN.Neuron
end

def server_for(_type, id) do
EXNN.Config.config_for(id).mod
end
end

def start_link do
Supervisor.start_link __MODULE__, :ok, name: __MODULE__
end

def start_node(genome) do
server = server_for(genome.type, genome.id)
Supervisor.start_child(__MODULE__, [server, genome])
end

def server_for(:neuron, _id) do
EXNN.Neuron
end

def server_for(_type, id) do
EXNN.Config.config_for(id).mod
Supervisor.start_child(__MODULE__, [genome])
end

def init(:ok) do
Expand Down
17 changes: 12 additions & 5 deletions lib/exnn/nodes.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule EXNN.Nodes do
use GenServer
use GenServer, async: true

def start_link do
GenServer.start_link(__MODULE__,
Expand All @@ -14,33 +14,40 @@ defmodule EXNN.Nodes do
# client api

def register(genome) do
GenServer.cast __MODULE__, {:register, genome}
GenServer.call __MODULE__, {:register, genome}
end

def node_pid(id) do
GenServer.call __MODULE__, {:pids, id}
end

def names do
GenServer.call __MODULE__, :names
end
# Server callbacks

def handle_cast({:register, genome}, state) do
def handle_call({:register, genome}, _from, state) do
if HashDict.get(state.names, genome.id) do
{:noreply, state}
else
{:ok, pid} = EXNN.NodeSupervisor.start_node(genome)
ref = Process.monitor(pid)
refs = HashDict.put state.refs, ref, genome.id
names = HashDict.put state.names, genome.id, pid
{:noreply, %{state | refs: refs, names: names}}
{:reply, :ok, %{state | refs: refs, names: names}}
end
end

def handle_call(:names, _from, state) do
{:reply, Dict.keys(state.names), state}
end

def handle_call({:pids, id}, _from, state) do
{:reply, state.names[id], state}
end

def handle_info({:DOWN, ref, :process, pid, _reason}, state) do
IO.puts "DOWN with reason: #{inspect(_reason)}"
IO.puts "/////// DOWN with reason: #{inspect(_reason)} ////////////////////"
{name, refs} = HashDict.pop(state.refs, ref)
names = HashDict.delete(state.names, name)
# GenEvent.sync_notify(state.events, {:exit, name, pid})
Expand Down
14 changes: 14 additions & 0 deletions lib/exnn/nodes/loader.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule EXNN.Nodes.Loader do

def start_link do
# implement state dump for restarts
result = Agent.get(EXNN.Connectome, &(&1))
|> Enum.each(&register/1)
{result, self}
end

def register({id, genome}) do
EXNN.Nodes.register(genome)
end

end
1 change: 1 addition & 0 deletions lib/exnn/sensor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ defmodule EXNN.Sensor do
def sync(sensor, origin_value) do
sensor = before_sync(sensor)
impulse = format_impulse(sensor, origin_value)
IO.puts "++++++ sensor impulse: #{inspect(impulse)} to: #{inspect(sensor.outs)} +++++"
forward = fn(out_id) ->
GenServer.cast out_id, {:signal, impulse}
end
Expand Down
3 changes: 2 additions & 1 deletion lib/exnn/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ defmodule EXNN.Supervisor do
|> child(EXNN.Connectome, [])
|> child(:supervisor, EXNN.NodeSupervisor, [])
|> child(EXNN.Nodes, [])
# |> child(:supervisor, EXNN.Nodes.Loader, [])
|> child(EXNN.Nodes.Loader, [])
|> child(EXNN.Trainer, [])
|> supervise(strategy: :one_for_one)
end

Expand Down
36 changes: 36 additions & 0 deletions lib/exnn/trainer.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
defmodule EXNN.Trainer do
use GenServer

@train_interval 200

def start_link do
GenServer.start_link(__MODULE__,
:ok,
name: __MODULE__)
end

def init(:ok) do
stream = Stream.repeatedly(&train/0)
{:ok, %{stream: stream}}
end

def train do
EXNN.Config.sensors |> Enum.each(&train/1)
# :timer.sleep @train_interval
end

def train(sensor_id) do
GenServer.cast sensor_id, {:signal, {self, :sync}}
end

# public api
def iterate(cycles) do
GenServer.call __MODULE__, {:iterate, cycles}
end

# server callbacks
def handle_call({:iterate, num}, _from, state) do
report = Stream.take(state.stream, num) |> Stream.run()
{:reply, report, state}
end
end
12 changes: 7 additions & 5 deletions test/exnn/nodes_test.exs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
defmodule EXNN.NodesTest do
use ExUnit.Case
use ExUnit.Case, async: true

setup do #_all do

{:ok, node_sup} = EXNN.NodeSupervisor.start_link
{_status, node_sup} = EXNN.NodeSupervisor.start_link
IO.puts "/////////////////////////////// exnn test node superv tries to start: #{_status}"
# {:ok, %{sup: node_sup}}

# on_exit fn ->
Expand All @@ -26,8 +26,10 @@ defmodule EXNN.NodesTest do
{:ok, nodes} = EXNN.Nodes.start_link

on_exit fn ->
[node_sup, config, nodes]
|> Enum.each(&Process.exit(&1, :kill))
Process.exit node_sup, :kill

[config, nodes]
|> Enum.each(&Process.exit(&1, :normal))
end

Process.register self, :test_pid
Expand Down
61 changes: 57 additions & 4 deletions test/exnn_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule EXNNTest do
on_exit fn ->
IO.puts "terminating app"
HostApp.stop(:normal)
# Process.exit(pid, :kill)
end
:ok
end
Expand All @@ -25,12 +26,60 @@ defmodule EXNNTest do

test "storing genomes" do
origin = self
Agent.cast :exnn_connectome, &(send(origin, HashDict.to_list(&1)))
genomes = Agent.get EXNN.Connectome, &(&1)

assert HashDict.to_list(genomes) == [neuron_l1_3: %{id: :neuron_l1_3,
ins: [s_1: 0.14210821770124227, s_2: 0.20944855618709624],
outs: [:neuron_l2_1, :neuron_l2_2], type: :neuron},
s_2: %{id: :s_2, outs: [:neuron_l1_1, :neuron_l1_2, :neuron_l1_3],
type: :sensor},
neuron_l1_2: %{id: :neuron_l1_2,
ins: [s_1: 0.47712105608919275, s_2: 0.5965100813402789],
outs: [:neuron_l2_1, :neuron_l2_2], type: :neuron},
neuron_l2_2: %{id: :neuron_l2_2,
ins: [neuron_l1_1: 0.5014907142064751, neuron_l1_2: 0.311326754804393,
neuron_l1_3: 0.597447524783298], outs: [:a_1], type: :neuron},
s_1: %{id: :s_1, outs: [:neuron_l1_1, :neuron_l1_2, :neuron_l1_3],
type: :sensor},
neuron_l1_1: %{id: :neuron_l1_1,
ins: [s_1: 0.915656206971831, s_2: 0.6669572934854013],
outs: [:neuron_l2_1, :neuron_l2_2], type: :neuron},
neuron_l2_1: %{id: :neuron_l2_1,
ins: [neuron_l1_1: 0.4435846174457203, neuron_l1_2: 0.7230402056221108,
neuron_l1_3: 0.94581636451987], outs: [:a_1], type: :neuron},
a_1: %{id: :a_1, ins: [:neuron_l2_1, :neuron_l2_2], type: :actuator}]
# [
# neuron_l1_3: %{id: :neuron_l1_3, ins: [s_1: 0.14210821770124227, s_2: 0.20944855618709624], outs: [:neuron_l2_1, :neuron_l2_2], type: :neuron},
# s_2: %{id: :s_2, type: :sensor},
# neuron_l1_2: %{id: :neuron_l1_2, ins: [s_1: 0.47712105608919275, s_2: 0.5965100813402789], outs: [:neuron_l2_1, :neuron_l2_2],
# type: :neuron},
# neuron_l2_2: %{id: :neuron_l2_2, ins: [neuron_l1_1: 0.5014907142064751, neuron_l1_2: 0.311326754804393, neuron_l1_3: 0.597447524783298], outs: [:a_1], type: :neuron},
# s_1: %{id: :s_1, type: :sensor},
# neuron_l1_1: %{id: :neuron_l1_1, ins: [s_1: 0.915656206971831, s_2: 0.6669572934854013], outs: [:neuron_l2_1, :neuron_l2_2], type: :neuron},
# neuron_l2_1: %{id: :neuron_l2_1, ins: [neuron_l1_1: 0.4435846174457203,
# neuron_l1_2: 0.7230402056221108, neuron_l1_3: 0.94581636451987], outs: [:a_1], type: :neuron},
# a_1: %{id: :a_1, ins: [:neuron_l2_1, :neuron_l2_2], type: :actuator}]
end

assert_receive [neuron_l1_3: %{id: :neuron_l1_3, ins: [s_1: 0.14210821770124227, s_2: 0.20944855618709624], outs: [:neuron_l2_1, :neuron_l2_2], type: :neuron}, s_2: %{id: :s_2, type: :sensor}, neuron_l1_2: %{id: :neuron_l1_2, ins: [s_1: 0.47712105608919275, s_2: 0.5965100813402789], outs: [:neuron_l2_1, :neuron_l2_2],
type: :neuron}, neuron_l2_2: %{id: :neuron_l2_2, ins: [neuron_l1_1: 0.5014907142064751, neuron_l1_2: 0.311326754804393, neuron_l1_3: 0.597447524783298], outs: [:a_1], type: :neuron}, s_1: %{id: :s_1, type: :sensor}, neuron_l1_1: %{id: :neuron_l1_1, ins: [s_1: 0.915656206971831, s_2: 0.6669572934854013], outs: [:neuron_l2_1, :neuron_l2_2], type: :neuron}, neuron_l2_1: %{id: :neuron_l2_1, ins: [neuron_l1_1: 0.4435846174457203, neuron_l1_2: 0.7230402056221108, neuron_l1_3: 0.94581636451987], outs: [:a_1], type: :neuron}, a_1: %{id: :a_1, ins: [:neuron_l2_1, :neuron_l2_2], type: :actuator}]
test "It should store all nodes as server" do
assert EXNN.Nodes.names == [:neuron_l1_3,
:s_2,
:neuron_l1_2,
:neuron_l2_2,
:s_1,
:neuron_l1_1,
:neuron_l2_1,
:a_1]
end

test "It should launch a first Training task" do
report = EXNN.Trainer.iterate(5)
:timer.sleep 500
recorded = GenServer.call(:a_1, :store)
IO.puts "==== #{inspect(recorded)} =========="
assert report == :ok
refute Enum.empty?(recorded)
end
end

defmodule HostApp do
Expand Down Expand Up @@ -66,7 +115,11 @@ defmodule HostApp.Recorder do

def act(state, {from, signal}) do
store = [{from, signal} | state.store]
Map.update(state, store: store)
%__MODULE__{state | store: store}
end

def handle_call(:store, _from, state) do
{:reply, state.store, state}
end
end

Expand Down

0 comments on commit 2489665

Please sign in to comment.