// - - - - - - - - - - - - - - - - - - - - -
// File: neuralnetwork.cpp                  |
// Purpose: implements class NeuralNetwork  |
// Author: Taivo Lints, Estonia             |
// Date: May, 2003                          |
// Copyright: see copyright.txt             |
// - - - - - - - - - - - - - - - - - - - - -

#include "neuralnetwork.h"
#include "neuron.h"
#include <vector>
#include <fstream>
#include <string>
#include <sstream>
using namespace std;

// - - - - - Class NeuralNetwork - - - - -

// ******************************
// * Construction & Destruction *
// ******************************

// - NeuralNetwork constructor - -
// Creates a network from your configuration file. If file not found,
// then you will get an empty network. You can check it by calling
// get_number_of_layers(). It will be zero when network is empty.
NeuralNetwork::NeuralNetwork(char* config_file) {

  load_config_file(config_file);
  create_unset_connections();

  error_limit = 0.1;
  max_iterations = 20000;

  iterations = 0;

}

// - NeuralNetwork copy-constructor - -
NeuralNetwork::NeuralNetwork(const NeuralNetwork& rN) {

  error_limit = rN.error_limit;
  max_iterations = rN.max_iterations;
  iterations = rN.iterations;
  v_int_layers = rN.v_int_layers;
  vPatterns = rN.vPatterns;

  NeuralNetwork& rN2 = const_cast<NeuralNetwork&>(rN);
              // A suspicious thing to do, but compiler will throw warnings
              // about invalid conversions in stl_container when I don't
              // cast away the const-ness. At the moment I don't have time
              // to trace the source of this problem. At least it SEEMS to
              // be a harmless problem...


  // Must NOT point to the neurons of the OTHER NeuralNetwork,
  // must create its own neurons.
  for(vector<Neuron*>::iterator i = rN2.vpNeurons.begin();
      i != rN2.vpNeurons.end(); i++) {

    vpNeurons.push_back(new Neuron(NULL, false));
    *(*(--vpNeurons.end())) = *(*i);
    (*(--vpNeurons.end()))->pvpNeurons = &vpNeurons;
                                   // This neuron now belongs to the vpNeurons
                                   // vector of our new NeuralNetwork.
  }

}

// - Operator= overloading - -
NeuralNetwork& NeuralNetwork::operator=(const NeuralNetwork& rN) {

  if(&rN != this) {   // Check for self-assignment.

    error_limit = rN.error_limit;
    max_iterations = rN.max_iterations;
    iterations = rN.iterations;
    v_int_layers = rN.v_int_layers;
    vPatterns = rN.vPatterns;

    NeuralNetwork& rN2 = const_cast<NeuralNetwork&>(rN);
                // A suspicious thing to do, but compiler will throw warnings
                // about invalid conversions in stl_container when I don't
                // cast away the const-ness. At the moment I don't have time
                // to trace the source of this problem. At least it SEEMS to
                // be a harmless problem...


    // Must NOT point to the neurons of the OTHER NeuralNetwork,
    // must create its own neurons.
    for(vector<Neuron*>::iterator i = rN2.vpNeurons.begin();
        i != rN2.vpNeurons.end(); i++) {

      vpNeurons.push_back(new Neuron(NULL, false));
      *(*(--vpNeurons.end())) = *(*i);
      (*(--vpNeurons.end()))->pvpNeurons = &vpNeurons;
                                     // This neuron now belongs to the vpNeurons
                                     // vector of our new NeuralNetwork.
    }
  }

  return *this;

}


// - NeuralNetwork destructor - -
// Performs clean-up.
NeuralNetwork::~NeuralNetwork() {

  // Deletes all Neurons (to prevent memory leak).
  for(vector<Neuron*>::iterator i = vpNeurons.begin();
      i != vpNeurons.end(); i++)
    delete *i;

}

// *********************************************
// * Functions for getting general information *
// *********************************************

