// (C) 2002 Jasper Bedaux, ITFA, University of Amsterdam
// http://www.bedaux.net/jasper/
// C++ header for layered neural network with recurrent connections

#ifndef NEURAL_NET_H
#define NEURAL_NET_H

#include <vector>
#include <string>

class Synapse;
class Neuron;
class Network;

enum Input {input}; // for input size network/neuron
enum Hidden {hidden}; // for size of each hidden layer
enum Output {output}; // for output size network/neuron
enum Layers {layers}; // for number of hidden layers

class Synapse {
public:
// functions to get postsynaptic neuron and weight
  Neuron* post() const { return n; }
  float weight() const { return w; }
// functions to set postsynaptic neuron and set/change weight
  void grow(float delta) { w += delta; }
//  alternative: add to weight only if the sign of w will not change
//  void grow(float delta) { if ((delta / w) > -1.) w += delta; }
  void setPost(Neuron* neuron) { n = neuron; }
  void setWeight(float value) { w = value; }
// constructors/destructor
  Synapse(Neuron* neuron = 0, float value = 0.) : n(neuron), w(value) {}
  Synapse(const Synapse& s) : n(s.n), w(s.w) {} // copy constructor
  Synapse& operator=(const Synapse& s) { // assignment operator
    if (&s != this) { n = s.n, w = s.w; }
    return *this; }
  ~Synapse() {}
private:
  Neuron* n; // pointer to postsynaptic neuron
  float w; // weight of connection
};

class Neuron {
public:
// functions returning state variables
  bool isActive() const { return state; }
  float potential() const { return h; }
  float threshold() const { return theta; }
// number of outgoing and incoming connections
  int size() const { return axon.size(); }
  int size(Input) const { return ni; }
  int size(Output) const { return size(); }
// functions to change state variables
  void setActivity(bool a) { state = a; }
  void raisePotential(float delta) { h += delta; }
  void resetPotential() { h = 0.; }
  void setThreshold(float t) { theta = t; }
// make connection to neuron post with weight w
  void connect(Neuron* post, float w = 0.) {
    axon.push_back(Synapse(post, w));
    ++post->ni;
  }
// array indexing operator
  Synapse& operator[](int i) { return axon[i]; }
// constructor, destructor
  Neuron() : state(false), h(0.), theta(0.), ni(0) {}
  ~Neuron() {}
private:
  bool state;
  float h; // membrane potential
  float theta; // threshold potential
  int ni; // number of incoming connections
  std::vector<Synapse> axon;
  Neuron(const Neuron&); // copy constructor not available
  void operator=(const Neuron&); // assignment operator unavailable
};

class Network {
public:
  int size() const { return n; }
  int size(Input) const { return ni; }
  int size(Hidden) const { return nh; }
  int size(Output) const { return no; }
  int size(Layers) const { return nl; } // number of hidden layers
  int offset(Input) const { return 0; }
  int offset(Hidden) const { return ni; }
  int offset(Output) const { return n - no; }
// offset of hidden layer starting with 0 for first hidden layer
  int offset(int number) const { return ni + nh * number; }
  Neuron* begin() const { return neuron; }
  Neuron* end() const { return output_end; }
  Neuron* begin(Input) const { return neuron; }
  Neuron* end(Input) const { return input_end; }
  Neuron* begin(Hidden) const { return input_end; }
  Neuron* end(Hidden) const { return hidden_end; }
  Neuron* begin(Output) const { return hidden_end; }
  Neuron* end(Output) const { return output_end; }
// begin/end of hidden layer numbered 0, 1, ...
  Neuron* begin(int number) const { return input_end + nh * number; }
  Neuron* end(int number) const { return begin(number) + nh; }
  Neuron* const neuron; // pointer to array of neurons
  Neuron& operator[](int i) { return neuron[i]; }

  void setInput(const std::vector<bool>& in);
  const std::vector<bool>& getOutput() const;
// simulate one timestep for feed-forward networks
// if nah and nao are non-zero, extremal dynamics is used
  void findFixed(const std::vector<bool>& in, int nah = 0, int nao = 0);
// find fixedpoint for recurrent networks
// returns number of steps needed or 0 if not found in tmax
// uf nah and nao are non-zero, extremal dynamics is used
  int findFixed(int tmax, const std::vector<bool>& in, int nah = 0, int nao = 0);

  Network( // constructor allocates memory and builds network
    int ninput, // number of neurons in input layer
    int nhidden, // number of neurons in each hidden layer
    int noutput, // number of neurons in output layer
    int hiddenlayers, // number of hidden layers
// chances[0] to chances[hiddenlayers] are downward connection chances
// chances[hiddenlayers + 1] is lateral connection chance
// chances[hiddenlayers + 2] to chances[2 * hiddenlayers] are upward connection chances
    const std::vector<float>& chances) throw (std::string);
// constructor for simple feed-forward net with one hidden layer
  Network(int ninput, int nhidden, int noutput, float ph, float po)
    throw (std::string);
  ~Network() { delete []neuron; }
private:
  const int n; // total number of neurons
  const int ni; // number of input neurons
  const int nh; // number of neurons in each hidden layer
  const int no; // number of output neurons
  const int nl; // number of hidden layers
  Neuron* const input_end;
  Neuron* const hidden_end;
  Neuron* const output_end;
  Network(const Network&); // copy constructor not available
  void operator=(const Network&); // assignment operator unavailable
};

// connect source to destination with specified chance
void connect(Neuron* src_begin, Neuron* src_end,
  Neuron* dest_begin, Neuron* dest_end, float chance = 1.);
// let the active neurons in the specified region fire: update potentials
void fire(Neuron* start, Neuron* end);
// update states of neurons in region, if na != 0 use extremal dynamics
void update(Neuron* start, Neuron* end, int na = 0);
// Hebbian learning rule in specified region
void Hebb(Neuron* start, Neuron* end, float eta, float kappa, float delta);
// anti-Hebbian learning rule
void antiHebb(Neuron* start, Neuron* end, float rho, float alpha, float delta);

#endif // NEURAL_NET_H