anchor.diff:                    Pid ! {fire, Self, 1.0, Token} 
                   end, Outputs),
     {1.0, []};
  -fire_neuron(Inputs, [], _, Main, Token) ->
  +fire_neuron(Inputs, [], _, Main, Token, _Activation) ->
     % -- output neuron
     Self = self(),
     Input = [receive {fire, Pid, In, Token} -> {Weight * In, In} end || {Pid, Weight} <- Inputs],
     Output = output_activation(sum_tuples(Input)),
     Main ! {output, Self, Output, Token},
     {Output, snd(Input)};
  -fire_neuron([{Pid, _}], Outputs, _, Main, Token) when Pid == Main ->
  +fire_neuron([{Pid, _}], Outputs, _, Main, Token, _Activation) when Pid == Main ->
     % -- input layer
     Self = self(),
     Output = receive {fire, Pid, In, Token} -> In end,
  
positive.code: compute_neurons([_L]) -> 0;
  compute_neurons([L1, L2 | Ls]) -> (L1 + 1) * L2 + compute_neurons([L2 | Ls]).
  
  random_weigths(N) -> [rand:uniform() * 2.0 - 1.0 || _ <- lists:seq(1, N)].
  % random_weigths(N) -> lists:seq(1, N).
  
  random_shuffle(L) ->
  
negative.code: -compile([export_all]).
  
  sigmoid(X) -> 1.0 / (1.0 + math:exp(-X)).
  sigmoid_grad(X) -> X * (1.0 - X).
  
  relu(X) -> max(0.0, X).
  relu_grad(X) when X =< 0 -> 0.0;
  relu_grad(_X) -> 1.0.
  
  activation(sigmoid, X) -> sigmoid(X);
  activation(relu, X) -> relu(X).
  
  activation_gradient(sigmoid, X) -> sigmoid_grad(X);
  activation_gradient(relu, X) -> relu_grad(X).
  
  output_activation(X) -> X.
  
  output_activation_gradient(_) -> 1.0.
  
  run_neuron(Activation) ->  % spawn an uninitialized neuron (including BiasNeurons)
    spawn_link(ann, run_neuron, [none, [], [], 0, Activation]).
  
  run_neuron(Main, Inputs, Outputs, Token, Activation) ->           % neuron event loop
    receive
      {main_pid, Pid} ->
        run_neuron(Pid, Inputs, Outputs, Token, Activation);
  
      {connect_to_output, Pid} ->
        run_neuron(Main, Inputs, [Pid | Outputs], Token, Activation);
  
      {connect_to_input, PidWeight} ->
        run_neuron(Main, [PidWeight | Inputs], Outputs, Token, Activation);
  
      {status, Token} ->
        status_neuron(Inputs, Outputs, Token),
        run_neuron(Main, Inputs, Outputs, Token + 1, Activation);
      
      {layer_weights, ReplyPid} ->
        W = layer_weights(lists:reverse(Outputs)),
        ReplyPid ! {response_layer_weights, W},
        run_neuron(Main, Inputs, Outputs, Token, Activation);
      
      {weights, ReplyPid} ->
        ReplyPid ! {response_weight, [W || {_I, W} <- lists:reverse(Inputs)]},
        run_neuron(Main, Inputs, Outputs, Token, Activation);
  
      {feed_forward, Token} ->
        fire_neuron(Inputs, Outputs, {feed_forward, Token}, Main, Token, Activation),
        run_neuron(Main, Inputs, Outputs, Token + 1, Activation);
  
      {back_prop, LearningRate, Token} ->
        NewInputs = learn_neuron(Inputs, Outputs, LearningRate, Main, Token, Activation),
        run_neuron(Main, NewInputs, Outputs, Token + 1, Activation)
    end.
  
  %% -----
  