// - Function: get_number_of_inputs() - -
// Returns the number of input nodes.
int NeuralNetwork::get_number_of_inputs() {

  if(get_number_of_layers() == 0)
    return(0);

  return(*v_int_layers.begin());

}

// - Function: get_number_of_outputs() - -
// Returns the number of output nodes.
int NeuralNetwork::get_number_of_outputs() {

  if(get_number_of_layers() == 0)
    return(0);

  return(*(--v_int_layers.end()));

}

// - Function: get_number_of_layers() - -
// Returns the number of layers, including the input layer (which is made of
// buffer nodes, not real neurons).
int NeuralNetwork::get_number_of_layers() {

  return(static_cast<int>(v_int_layers.size()));

}

// - Function: get_number_of_nodes() - -
// Returns the total number of nodes.
int NeuralNetwork::get_number_of_nodes() {

  return(static_cast<int>(vpNeurons.size()));

}

// ************************************************
// * Functions for setting some neuron parameters *
// ************************************************

// - Function: set_treshold - -
// Sets treshold value in all neurons.
void NeuralNetwork::set_treshold(double treshold) {

  for(vector<Neuron*>::iterator i = vpNeurons.begin();
      i != vpNeurons.end(); i++)
    (*i)->treshold = treshold;

}

// - Function: set_slope_parameter - -
// Sets slope_parameter value in all neurons.
void NeuralNetwork::set_slope_parameter(double slope_parameter) {

  for(vector<Neuron*>::iterator i = vpNeurons.begin();
      i != vpNeurons.end(); i++)
    (*i)->slope_parameter = slope_parameter;

}


// ******************************************
// * Functions for basic use of the network *
// ******************************************

// - Function: set_inputs - -
// Sets network inputs. Returns false if size of given input vector is incorrect.
bool NeuralNetwork::set_inputs(vector<double>* pv_dbl_inputvalues) {

  // How many inputs does our network have?
  int num_of_inputs = get_number_of_inputs();

  // Check, if input vector size is correct or not.
  if(static_cast<int>(pv_dbl_inputvalues->size()) != num_of_inputs)
    return(false);

  // Assign input values to buffer neurons in first layer.
  for(int i = 0; i < num_of_inputs; i++)
    vpNeurons[i]->output = (*pv_dbl_inputvalues)[i];

  return(true);

}

// - Function: update() - -
// Updates the network (calls update() for all neurons).
void NeuralNetwork::update() {

  // Update starts from the beginning of vpNeurons and goes linearly through
  // this vector. So when (imaginary) layers are correctly ordered in this
  // vector of neurons, then the information from inputs is propagated through
  // all the network during this "for" cycle.
  for(vector<Neuron*>::iterator i = vpNeurons.begin();
      i != vpNeurons.end(); i++)
    (*i)->update();

}

// - Function: get_outputs - -
// Writes network outputs into given vector. If vector size is incorrect,
// then will fix it.
void NeuralNetwork::get_outputs(vector<double>* pv_outputs) {

  // How many outputs and neurons do we have?
  int num_of_outputs = get_number_of_outputs(),
      num_of_neurons = get_number_of_nodes();

  // If given vector is of wrong size, then will fix it.
  if(static_cast<int>(pv_outputs->size()) != num_of_outputs)
    pv_outputs->resize(num_of_outputs);

  // What is the number of first output neuron in vpNeurons vector?
  int first_output_node = num_of_neurons - num_of_outputs;

  // Writing neuron output values into given vector.
  for(int i = first_output_node; i < num_of_neurons; i++)
    (*pv_outputs)[i - first_output_node] = vpNeurons[i]->output;

}

// **************************************************
// * Functions for setting some training parameters *
// **************************************************

// - Function: set_learning_rate - -
// Sets learning rate in all neurons.
void NeuralNetwork::set_learning_rate(double learning_rate) {

  for(vector<Neuron*>::iterator i = vpNeurons.begin();
      i != vpNeurons.end(); i++)
    (*i)->learning_rate = learning_rate;

}

// - Function: set_max_weight - -
// Sets max_weight value in all neurons.
void NeuralNetwork::set_max_weight(double max_weight) {

  for(vector<Neuron*>::iterator i = vpNeurons.begin();
      i != vpNeurons.end(); i++)
    (*i)->max_weight = max_weight;

}

// - Function: add_training_pattern - -
// Adds a training pattern to vPatterns.
void NeuralNetwork::add_training_pattern(Pattern myPattern) {

  vPatterns.push_back(myPattern);

}

// - Function: verify_all_patterns - -
// Verifies that the number of input and output values in
// all patterns is correct. Returns "true" if all OK.
bool NeuralNetwork::verify_all_patterns() {

  // How many inputs and outputs do we have?
  int num_of_inputs = get_number_of_inputs(),
      num_of_outputs = get_number_of_outputs();

  // Verify all patterns
  for(vector<Pattern>::iterator i = vPatterns.begin();
      i != vPatterns.end(); i++)
    if( (static_cast<int>((*i).vInputs.size()) != num_of_inputs) or
        (static_cast<int>((*i).vOutputs.size()) != num_of_outputs) )
      return(false);

  return(true);

}

// - Function: erase_all_patterns - -
// Deletes all training patterns.
void NeuralNetwork::erase_all_patterns() {

  vPatterns.clear();

}

// - Function: get_number_of_patterns - -
// Returns the number of training patterns in vPatterns vector.
int NeuralNetwork::get_number_of_patterns() {

  return(static_cast<int>(vPatterns.size()));

}


// **************************
// * Functions for training *
// **************************

// - Function: train - -
// Trains network using backpropagation algorithm. Returns 0,
// if training successful; returns 1, if goal not reached;
// returns 2, if problems with patterns. Also sets iterations
// parameter (see "neuralnetwork.h" for more information).

// If you don't want to use training file (a rare case, though),
// then call train(NULL).

int NeuralNetwork::train(char* training_file) {

  // If training file IS given, then load it.
  if(training_file != NULL) {
    erase_all_patterns();   // Deletes old data.
    if(load_training_file(training_file) > 0)  // Reads new data from file.
      return(2);
  }

  // If wrong patterns, then exit function.
  if(not verify_all_patterns())
    return(2);

  int i;  // We want to preserve the value of "for" cycle iterator beyond
          // the scope of "for".

  // Repeats training until ready, or until max_iterations limit reached.
  for(i = 0; i < max_iterations; i++) {

    // First we have to make sure if training is necessary (maybe network
    // already behaves like we want?).
    
    bool goal_reached = true;

    // Goes through all patterns.
    for(vector<Pattern>::iterator iter = vPatterns.begin();
        iter != vPatterns.end(); iter++) {

      set_inputs( &((*iter).vInputs) );
      update();

      vector<double> v_outputs;
      get_outputs(&v_outputs);

      // Checks, if outputs are inside or outside allowed limits.
      for(int output_number = 0;
          output_number < static_cast<int>(v_outputs.size());
          output_number++) {

        double target_output = (*iter).vOutputs[output_number],
               output = v_outputs[output_number],
               error = abs(target_output - output);

        if(error > error_limit) {
          goal_reached = false;
          goto learn;  // Using "goto" is generally considered as a poor
                       // programming style. However, this is one of the
                       // rare cases where using "goto" is not strongly
                       // deprecated (i.e. jumping out of multiple loops,
                       // alternatively done by writing several flag
                       // testings and breaks. Of course, the situation
                       // must still be analyzed carefully before using "goto"
                       // to avoid breaking your code (e.g. nonlocal goto
                       // may cause skipping some destructors -- a very bad
                       // thing to do!)).
        }
      }
    }

    // If goal is reached, then exits this function.
    if(goal_reached) {
      iterations = i;
      return(0);
    }

    // If goal is NOT reached, then performs a training procedure.
    learn:  // A label for goto.

    // What is the number of first output neuron in vpNeurons vector?
    int first_output_node = get_number_of_nodes() - get_number_of_outputs();
    
    // Goes through all patterns.
    for(vector<Pattern>::iterator iter = vPatterns.begin();
        iter != vPatterns.end(); iter++) {

      set_inputs( &((*iter).vInputs) );
      update();

      vector<double> v_outputs;
      get_outputs(&v_outputs);

      // Sets error value in output neurons.
      for(int output_number = 0;
          output_number < static_cast<int>(v_outputs.size());
          output_number++) {

        double target_output = (*iter).vOutputs[output_number],
               output = v_outputs[output_number];

        vpNeurons[first_output_node + output_number]->
          error = target_output - output;
      }

      // Commands each neuron to learn (and to backpropagate the error).
      // Goes through vpNeurons in reverse direction because of the
      // BACKpropagation algorithm.
      for(vector<Neuron*>::reverse_iterator r = vpNeurons.rbegin();
          r != vpNeurons.rend(); r++)
        (*r)->learn();
    }
  }

  // Apparently the goal was not reached.
  iterations = i;
  return(1);

}


// ************************************
// * Functions for saving the network *
// ************************************

// - Function: save_network - -
// Saves your network into a file. You can use this generated file
// for initializing a new network (instead of the usual configuration
// file, where weights are not described).
void NeuralNetwork::save_network(char* savefile) {

  // Creates an output file (if already exists, then overwrites it).
  ofstream out_file(savefile);

  // Sets some output parameters.
  out_file.setf(ios::fixed, ios::floatfield);
  out_file.precision(30);  // Won't ever be so big, but it automatically cuts
                           // down to the max. possible precision. I hope ;)

  // If no neurons, then nothing to save.
  if(get_number_of_nodes() == 0) {
    out_file << "Network is empty, nothing to save here." << endl;
    return;
  }

  // Saves input layer information.
  out_file << "input_layer: " << v_int_layers[0] << endl;

  // For counting nodes (to detect the end of current layer).
  int node_counter = 0;

  int layer_number = 0;

  // Goes through all neurons.
  for(int neuron_number = 0;
      neuron_number < get_number_of_nodes(); neuron_number++) {

    // Checking the end of a layer.
    if(node_counter == v_int_layers[layer_number]) {
      layer_number++;
      out_file << endl << "comp_layer: " << v_int_layers[layer_number] << endl;
      node_counter = 0;
    }

    if(layer_number > 0) {

      out_file << "  weights:";

      // Saves all weights of this neuron.
      for(int connection_number = 0;
          connection_number <
            static_cast<int>(vpNeurons[neuron_number]->vpConnections.size());
          connection_number++) {

        double this_weight = vpNeurons[neuron_number]->
                               vpConnections[connection_number]->weight;

        // For keeping columns a bit more straight.
        if(abs(this_weight) < 10)
          out_file << " ";
        
        if(this_weight < 0)
          out_file << " " << this_weight;
        else
          out_file << "  " << this_weight;

      }
    out_file << endl;
    }

    node_counter++;
      
  }

  // Saves some other parameters, too (assuming that all neurons
  // have same parameters!).

  out_file << endl;
  out_file << "treshold: " << get_treshold() << endl;
  out_file << "slope_parameter: " << get_slope_parameter() << endl;
  out_file << "max_weight: " << get_max_weight() << endl;

}


// ************************************************
// * Functions for getting some neuron parameters *
// ************************************************

// All these functions assume, that all neurons have similar
// parameters. So they just use values taken from the first
// neuron in vpNeurons vector (which is actually an input node,
// but still should have all these parameters set correctly).
// If network is empty, then all these functions return 0.

// - Function: get_treshold - -
double NeuralNetwork::get_treshold() {

  if(get_number_of_nodes() == 0)
    return(0);

  return(vpNeurons[0]->treshold);

}

// - Function: get_slope_parameter - -
double NeuralNetwork::get_slope_parameter() {

  if(get_number_of_nodes() == 0)
    return(0);

  return(vpNeurons[0]->slope_parameter);

}

// - Function: get_learning_rate - -
double NeuralNetwork::get_learning_rate() {

  if(get_number_of_nodes() == 0)
    return(0);

  return(vpNeurons[0]->learning_rate);

}

// - Function: get_max_weight - -
// Returns the value of parameter max_weight, NOT the maximum
// weight found in network.
double NeuralNetwork::get_max_weight() {

  if(get_number_of_nodes() == 0)
    return(0);

  return(vpNeurons[0]->max_weight);

}


// *****************
// * Private stuff *
// *****************

// You can't use that stuff from outside code.
// That's why the "density" of comments is lower here...

// - Function: load_config_file - -
// Loads info from configuration file, if this file exists.
// It loads as much as it can without raising any alarms,
// so make sure your configuration file is correct.
void NeuralNetwork::load_config_file(char* config_file) {

  // Opens the configuration file
  ifstream inp_file(config_file);

  // If no file found, then exits this function.
  if(!inp_file)
    return;

  // Some variables.
  string inp_line_string;  // For storing a line read from file
  
  int neuron_number = 0;  // Neuron counter for weight adding.

  // Now we will go through the file, reading it line by line.
  while(getline(inp_file, inp_line_string)) {

    // Converts string to stringstream for easier use.
    stringstream inp_line_stream(inp_line_string);

    // Some variables.
    string param_label = "";
    int num_of_neurons = 0;
    double inp_treshold,
           inp_slope_parameter;

    // Extracts the first non-white-space word.
    inp_line_stream >> param_label;

    // Creates input layer.
    if(param_label == "input_layer:") {
      inp_line_stream >> num_of_neurons;

      for(int i = 0; i < num_of_neurons; i++)  // Adds neurons.
        vpNeurons.push_back(new Neuron(&vpNeurons, false));

      v_int_layers.push_back(num_of_neurons);  // Adds layer size.
    }

    // Creates computational layer.
    if(param_label == "comp_layer:") {
      inp_line_stream >> num_of_neurons;

      for(int i = 0; i < num_of_neurons; i++)  // Adds neurons.
        vpNeurons.push_back(new Neuron(&vpNeurons, true));

      if(num_of_neurons > 0)      // Adds layer only if layer exists.
        v_int_layers.push_back(num_of_neurons);

      neuron_number = 0;  // Resets neuron counter for weight adding.
    }

    // Adjusts weights, if they are given (no max_weight checking!)
    if(param_label == "weights:") {

      int size_this_lr = *(v_int_layers.end() - 1);

      if( (get_number_of_layers() < 2) or // Input layer can't have connections!
          (neuron_number >= size_this_lr) )  // Too much "weights:" are given!
        break;

      int size_prev_lr = *(v_int_layers.end() - 2),
          start_of_this_lr = get_number_of_nodes() - size_this_lr,
          start_of_prev_lr = start_of_this_lr - size_prev_lr,
          connections_added = 0;

      double weight;

      while( (inp_line_stream >> weight) and
             (connections_added < size_prev_lr) ) {
        vpNeurons[start_of_this_lr + neuron_number]->
          add_connection(start_of_prev_lr + connections_added, weight);

        connections_added++;
      }

      neuron_number++;
    }

    // Sets treshold values.
    if(param_label == "treshold:")
      if(inp_line_stream >> inp_treshold)
        set_treshold(inp_treshold);

    // Sets slope_parameter values.
    if(param_label == "slope_parameter:")
      if(inp_line_stream >> inp_slope_parameter)
        set_slope_parameter(inp_slope_parameter);

  }

}

// - Function: create_unset_connections - -
// Creates missing connections so that network will be a
// fully connected multilayer network.
void NeuralNetwork::create_unset_connections() {

  // If less than two layers then no connections to create.
  if(get_number_of_layers() < 2)
    return;

  // Initializes random number generator. 
  srand(time(0));
  
  // Pointer to the first neuron in first comp_layer.
  vector<Neuron*>::iterator i = vpNeurons.begin() + v_int_layers[0];

  int current_layer = 1,
      size_prev_lr = v_int_layers[0],
      size_this_lr = v_int_layers[1],
      start_of_prev_lr = 0,
      start_of_this_lr = size_prev_lr,
      neuron_number = 0;

  // Will go through the vpNeurons one neuron at a time.
  while(i != vpNeurons.end()) {
    
    // First node from previous layer without a connection to this neuron.
    int source = start_of_prev_lr +
      static_cast<int>((*i)->vpConnections.size());

    // Adds all missing connections.
    while(static_cast<int>((*i)->vpConnections.size()) < size_prev_lr) {

      // Finds a random weight in range -max_weight..+max_weight.
      double help_value = (1 - static_cast<double>(rand()) * 2 / RAND_MAX),
             rnd_weight = (*i)->max_weight * help_value;

      (*i)->add_connection(source, rnd_weight);

      source++;
    }

    neuron_number++;

    // Update the layer and neuron counting.
    if( (neuron_number == size_this_lr) and
        (current_layer < get_number_of_layers() - 1) ) {
  
      start_of_prev_lr += size_prev_lr;
      start_of_this_lr += size_this_lr;

      current_layer++;

      size_prev_lr = v_int_layers[current_layer - 1];
      size_this_lr = v_int_layers[current_layer];

      neuron_number = 0;

    }

    i++;

  }

}

// - Function: load_training_file - -
// Loads info from training file. Returns the number of loading errors.
// But be aware that zero errors does not guarantee that everything is
// OK. Use verify_all_patterns(), too.
int NeuralNetwork::load_training_file(char* training_file) {

  // Opens the training file.
  ifstream inp_file(training_file);

  // If no file found, then exits this function.
  if(!inp_file)
    return(1);

  // Some variables.
  int loading_errors = 0;  // How many errors found in training file.
  string inp_line_string;  // For storing a line read from file

  // Now we will go through the file, reading it line by line.
  while(getline(inp_file, inp_line_string)) {

    // Converts string to stringstream for easier use.
    stringstream inp_line_stream(inp_line_string);

    // Some variables.
    string param_label = "";
    int inp_max_iterations;
    double inp_error_limit,
           inp_learning_rate,
           inp_max_weight;

    // Extracts the first non-white-space word.
    inp_line_stream >> param_label;

    // Creates a pattern.
    if(param_label == "pattern:") {
      Pattern ConstrPattern;
      bool inputs = true;  // First numbers represent inputs.
      string word;

      // Goes through the line word by word.
      while(inp_line_stream >> word) {
  
        stringstream helper_stream(word),
                     helper_stream_copy(word);

        double number;

        if(helper_stream >> number) {
          if(inputs)
            ConstrPattern.vInputs.push_back(number);
          else
            ConstrPattern.vOutputs.push_back(number);
        }
        else {
          helper_stream_copy >> word;

          if(word == "->")
            inputs = false;  // After "->" will come outputs.
          else
            loading_errors++;
        }
      }
      add_training_pattern(ConstrPattern);
    }

    // Sets error_limit value.
    if(param_label == "error_limit:")
      if(inp_line_stream >> inp_error_limit)
        error_limit = inp_error_limit;

    // Sets max_iterations value.
    if(param_label == "max_iterations:")
      if(inp_line_stream >> inp_max_iterations)
        max_iterations = inp_max_iterations;

    // Sets learning_rate values.
    if(param_label == "learning_rate:")
      if(inp_line_stream >> inp_learning_rate)
        set_learning_rate(inp_learning_rate);
    
    // Sets max_weight values.
    if(param_label == "max_weight:")
      if(inp_line_stream >> inp_max_weight)
        set_max_weight(inp_max_weight);
    
  }

  return(loading_errors);

}