/*
  Fast Artificial Neural Network Library (fann)
  Copyright (C) 2003-2016 Steffen Nissen (steffen.fann@gmail.com)

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <math.h>

#include "config.h"
#include "fann.h"

/* #define FANN_NO_SEED */

FANN_EXTERNAL struct fann *FANN_API fann_create_standard(unsigned int num_layers, ...) {
  struct fann *ann;
  va_list layer_sizes;
  int i;
  int status;
  int arg;
  unsigned int *layers = (unsigned int *)calloc(num_layers, sizeof(unsigned int));

  if (layers == NULL) {
    fann_error(NULL, FANN_E_CANT_ALLOCATE_MEM);
    return NULL;
  }

  va_start(layer_sizes, num_layers);

  status = 1;
  for (i = 0; i < (int)num_layers; i++) {
    arg = va_arg(layer_sizes, unsigned int);
    if (arg < 0 || arg > 1000000) status = 0;
    layers[i] = arg;
  }
  va_end(layer_sizes);

  if (!status) {
    fann_error(NULL, FANN_E_CANT_ALLOCATE_MEM);
    free(layers);
    return NULL;
  }

  ann = fann_create_standard_array(num_layers, layers);

  free(layers);

  return ann;
}

FANN_EXTERNAL struct fann *FANN_API fann_create_standard_array(unsigned int num_layers,
                                                               const unsigned int *layers) {
  return fann_create_sparse_array(1, num_layers, layers);
}

FANN_EXTERNAL struct fann *FANN_API fann_create_sparse(float connection_rate,
                                                       unsigned int num_layers, ...) {
  struct fann *ann;
  va_list layer_sizes;
  int i;
  int status;
  int arg;
  unsigned int *layers = (unsigned int *)calloc(num_layers, sizeof(unsigned int));

  if (layers == NULL) {
    fann_error(NULL, FANN_E_CANT_ALLOCATE_MEM);
    return NULL;
  }

  va_start(layer_sizes, num_layers);
  status = 1;
  for (i = 0; i < (int)num_layers; i++) {
    arg = va_arg(layer_sizes, unsigned int);
    if (arg < 0 || arg > 1000000) status = 0;
    layers[i] = arg;
  }
  va_end(layer_sizes);

  if (!status) {
    fann_error(NULL, FANN_E_CANT_ALLOCATE_MEM);
    free(layers);
    return NULL;
  }

  ann = fann_create_sparse_array(connection_rate, num_layers, layers);
  free(layers);

  return ann;
}

FANN_EXTERNAL struct fann *FANN_API fann_create_sparse_array(float connection_rate,
                                                             unsigned int num_layers,
                                                             const unsigned int *layers) {
  struct fann_layer *layer_it, *last_layer, *prev_layer;
  struct fann *ann;
  struct fann_neuron *neuron_it, *last_neuron, *random_neuron, *bias_neuron;
#ifdef DEBUG
  unsigned int prev_layer_size;
#endif
  unsigned int num_neurons_in, num_neurons_out, i, j;
  unsigned int min_connections, max_connections, num_connections;
  unsigned int connections_per_neuron, allocated_connections;
  unsigned int random_number, found_connection, tmp_con;

#ifdef FIXEDFANN
  unsigned int multiplier;
#endif
  if (connection_rate > 1) {
    connection_rate = 1;
  }

  fann_seed_rand();

  /* allocate the general structure */
  ann = fann_allocate_structure(num_layers);
  if (ann == NULL) {
    fann_error(NULL, FANN_E_CANT_ALLOCATE_MEM);
    return NULL;
  }

  ann->connection_rate = connection_rate;
#ifdef FIXEDFANN
  multiplier = ann->multiplier;
  fann_update_stepwise(ann);
#endif

  /* determine how many neurons there should be in each layer */
  i = 0;
  for (layer_it = ann->first_layer; layer_it != ann->last_layer; layer_it++) {
    /* we do not allocate room here, but we make sure that
     * last_neuron - first_neuron is the number of neurons */
    layer_it->first_neuron = NULL;
    layer_it->last_neuron = layer_it->first_neuron + layers[i++] + 1; /* +1 for bias */
    ann->total_neurons += (unsigned int)(layer_it->last_neuron - layer_it->first_neuron);
  }

  ann->num_output =
      (unsigned int)((ann->last_layer - 1)->last_neuron - (ann->last_layer - 1)->first_neuron - 1);
  ann->num_input =
      (unsigned int)(ann->first_layer->last_neuron - ann->first_layer->first_neuron - 1);

  /* allocate room for the actual neurons */
  fann_allocate_neurons(ann);
  if (ann->errno_f == FANN_E_CANT_ALLOCATE_MEM) {
    fann_destroy(ann);
    return NULL;
  }

#ifdef DEBUG
  printf("creating network with connection rate %f\n", connection_rate);
  printf("input\n");
  printf("  layer       : %d neurons, 1 bias\n",
         (int)(ann->first_layer->last_neuron - ann->first_layer->first_neuron - 1));
#endif

  num_neurons_in = ann->num_input;
  for (layer_it = ann->first_layer + 1; layer_it != ann->last_layer; layer_it++) {
    num_neurons_out = (unsigned int)(layer_it->last_neuron - layer_it->first_neuron - 1);
    /*�if all neurons in each layer should be connected to at least one neuron
     * in the previous layer, and one neuron in the next layer.
     * and the bias node should be connected to the all neurons in the next layer.
     * Then this is the minimum amount of neurons */
    min_connections = fann_max(num_neurons_in, num_neurons_out); /* not calculating bias */
    max_connections = num_neurons_in * num_neurons_out;          /* not calculating bias */
    num_connections =
        fann_max(min_connections, (unsigned int)(0.5 + (connection_rate * max_connections))) +
        num_neurons_out;

    connections_per_neuron = num_connections / num_neurons_out;
    allocated_connections = 0;
    /* Now split out the connections on the different neurons */
    for (i = 0; i != num_neurons_out; i++) {
      layer_it->first_neuron[i].first_con = ann->total_connections + allocated_connections;
      allocated_connections += connections_per_neuron;
      layer_it->first_neuron[i].last_con = ann->total_connections + allocated_connections;

      layer_it->first_neuron[i].activation_function = FANN_SIGMOID_STEPWISE;
#ifdef FIXEDFANN
      layer_it->first_neuron[i].activation_steepness = ann->multiplier / 2;
#else
      layer_it->first_neuron[i].activation_steepness = 0.5;
#endif

      if (allocated_connections < (num_connections * (i + 1)) / num_neurons_out) {
        layer_it->first_neuron[i].last_con++;
        allocated_connections++;
      }
    }

    /* bias neuron also gets stuff */
    layer_it->first_neuron[i].first_con = ann->total_connections + allocated_connections;
    layer_it->first_neuron[i].last_con = ann->total_connections + allocated_connections;

    ann->total_connections += num_connections;

    /* used in the next run of the loop */
    num_neurons_in = num_neurons_out;
  }

  fann_allocate_connections(ann);
  if (ann->errno_f == FANN_E_CANT_ALLOCATE_MEM) {
    fann_destroy(ann);
    return NULL;
  }

  if (connection_rate >= 1) {
#ifdef DEBUG
    prev_layer_size = ann->num_input + 1;
#endif
    prev_layer = ann->first_layer;
    last_layer = ann->last_layer;
    for (layer_it = ann->first_layer + 1; layer_it != last_layer; layer_it++) {
      last_neuron = layer_it->last_neuron - 1;
      for (neuron_it = layer_it->first_neuron; neuron_it != last_neuron; neuron_it++) {
        tmp_con = neuron_it->last_con - 1;
        for (i = neuron_it->first_con; i != tmp_con; i++) {
          ann->weights[i] = (fann_type)fann_random_weight();
          /* these connections are still initialized for fully connected networks, to allow
           * operations to work, that are not optimized for fully connected networks.
           */
          ann->connections[i] = prev_layer->first_neuron + (i - neuron_it->first_con);
        }

        /* bias weight */
        ann->weights[tmp_con] = (fann_type)fann_random_bias_weight();
        ann->connections[tmp_con] = prev_layer->first_neuron + (tmp_con - neuron_it->first_con);
      }
#ifdef DEBUG
      prev_layer_size = layer_it->last_neuron - layer_it->first_neuron;
#endif
      prev_layer = layer_it;
#ifdef DEBUG
      printf("  layer       : %d neurons, 1 bias\n", prev_layer_size - 1);
#endif
    }
  } else {
    /* make connections for a network, that are not fully connected */

    /* generally, what we do is first to connect all the input
     * neurons to a output neuron, respecting the number of
     * available input neurons for each output neuron. Then
     * we go through all the output neurons, and connect the
     * rest of the connections to input neurons, that they are
     * not allready connected to.
     */

    /* All the connections are cleared by calloc, because we want to
     * be able to see which connections are allready connected */

    for (layer_it = ann->first_layer + 1; layer_it != ann->last_layer; layer_it++) {
      num_neurons_out = (unsigned int)(layer_it->last_neuron - layer_it->first_neuron - 1);
      num_neurons_in =
          (unsigned int)((layer_it - 1)->last_neuron - (layer_it - 1)->first_neuron - 1);

      /* first connect the bias neuron */
      bias_neuron = (layer_it - 1)->last_neuron - 1;
      last_neuron = layer_it->last_neuron - 1;
      for (neuron_it = layer_it->first_neuron; neuron_it != last_neuron; neuron_it++) {
        ann->connections[neuron_it->first_con] = bias_neuron;
        ann->weights[neuron_it->first_con] = (fann_type)fann_random_bias_weight();
      }

      /* then connect all neurons in the input layer */
      last_neuron = (layer_it - 1)->last_neuron - 1;
      for (neuron_it = (layer_it - 1)->first_neuron; neuron_it != last_neuron; neuron_it++) {
        /* random neuron in the output layer that has space
         * for more connections */
        do {
          random_number = (int)(0.5 + fann_rand(0, num_neurons_out - 1));
          random_neuron = layer_it->first_neuron + random_number;
          /* checks the last space in the connections array for room */
        } while (ann->connections[random_neuron->last_con - 1]);

        /* find an empty space in the connection array and connect */
        for (i = random_neuron->first_con; i < random_neuron->last_con; i++) {
          if (ann->connections[i] == NULL) {
            ann->connections[i] = neuron_it;
            ann->weights[i] = (fann_type)fann_random_weight();
            break;
          }
        }
      }

      /* then connect the rest of the unconnected neurons */
      last_neuron = layer_it->last_neuron - 1;
      for (neuron_it = layer_it->first_neuron; neuron_it != last_neuron; neuron_it++) {
        /* find empty space in the connection array and connect */
        for (i = neuron_it->first_con; i < neuron_it->last_con; i++) {
          /* continue if allready connected */
          if (ann->connections[i] != NULL) continue;

          do {
            found_connection = 0;
            random_number = (int)(0.5 + fann_rand(0, num_neurons_in - 1));
            random_neuron = (layer_it - 1)->first_neuron + random_number;

            /* check to see if this connection is allready there */
            for (j = neuron_it->first_con; j < i; j++) {
              if (random_neuron == ann->connections[j]) {
                found_connection = 1;
                break;
              }
            }

          } while (found_connection);

          /* we have found a neuron that is not allready
           * connected to us, connect it */
          ann->connections[i] = random_neuron;
          ann->weights[i] = (fann_type)fann_random_weight();
        }
      }

#ifdef DEBUG
      printf("  layer       : %d neurons, 1 bias\n", num_neurons_out);
#endif
    }

    /* TODO it would be nice to have the randomly created
     * connections sorted for smoother memory access.
     */
  }

#ifdef DEBUG
  printf("output\n");
#endif

  return ann;
}

FANN_EXTERNAL struct fann *FANN_API fann_create_shortcut(unsigned int num_layers, ...) {
  struct fann *ann;
  int i;
  int status;
  int arg;
  va_list layer_sizes;
  unsigned int *layers = (unsigned int *)calloc(num_layers, sizeof(unsigned int));

  if (layers == NULL) {
    fann_error(NULL, FANN_E_CANT_ALLOCATE_MEM);
    return NULL;
  }

  va_start(layer_sizes, num_layers);
  status = 1;
  for (i = 0; i < (int)num_layers; i++) {
    arg = va_arg(layer_sizes, unsigned int);
    if (arg < 0 || arg > 1000000) status = 0;
    layers[i] = arg;
  }
  va_end(layer_sizes);

  if (!status) {
    fann_error(NULL, FANN_E_CANT_ALLOCATE_MEM);
    free(layers);
    return NULL;
  }

  ann = fann_create_shortcut_array(num_layers, layers);

  free(layers);

  return ann;
}

FANN_EXTERNAL struct fann *FANN_API fann_create_shortcut_array(unsigned int num_layers,
                                                               const unsigned int *layers) {
  struct fann_layer *layer_it, *layer_it2, *last_layer;
  struct fann *ann;
  struct fann_neuron *neuron_it, *neuron_it2 = 0;
  unsigned int i;
  unsigned int num_neurons_in, num_neurons_out;

#ifdef FIXEDFANN
  unsigned int multiplier;
#endif
  fann_seed_rand();

  /* allocate the general structure */
  ann = fann_allocate_structure(num_layers);
  if (ann == NULL) {
    fann_error(NULL, FANN_E_CANT_ALLOCATE_MEM);
    return NULL;
  }

  ann->connection_rate = 1;
  ann->network_type = FANN_NETTYPE_SHORTCUT;
#ifdef FIXEDFANN
  multiplier = ann->multiplier;
  fann_update_stepwise(ann);
#endif

  /* determine how many neurons there should be in each layer */
  i = 0;
  for (layer_it = ann->first_layer; layer_it != ann->last_layer; layer_it++) {
    /* we do not allocate room here, but we make sure that
     * last_neuron - first_neuron is the number of neurons */
    layer_it->first_neuron = NULL;
    layer_it->last_neuron = layer_it->first_neuron + layers[i++];
    if (layer_it == ann->first_layer) {
      /* there is a bias neuron in the first layer */
      layer_it->last_neuron++;
    }

    ann->total_neurons += (unsigned int)(layer_it->last_neuron - layer_it->first_neuron);
  }

  ann->num_output =
      (unsigned int)((ann->last_layer - 1)->last_neuron - (ann->last_layer - 1)->first_neuron);
  ann->num_input =
      (unsigned int)(ann->first_layer->last_neuron - ann->first_layer->first_neuron - 1);

  /* allocate room for the actual neurons */
  fann_allocate_neurons(ann);
  if (ann->errno_f == FANN_E_CANT_ALLOCATE_MEM) {
    fann_destroy(ann);
    return NULL;
  }

#ifdef DEBUG
  printf("creating fully shortcut connected network.\n");
  printf("input\n");
  printf("  layer       : %d neurons, 1 bias\n",
         (int)(ann->first_layer->last_neuron - ann->first_layer->first_neuron - 1));
#endif

  num_neurons_in = ann->num_input;
  last_layer = ann->last_layer;
  for (layer_it = ann->first_layer + 1; layer_it != last_layer; layer_it++) {
    num_neurons_out = (unsigned int)(layer_it->last_neuron - layer_it->first_neuron);

    /* Now split out the connections on the different neurons */
    for (i = 0; i != num_neurons_out; i++) {
      layer_it->first_neuron[i].first_con = ann->total_connections;
      ann->total_connections += num_neurons_in + 1;
      layer_it->first_neuron[i].last_con = ann->total_connections;

      layer_it->first_neuron[i].activation_function = FANN_SIGMOID_STEPWISE;
#ifdef FIXEDFANN
      layer_it->first_neuron[i].activation_steepness = ann->multiplier / 2;
#else
      layer_it->first_neuron[i].activation_steepness = 0.5;
#endif
    }

#ifdef DEBUG
    printf("  layer       : %d neurons, 0 bias\n", num_neurons_out);
#endif
    /* used in the next run of the loop */
    num_neurons_in += num_neurons_out;
  }

  fann_allocate_connections(ann);
  if (ann->errno_f == FANN_E_CANT_ALLOCATE_MEM) {
    fann_destroy(ann);
    return NULL;
  }

  /* Connections are created from all neurons to all neurons in later layers
   */
  num_neurons_in = ann->num_input + 1;
  for (layer_it = ann->first_layer + 1; layer_it != last_layer; layer_it++) {
    for (neuron_it = layer_it->first_neuron; neuron_it != layer_it->last_neuron; neuron_it++) {
      i = neuron_it->first_con;
      for (layer_it2 = ann->first_layer; layer_it2 != layer_it; layer_it2++) {
        for (neuron_it2 = layer_it2->first_neuron; neuron_it2 != layer_it2->last_neuron;
             neuron_it2++) {
          ann->weights[i] = (fann_type)fann_random_weight();
          ann->connections[i] = neuron_it2;
          i++;
        }
      }
    }
    num_neurons_in += (unsigned int)(layer_it->last_neuron - layer_it->first_neuron);
  }

#ifdef DEBUG
  printf("output\n");
#endif

  return ann;
}

FANN_EXTERNAL fann_type *FANN_API fann_run(struct fann *ann, fann_type *input) {
  struct fann_neuron *neuron_it, *last_neuron, *neurons, **neuron_pointers;
  unsigned int i, num_connections, num_input, num_output;
  fann_type neuron_sum, *output;
  fann_type *weights;
  struct fann_layer *layer_it, *last_layer;
  unsigned int activation_function;
  fann_type steepness;

  /* store some variabels local for fast access */
  struct fann_neuron *first_neuron = ann->first_layer->first_neuron;

#ifdef FIXEDFANN
  int multiplier = ann->multiplier;
  unsigned int decimal_point = ann->decimal_point;

  /* values used for the stepwise linear sigmoid function */
  fann_type r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0;
  fann_type v1 = 0, v2 = 0, v3 = 0, v4 = 0, v5 = 0, v6 = 0;

  fann_type last_steepness = 0;
  unsigned int last_activation_function = 0;
#else
  fann_type max_sum = 0;
#endif

  /* first set the input */
  num_input = ann->num_input;
  for (i = 0; i != num_input; i++) {
#ifdef FIXEDFANN
    if (fann_abs(input[i]) > multiplier) {
      printf(
          "Warning input number %d is out of range -%d - %d with value %d, integer overflow may "
          "occur.\n",
          i, multiplier, multiplier, input[i]);
    }
#endif
    first_neuron[i].value = input[i];
  }
  /* Set the bias neuron in the input layer */
#ifdef FIXEDFANN
  (ann->first_layer->last_neuron - 1)->value = multiplier;
#else
  (ann->first_layer->last_neuron - 1)->value = 1;
#endif

  last_layer = ann->last_layer;
  for (layer_it = ann->first_layer + 1; layer_it != last_layer; layer_it++) {
    last_neuron = layer_it->last_neuron;
    for (neuron_it = layer_it->first_neuron; neuron_it != last_neuron; neuron_it++) {
      if (neuron_it->first_con == neuron_it->last_con) {
        /* bias neurons */
#ifdef FIXEDFANN
        neuron_it->value = multiplier;
#else
        neuron_it->value = 1;
#endif
        continue;
      }

      activation_function = neuron_it->activation_function;
      steepness = neuron_it->activation_steepness;

      neuron_sum = 0;
      num_connections = neuron_it->last_con - neuron_it->first_con;
      weights = ann->weights + neuron_it->first_con;

      if (ann->connection_rate >= 1) {
        if (ann->network_type == FANN_NETTYPE_SHORTCUT) {
          neurons = ann->first_layer->first_neuron;
        } else {
          neurons = (layer_it - 1)->first_neuron;
        }

        /* unrolled loop start */
        i = num_connections & 3; /* same as modulo 4 */
        switch (i) {
          case 3:
            neuron_sum += fann_mult(weights[2], neurons[2].value);
          case 2:
            neuron_sum += fann_mult(weights[1], neurons[1].value);
          case 1:
            neuron_sum += fann_mult(weights[0], neurons[0].value);
          case 0:
            break;
        }

        for (; i != num_connections; i += 4) {
          neuron_sum += fann_mult(weights[i], neurons[i].value) +
                        fann_mult(weights[i + 1], neurons[i + 1].value) +
                        fann_mult(weights[i + 2], neurons[i + 2].value) +
                        fann_mult(weights[i + 3], neurons[i + 3].value);
        }
        /* unrolled loop end */

        /*
         * for(i = 0;i != num_connections; i++){
         * printf("%f += %f*%f, ", neuron_sum, weights[i], neurons[i].value);
         * neuron_sum += fann_mult(weights[i], neurons[i].value);
         * }
         */
      } else {
        neuron_pointers = ann->connections + neuron_it->first_con;

        i = num_connections & 3; /* same as modulo 4 */
        switch (i) {
          case 3:
            neuron_sum += fann_mult(weights[2], neuron_pointers[2]->value);
          case 2:
            neuron_sum += fann_mult(weights[1], neuron_pointers[1]->value);
          case 1:
            neuron_sum += fann_mult(weights[0], neuron_pointers[0]->value);
          case 0:
            break;
        }

        for (; i != num_connections; i += 4) {
          neuron_sum += fann_mult(weights[i], neuron_pointers[i]->value) +
                        fann_mult(weights[i + 1], neuron_pointers[i + 1]->value) +
                        fann_mult(weights[i + 2], neuron_pointers[i + 2]->value) +
                        fann_mult(weights[i + 3], neuron_pointers[i + 3]->value);
        }
      }

#ifdef FIXEDFANN
      neuron_it->sum = fann_mult(steepness, neuron_sum);

      if (activation_function != last_activation_function || steepness != last_steepness) {
        switch (activation_function) {
          case FANN_SIGMOID:
          case FANN_SIGMOID_STEPWISE:
            r1 = ann->sigmoid_results[0];
            r2 = ann->sigmoid_results[1];
            r3 = ann->sigmoid_results[2];
            r4 = ann->sigmoid_results[3];
            r5 = ann->sigmoid_results[4];
            r6 = ann->sigmoid_results[5];
            v1 = ann->sigmoid_values[0] / steepness;
            v2 = ann->sigmoid_values[1] / steepness;
            v3 = ann->sigmoid_values[2] / steepness;
            v4 = ann->sigmoid_values[3] / steepness;
            v5 = ann->sigmoid_values[4] / steepness;
            v6 = ann->sigmoid_values[5] / steepness;
            break;
          case FANN_SIGMOID_SYMMETRIC:
          case FANN_SIGMOID_SYMMETRIC_STEPWISE:
            r1 = ann->sigmoid_symmetric_results[0];
            r2 = ann->sigmoid_symmetric_results[1];
            r3 = ann->sigmoid_symmetric_results[2];
            r4 = ann->sigmoid_symmetric_results[3];
            r5 = ann->sigmoid_symmetric_results[4];
            r6 = ann->sigmoid_symmetric_results[5];
            v1 = ann->sigmoid_symmetric_values[0] / steepness;
            v2 = ann->sigmoid_symmetric_values[1] / steepness;
            v3 = ann->sigmoid_symmetric_values[2] / steepness;
            v4 = ann->sigmoid_symmetric_values[3] / steepness;
            v5 = ann->sigmoid_symmetric_values[4] / steepness;
            v6 = ann->sigmoid_symmetric_values[5] / steepness;
            break;
          case FANN_THRESHOLD:
            break;
        }
      }

      switch (activation_function) {
        case FANN_SIGMOID:
        case FANN_SIGMOID_STEPWISE:
          neuron_it->value = (fann_type)fann_stepwise(v1, v2, v3, v4, v5, v6, r1, r2, r3, r4, r5,
                                                      r6, 0, multiplier, neuron_sum);
          break;
        case FANN_SIGMOID_SYMMETRIC:
        case FANN_SIGMOID_SYMMETRIC_STEPWISE:
          neuron_it->value = (fann_type)fann_stepwise(v1, v2, v3, v4, v5, v6, r1, r2, r3, r4, r5,
                                                      r6, -multiplier, multiplier, neuron_sum);
          break;
        case FANN_THRESHOLD:
          neuron_it->value = (fann_type)((neuron_sum < 0) ? 0 : multiplier);
          break;
        case FANN_THRESHOLD_SYMMETRIC:
          neuron_it->value = (fann_type)((neuron_sum < 0) ? -multiplier : multiplier);
          break;
        case FANN_LINEAR:
          neuron_it->value = neuron_sum;
          break;
        case FANN_LINEAR_PIECE:
          neuron_it->value = (fann_type)((neuron_sum < 0)            ? 0
                                         : (neuron_sum > multiplier) ? multiplier
                                                                     : neuron_sum);
          break;
        case FANN_LINEAR_PIECE_SYMMETRIC:
          neuron_it->value = (fann_type)((neuron_sum < -multiplier)  ? -multiplier
                                         : (neuron_sum > multiplier) ? multiplier
                                                                     : neuron_sum);
          break;
        case FANN_LINEAR_PIECE_RECT:
          neuron_it->value = (fann_type)((neuron_sum < 0) ? 0 : neuron_sum);
          break;
        case FANN_LINEAR_PIECE_RECT_LEAKY:
          neuron_it->value = (fann_type)((neuron_sum < 0) ? 0.01 * neuron_sum : neuron_sum);
          break;
        case FANN_ELLIOT:
        case FANN_ELLIOT_SYMMETRIC:
        case FANN_GAUSSIAN:
        case FANN_GAUSSIAN_SYMMETRIC:
        case FANN_GAUSSIAN_STEPWISE:
        case FANN_SIN_SYMMETRIC:
        case FANN_COS_SYMMETRIC:
          fann_error((struct fann_error *)ann, FANN_E_CANT_USE_ACTIVATION);
          break;
      }
      last_steepness = steepness;
      last_activation_function = activation_function;
#else
      neuron_sum = fann_mult(steepness, neuron_sum);

      max_sum = 150 / steepness;
      if (neuron_sum > max_sum)
        neuron_sum = max_sum;
      else if (neuron_sum < -max_sum)
        neuron_sum = -max_sum;

      neuron_it->sum = neuron_sum;

      fann_activation_switch(activation_function, neuron_sum, neuron_it->value);
#endif
    }
  }

  /* set the output */
  output = ann->output;
  num_output = ann->num_output;
  neurons = (ann->last_layer - 1)->first_neuron;
  for (i = 0; i != num_output; i++) {
    output[i] = neurons[i].value;
  }
  return ann->output;
}

FANN_EXTERNAL void FANN_API fann_destroy(struct fann *ann) {
  if (ann == NULL) return;
  fann_safe_free(ann->weights);
  fann_safe_free(ann->connections);
  fann_safe_free(ann->first_layer->first_neuron);
  fann_safe_free(ann->first_layer);
  fann_safe_free(ann->output);
  fann_safe_free(ann->train_errors);
  fann_safe_free(ann->train_slopes);
  fann_safe_free(ann->prev_train_slopes);
  fann_safe_free(ann->prev_steps);
  fann_safe_free(ann->prev_weights_deltas);
  fann_safe_free(ann->errstr);
  fann_safe_free(ann->cascade_activation_functions);
  fann_safe_free(ann->cascade_activation_steepnesses);
  fann_safe_free(ann->cascade_candidate_scores);

#ifndef FIXEDFANN
  fann_safe_free(ann->scale_mean_in);
  fann_safe_free(ann->scale_deviation_in);
  fann_safe_free(ann->scale_new_min_in);
  fann_safe_free(ann->scale_factor_in);

  fann_safe_free(ann->scale_mean_out);
  fann_safe_free(ann->scale_deviation_out);
  fann_safe_free(ann->scale_new_min_out);
  fann_safe_free(ann->scale_factor_out);
#endif

  fann_safe_free(ann);
}

FANN_EXTERNAL void FANN_API fann_randomize_weights(struct fann *ann, fann_type min_weight,
                                                   fann_type max_weight) {
  fann_type *last_weight;
  fann_type *weights = ann->weights;

  last_weight = weights + ann->total_connections;
  for (; weights != last_weight; weights++) {
    *weights = (fann_type)(fann_rand(min_weight, max_weight));
  }

#ifndef FIXEDFANN
  if (ann->prev_train_slopes != NULL) {
    fann_clear_train_arrays(ann);
  }
#endif
}

/* deep copy of the fann structure */
FANN_EXTERNAL struct fann *FANN_API fann_copy(struct fann *orig) {
  struct fann *copy;
  unsigned int num_layers = (unsigned int)(orig->last_layer - orig->first_layer);
  struct fann_layer *orig_layer_it, *copy_layer_it;
  unsigned int layer_size;
  struct fann_neuron *last_neuron, *orig_neuron_it, *copy_neuron_it;
  unsigned int i;
  struct fann_neuron *orig_first_neuron, *copy_first_neuron;
  unsigned int input_neuron;

  copy = fann_allocate_structure(num_layers);
  if (copy == NULL) {
    fann_error((struct fann_error *)orig, FANN_E_CANT_ALLOCATE_MEM);
    return NULL;
  }
  copy->errno_f = orig->errno_f;
  if (orig->errstr) {
    copy->errstr = (char *)malloc(FANN_ERRSTR_MAX);
    if (copy->errstr == NULL) {
      fann_destroy(copy);
      return NULL;
    }
    strcpy(copy->errstr, orig->errstr);
  }
  copy->error_log = orig->error_log;

  copy->learning_rate = orig->learning_rate;
  copy->learning_momentum = orig->learning_momentum;
  copy->connection_rate = orig->connection_rate;
  copy->network_type = orig->network_type;
  copy->num_MSE = orig->num_MSE;
  copy->MSE_value = orig->MSE_value;
  copy->num_bit_fail = orig->num_bit_fail;
  copy->bit_fail_limit = orig->bit_fail_limit;
  copy->train_error_function = orig->train_error_function;
  copy->train_stop_function = orig->train_stop_function;
  copy->training_algorithm = orig->training_algorithm;
  copy->callback = orig->callback;
  copy->user_data = orig->user_data;
#ifndef FIXEDFANN
  copy->cascade_output_change_fraction = orig->cascade_output_change_fraction;
  copy->cascade_output_stagnation_epochs = orig->cascade_output_stagnation_epochs;
  copy->cascade_candidate_change_fraction = orig->cascade_candidate_change_fraction;
  copy->cascade_candidate_stagnation_epochs = orig->cascade_candidate_stagnation_epochs;
  copy->cascade_best_candidate = orig->cascade_best_candidate;
  copy->cascade_candidate_limit = orig->cascade_candidate_limit;
  copy->cascade_weight_multiplier = orig->cascade_weight_multiplier;
  copy->cascade_max_out_epochs = orig->cascade_max_out_epochs;
  copy->cascade_max_cand_epochs = orig->cascade_max_cand_epochs;

  /* copy cascade activation functions */
  copy->cascade_activation_functions_count = orig->cascade_activation_functions_count;
  copy->cascade_activation_functions = (enum fann_activationfunc_enum *)realloc(
      copy->cascade_activation_functions,
      copy->cascade_activation_functions_count * sizeof(enum fann_activationfunc_enum));
  if (copy->cascade_activation_functions == NULL) {
    fann_error((struct fann_error *)orig, FANN_E_CANT_ALLOCATE_MEM);
    fann_destroy(copy);
    return NULL;
  }
  memcpy(copy->cascade_activation_functions, orig->cascade_activation_functions,
         copy->cascade_activation_functions_count * sizeof(enum fann_activationfunc_enum));

  /* copy cascade activation steepnesses */
  copy->cascade_activation_steepnesses_count = orig->cascade_activation_steepnesses_count;
  copy->cascade_activation_steepnesses =
      (fann_type *)realloc(copy->cascade_activation_steepnesses,
                           copy->cascade_activation_steepnesses_count * sizeof(fann_type));
  if (copy->cascade_activation_steepnesses == NULL) {
    fann_error((struct fann_error *)orig, FANN_E_CANT_ALLOCATE_MEM);
    fann_destroy(copy);
    return NULL;
  }
  memcpy(copy->cascade_activation_steepnesses, orig->cascade_activation_steepnesses,
         copy->cascade_activation_steepnesses_count * sizeof(fann_type));

  copy->cascade_num_candidate_groups = orig->cascade_num_candidate_groups;

  /* copy candidate scores, if used */
  if (orig->cascade_candidate_scores == NULL) {
    copy->cascade_candidate_scores = NULL;
  } else {
    copy->cascade_candidate_scores =
        (fann_type *)malloc(fann_get_cascade_num_candidates(copy) * sizeof(fann_type));
    if (copy->cascade_candidate_scores == NULL) {
      fann_error((struct fann_error *)orig, FANN_E_CANT_ALLOCATE_MEM);
      fann_destroy(copy);
      return NULL;
    }
    memcpy(copy->cascade_candidate_scores, orig->cascade_candidate_scores,
           fann_get_cascade_num_candidates(copy) * sizeof(fann_type));
  }
#endif /* FIXEDFANN */

  copy->quickprop_decay = orig->quickprop_decay;
  copy->quickprop_mu = orig->quickprop_mu;
  copy->rprop_increase_factor = orig->rprop_increase_factor;
  copy->rprop_decrease_factor = orig->rprop_decrease_factor;
  copy->rprop_delta_min = orig->rprop_delta_min;
  copy->rprop_delta_max = orig->rprop_delta_max;
  copy->rprop_delta_zero = orig->rprop_delta_zero;

  /* user_data is not deep copied.  user should use fann_copy_with_user_data() for that */
  copy->user_data = orig->user_data;

#ifdef FIXEDFANN
  copy->decimal_point = orig->decimal_point;
  copy->multiplier = orig->multiplier;
  memcpy(copy->sigmoid_results, orig->sigmoid_results, 6 * sizeof(fann_type));
  memcpy(copy->sigmoid_values, orig->sigmoid_values, 6 * sizeof(fann_type));
  memcpy(copy->sigmoid_symmetric_results, orig->sigmoid_symmetric_results, 6 * sizeof(fann_type));
  memcpy(copy->sigmoid_symmetric_values, orig->sigmoid_symmetric_values, 6 * sizeof(fann_type));
#endif

  /* copy layer sizes, prepare for fann_allocate_neurons */
  for (orig_layer_it = orig->first_layer, copy_layer_it = copy->first_layer;
       orig_layer_it != orig->last_layer; orig_layer_it++, copy_layer_it++) {
    layer_size = (unsigned int)(orig_layer_it->last_neuron - orig_layer_it->first_neuron);
    copy_layer_it->first_neuron = NULL;
    copy_layer_it->last_neuron = copy_layer_it->first_neuron + layer_size;
    copy->total_neurons += layer_size;
  }
  copy->num_input = orig->num_input;
  copy->num_output = orig->num_output;

  /* copy scale parameters, when used */
#ifndef FIXEDFANN
  if (orig->scale_mean_in != NULL) {
    fann_allocate_scale(copy);
    for (i = 0; i < orig->num_input; i++) {
      copy->scale_mean_in[i] = orig->scale_mean_in[i];
      copy->scale_deviation_in[i] = orig->scale_deviation_in[i];
      copy->scale_new_min_in[i] = orig->scale_new_min_in[i];
      copy->scale_factor_in[i] = orig->scale_factor_in[i];
    }
    for (i = 0; i < orig->num_output; i++) {
      copy->scale_mean_out[i] = orig->scale_mean_out[i];
      copy->scale_deviation_out[i] = orig->scale_deviation_out[i];
      copy->scale_new_min_out[i] = orig->scale_new_min_out[i];
      copy->scale_factor_out[i] = orig->scale_factor_out[i];
    }
  }
#endif

  /* copy the neurons */
  fann_allocate_neurons(copy);
  if (copy->errno_f == FANN_E_CANT_ALLOCATE_MEM) {
    fann_destroy(copy);
    return NULL;
  }
  layer_size =
      (unsigned int)((orig->last_layer - 1)->last_neuron - (orig->last_layer - 1)->first_neuron);
  memcpy(copy->output, orig->output, layer_size * sizeof(fann_type));

  last_neuron = (orig->last_layer - 1)->last_neuron;
  for (orig_neuron_it = orig->first_layer->first_neuron,
      copy_neuron_it = copy->first_layer->first_neuron;
       orig_neuron_it != last_neuron; orig_neuron_it++, copy_neuron_it++) {
    memcpy(copy_neuron_it, orig_neuron_it, sizeof(struct fann_neuron));
  }
  /* copy the connections */
  copy->total_connections = orig->total_connections;
  fann_allocate_connections(copy);
  if (copy->errno_f == FANN_E_CANT_ALLOCATE_MEM) {
    fann_destroy(copy);
    return NULL;
  }

  orig_first_neuron = orig->first_layer->first_neuron;
  copy_first_neuron = copy->first_layer->first_neuron;
  for (i = 0; i < orig->total_connections; i++) {
    copy->weights[i] = orig->weights[i];
    input_neuron = (unsigned int)(orig->connections[i] - orig_first_neuron);
    copy->connections[i] = copy_first_neuron + input_neuron;
  }

  if (orig->train_slopes) {
    copy->train_slopes = (fann_type *)malloc(copy->total_connections_allocated * sizeof(fann_type));
    if (copy->train_slopes == NULL) {
      fann_error((struct fann_error *)orig, FANN_E_CANT_ALLOCATE_MEM);
      fann_destroy(copy);
      return NULL;
    }
    memcpy(copy->train_slopes, orig->train_slopes,
           copy->total_connections_allocated * sizeof(fann_type));
  }

  if (orig->prev_steps) {
    copy->prev_steps = (fann_type *)malloc(copy->total_connections_allocated * sizeof(fann_type));
    if (copy->prev_steps == NULL) {
      fann_error((struct fann_error *)orig, FANN_E_CANT_ALLOCATE_MEM);
      fann_destroy(copy);
      return NULL;
    }
    memcpy(copy->prev_steps, orig->prev_steps,
           copy->total_connections_allocated * sizeof(fann_type));
  }

  if (orig->prev_train_slopes) {
    copy->prev_train_slopes =
        (fann_type *)malloc(copy->total_connections_allocated * sizeof(fann_type));
    if (copy->prev_train_slopes == NULL) {
      fann_error((struct fann_error *)orig, FANN_E_CANT_ALLOCATE_MEM);
      fann_destroy(copy);
      return NULL;
    }
    memcpy(copy->prev_train_slopes, orig->prev_train_slopes,
           copy->total_connections_allocated * sizeof(fann_type));
  }

  if (orig->prev_weights_deltas) {
    copy->prev_weights_deltas =
        (fann_type *)malloc(copy->total_connections_allocated * sizeof(fann_type));
    if (copy->prev_weights_deltas == NULL) {
      fann_error((struct fann_error *)orig, FANN_E_CANT_ALLOCATE_MEM);
      fann_destroy(copy);
      return NULL;
    }
    memcpy(copy->prev_weights_deltas, orig->prev_weights_deltas,
           copy->total_connections_allocated * sizeof(fann_type));
  }

  return copy;
}

FANN_EXTERNAL void FANN_API fann_print_connections(struct fann *ann) {
  struct fann_layer *layer_it;
  struct fann_neuron *neuron_it;
  unsigned int i;
  int value;
  char *neurons;
  unsigned int num_neurons = fann_get_total_neurons(ann) - fann_get_num_output(ann);

  neurons = (char *)malloc(num_neurons + 1);
  if (neurons == NULL) {
    fann_error(NULL, FANN_E_CANT_ALLOCATE_MEM);
    return;
  }
  neurons[num_neurons] = 0;

  printf("Layer / Neuron ");
  for (i = 0; i < num_neurons; i++) {
    printf("%d", i % 10);
  }
  printf("\n");

  for (layer_it = ann->first_layer + 1; layer_it != ann->last_layer; layer_it++) {
    for (neuron_it = layer_it->first_neuron; neuron_it != layer_it->last_neuron; neuron_it++) {
      memset(neurons, (int)'.', num_neurons);
      for (i = neuron_it->first_con; i < neuron_it->last_con; i++) {
        if (ann->weights[i] < 0) {
#ifdef FIXEDFANN
          value = (int)((ann->weights[i] / (double)ann->multiplier) - 0.5);
#else
          value = (int)((ann->weights[i]) - 0.5);
#endif
          if (value < -25) value = -25;
          neurons[ann->connections[i] - ann->first_layer->first_neuron] = (char)('a' - value);
        } else {
#ifdef FIXEDFANN
          value = (int)((ann->weights[i] / (double)ann->multiplier) + 0.5);
#else
          value = (int)((ann->weights[i]) + 0.5);
#endif
          if (value > 25) value = 25;
          neurons[ann->connections[i] - ann->first_layer->first_neuron] = (char)('A' + value);
        }
      }
      printf("L %3d / N %4d %s\n", (int)(layer_it - ann->first_layer),
             (int)(neuron_it - ann->first_layer->first_neuron), neurons);
    }
  }

  free(neurons);
}

/* Initialize the weights using Widrow + Nguyen's algorithm.
 */
FANN_EXTERNAL void FANN_API fann_init_weights(struct fann *ann,
                                              struct fann_train_data *train_data) {
  fann_type smallest_inp, largest_inp;
  unsigned int dat = 0, elem, num_connect, num_hidden_neurons;
  struct fann_layer *layer_it;
  struct fann_neuron *neuron_it, *last_neuron, *bias_neuron;

#ifdef FIXEDFANN
  unsigned int multiplier = ann->multiplier;
#endif
  float scale_factor;

  for (smallest_inp = largest_inp = train_data->input[0][0]; dat < train_data->num_data; dat++) {
    for (elem = 0; elem < train_data->num_input; elem++) {
      if (train_data->input[dat][elem] < smallest_inp) smallest_inp = train_data->input[dat][elem];
      if (train_data->input[dat][elem] > largest_inp) largest_inp = train_data->input[dat][elem];
    }
  }

  num_hidden_neurons = (unsigned int)(ann->total_neurons - (ann->num_input + ann->num_output +
                                                            (ann->last_layer - ann->first_layer)));
  scale_factor = (float)(pow((double)(0.7f * (double)num_hidden_neurons),
                             (double)(1.0f / (double)ann->num_input)) /
                         (double)(largest_inp - smallest_inp));

#ifdef DEBUG
  printf("Initializing weights with scale factor %f\n", scale_factor);
#endif
  bias_neuron = ann->first_layer->last_neuron - 1;
  for (layer_it = ann->first_layer + 1; layer_it != ann->last_layer; layer_it++) {
    last_neuron = layer_it->last_neuron;

    if (ann->network_type == FANN_NETTYPE_LAYER) {
      bias_neuron = (layer_it - 1)->last_neuron - 1;
    }

    for (neuron_it = layer_it->first_neuron; neuron_it != last_neuron; neuron_it++) {
      for (num_connect = neuron_it->first_con; num_connect < neuron_it->last_con; num_connect++) {
        if (bias_neuron == ann->connections[num_connect]) {
#ifdef FIXEDFANN
          ann->weights[num_connect] =
              (fann_type)fann_rand(-scale_factor, scale_factor * multiplier);
#else
          ann->weights[num_connect] = (fann_type)fann_rand(-scale_factor, scale_factor);
#endif
        } else {
#ifdef FIXEDFANN
          ann->weights[num_connect] = (fann_type)fann_rand(0, scale_factor * multiplier);
#else
          ann->weights[num_connect] = (fann_type)fann_rand(0, scale_factor);
#endif
        }
      }
    }
  }

#ifndef FIXEDFANN
  if (ann->prev_train_slopes != NULL) {
    fann_clear_train_arrays(ann);
  }
#endif
}

FANN_EXTERNAL void FANN_API fann_print_parameters(struct fann *ann) {
  struct fann_layer *layer_it;
#ifndef FIXEDFANN
  unsigned int i;
#endif

  printf("Input layer                          :%4d neurons, 1 bias\n", ann->num_input);
  for (layer_it = ann->first_layer + 1; layer_it != ann->last_layer - 1; layer_it++) {
    if (ann->network_type == FANN_NETTYPE_SHORTCUT) {
      printf("  Hidden layer                       :%4d neurons, 0 bias\n",
             (int)(layer_it->last_neuron - layer_it->first_neuron));
    } else {
      printf("  Hidden layer                       :%4d neurons, 1 bias\n",
             (int)(layer_it->last_neuron - layer_it->first_neuron - 1));
    }
  }
  printf("Output layer                         :%4d neurons\n", ann->num_output);
  printf("Total neurons and biases             :%4d\n", fann_get_total_neurons(ann));
  printf("Total connections                    :%4d\n", ann->total_connections);
  printf("Connection rate                      :%8.3f\n", ann->connection_rate);
  printf("Network type                         :   %s\n", FANN_NETTYPE_NAMES[ann->network_type]);
#ifdef FIXEDFANN
  printf("Decimal point                        :%4d\n", ann->decimal_point);
  printf("Multiplier                           :%4d\n", ann->multiplier);
#else
  printf("Training algorithm                   :   %s\n",
         FANN_TRAIN_NAMES[ann->training_algorithm]);
  printf("Training error function              :   %s\n",
         FANN_ERRORFUNC_NAMES[ann->train_error_function]);
  printf("Training stop function               :   %s\n",
         FANN_STOPFUNC_NAMES[ann->train_stop_function]);
#endif
#ifdef FIXEDFANN
  printf("Bit fail limit                       :%4d\n", ann->bit_fail_limit);
#else
  printf("Bit fail limit                       :%8.3f\n", ann->bit_fail_limit);
  printf("Learning rate                        :%8.3f\n", ann->learning_rate);
  printf("Learning momentum                    :%8.3f\n", ann->learning_momentum);
  printf("Quickprop decay                      :%11.6f\n", ann->quickprop_decay);
  printf("Quickprop mu                         :%8.3f\n", ann->quickprop_mu);
  printf("RPROP increase factor                :%8.3f\n", ann->rprop_increase_factor);
  printf("RPROP decrease factor                :%8.3f\n", ann->rprop_decrease_factor);
  printf("RPROP delta min                      :%8.3f\n", ann->rprop_delta_min);
  printf("RPROP delta max                      :%8.3f\n", ann->rprop_delta_max);
  printf("Cascade output change fraction       :%11.6f\n", ann->cascade_output_change_fraction);
  printf("Cascade candidate change fraction    :%11.6f\n", ann->cascade_candidate_change_fraction);
  printf("Cascade output stagnation epochs     :%4d\n", ann->cascade_output_stagnation_epochs);
  printf("Cascade candidate stagnation epochs  :%4d\n", ann->cascade_candidate_stagnation_epochs);
  printf("Cascade max output epochs            :%4d\n", ann->cascade_max_out_epochs);
  printf("Cascade min output epochs            :%4d\n", ann->cascade_min_out_epochs);
  printf("Cascade max candidate epochs         :%4d\n", ann->cascade_max_cand_epochs);
  printf("Cascade min candidate epochs         :%4d\n", ann->cascade_min_cand_epochs);
  printf("Cascade weight multiplier            :%8.3f\n", ann->cascade_weight_multiplier);
  printf("Cascade candidate limit              :%8.3f\n", ann->cascade_candidate_limit);
  for (i = 0; i < ann->cascade_activation_functions_count; i++)
    printf("Cascade activation functions[%d]      :   %s\n", i,
           FANN_ACTIVATIONFUNC_NAMES[ann->cascade_activation_functions[i]]);
  for (i = 0; i < ann->cascade_activation_steepnesses_count; i++)
    printf("Cascade activation steepnesses[%d]    :%8.3f\n", i,
           ann->cascade_activation_steepnesses[i]);

  printf("Cascade candidate groups             :%4d\n", ann->cascade_num_candidate_groups);
  printf("Cascade no. of candidates            :%4d\n", fann_get_cascade_num_candidates(ann));

  /* TODO: dump scale parameters */
#endif
}

FANN_GET(unsigned int, num_input)
FANN_GET(unsigned int, num_output)

FANN_EXTERNAL unsigned int FANN_API fann_get_total_neurons(struct fann *ann) {
  if (ann->network_type) {
    return ann->total_neurons;
  } else {
    /* -1, because there is always an unused bias neuron in the last layer */
    return ann->total_neurons - 1;
  }
}

FANN_GET(unsigned int, total_connections)

FANN_EXTERNAL enum fann_nettype_enum FANN_API fann_get_network_type(struct fann *ann) {
  /* Currently two types: LAYER = 0, SHORTCUT = 1 */
  /* Enum network_types must be set to match the return values  */
  return ann->network_type;
}

FANN_EXTERNAL float FANN_API fann_get_connection_rate(struct fann *ann) {
  return ann->connection_rate;
}

FANN_EXTERNAL unsigned int FANN_API fann_get_num_layers(struct fann *ann) {
  return (unsigned int)(ann->last_layer - ann->first_layer);
}

FANN_EXTERNAL void FANN_API fann_get_layer_array(struct fann *ann, unsigned int *layers) {
  struct fann_layer *layer_it;

  for (layer_it = ann->first_layer; layer_it != ann->last_layer; layer_it++) {
    unsigned int count = (unsigned int)(layer_it->last_neuron - layer_it->first_neuron);
    /* Remove the bias from the count of neurons. */
    switch (fann_get_network_type(ann)) {
      case FANN_NETTYPE_LAYER: {
        --count;
        break;
      }
      case FANN_NETTYPE_SHORTCUT: {
        /* The bias in the first layer is reused for all layers */
        if (layer_it == ann->first_layer) --count;
        break;
      }
      default: {
        /* Unknown network type, assume no bias present  */
        break;
      }
    }
    *layers++ = count;
  }
}

FANN_EXTERNAL void FANN_API fann_get_bias_array(struct fann *ann, unsigned int *bias) {
  struct fann_layer *layer_it;

  for (layer_it = ann->first_layer; layer_it != ann->last_layer; ++layer_it, ++bias) {
    switch (fann_get_network_type(ann)) {
      case FANN_NETTYPE_LAYER: {
        /* Report one bias in each layer except the last */
        if (layer_it != ann->last_layer - 1)
          *bias = 1;
        else
          *bias = 0;
        break;
      }
      case FANN_NETTYPE_SHORTCUT: {
        /* The bias in the first layer is reused for all layers */
        if (layer_it == ann->first_layer)
          *bias = 1;
        else
          *bias = 0;
        break;
      }
      default: {
        /* Unknown network type, assume no bias present  */
        *bias = 0;
        break;
      }
    }
  }
}

FANN_EXTERNAL void FANN_API fann_get_connection_array(struct fann *ann,
                                                      struct fann_connection *connections) {
  struct fann_neuron *first_neuron;
  struct fann_layer *layer_it;
  struct fann_neuron *neuron_it;
  unsigned int idx;
  unsigned int source_index;
  unsigned int destination_index;

  first_neuron = ann->first_layer->first_neuron;

  source_index = 0;
  destination_index = 0;

  /* The following assumes that the last unused bias has no connections */

  /* for each layer */
  for (layer_it = ann->first_layer; layer_it != ann->last_layer; layer_it++) {
    /* for each neuron */
    for (neuron_it = layer_it->first_neuron; neuron_it != layer_it->last_neuron; neuron_it++) {
      /* for each connection */
      for (idx = neuron_it->first_con; idx < neuron_it->last_con; idx++) {
        /* Assign the source, destination and weight */
        connections->from_neuron = (unsigned int)(ann->connections[source_index] - first_neuron);
        connections->to_neuron = destination_index;
        connections->weight = ann->weights[source_index];

        connections++;
        source_index++;
      }
      destination_index++;
    }
  }
}

FANN_EXTERNAL void FANN_API fann_set_weight_array(struct fann *ann,
                                                  struct fann_connection *connections,
                                                  unsigned int num_connections) {
  unsigned int idx;

  for (idx = 0; idx < num_connections; idx++) {
    fann_set_weight(ann, connections[idx].from_neuron, connections[idx].to_neuron,
                    connections[idx].weight);
  }
}

FANN_EXTERNAL void FANN_API fann_set_weight(struct fann *ann, unsigned int from_neuron,
                                            unsigned int to_neuron, fann_type weight) {
  struct fann_neuron *first_neuron;
  struct fann_layer *layer_it;
  struct fann_neuron *neuron_it;
  unsigned int idx;
  unsigned int source_index;
  unsigned int destination_index;

  first_neuron = ann->first_layer->first_neuron;

  source_index = 0;
  destination_index = 0;

  /* Find the connection, simple brute force search through the network
     for one or more connections that match to minimize datastructure dependencies.
     Nothing is done if the connection does not already exist in the network. */

  /* for each layer */
  for (layer_it = ann->first_layer; layer_it != ann->last_layer; layer_it++) {
    /* for each neuron */
    for (neuron_it = layer_it->first_neuron; neuron_it != layer_it->last_neuron; neuron_it++) {
      /* for each connection */
      for (idx = neuron_it->first_con; idx < neuron_it->last_con; idx++) {
        /* If the source and destination neurons match, assign the weight */
        if (((int)from_neuron == ann->connections[source_index] - first_neuron) &&
            (to_neuron == destination_index)) {
          ann->weights[source_index] = weight;
        }
        source_index++;
      }
      destination_index++;
    }
  }
}

FANN_EXTERNAL void FANN_API fann_get_weights(struct fann *ann, fann_type *weights) {
  memcpy(weights, ann->weights, sizeof(fann_type) * ann->total_connections);
}

FANN_EXTERNAL void FANN_API fann_set_weights(struct fann *ann, fann_type *weights) {
  memcpy(ann->weights, weights, sizeof(fann_type) * ann->total_connections);
}

FANN_GET_SET(void *, user_data)

#ifdef FIXEDFANN

FANN_GET(unsigned int, decimal_point)
FANN_GET(unsigned int, multiplier)

/* INTERNAL FUNCTION
   Adjust the steepwise functions (if used)
*/
void fann_update_stepwise(struct fann *ann) {
  unsigned int i = 0;

  /* Calculate the parameters for the stepwise linear
   * sigmoid function fixed point.
   * Using a rewritten sigmoid function.
   * results 0.005, 0.05, 0.25, 0.75, 0.95, 0.995
   */
  ann->sigmoid_results[0] = fann_max((fann_type)(ann->multiplier / 200.0 + 0.5), 1);
  ann->sigmoid_results[1] = fann_max((fann_type)(ann->multiplier / 20.0 + 0.5), 1);
  ann->sigmoid_results[2] = fann_max((fann_type)(ann->multiplier / 4.0 + 0.5), 1);
  ann->sigmoid_results[3] =
      fann_min(ann->multiplier - (fann_type)(ann->multiplier / 4.0 + 0.5), ann->multiplier - 1);
  ann->sigmoid_results[4] =
      fann_min(ann->multiplier - (fann_type)(ann->multiplier / 20.0 + 0.5), ann->multiplier - 1);
  ann->sigmoid_results[5] =
      fann_min(ann->multiplier - (fann_type)(ann->multiplier / 200.0 + 0.5), ann->multiplier - 1);

  ann->sigmoid_symmetric_results[0] =
      fann_max((fann_type)((ann->multiplier / 100.0) - ann->multiplier - 0.5),
               (fann_type)(1 - (fann_type)ann->multiplier));
  ann->sigmoid_symmetric_results[1] =
      fann_max((fann_type)((ann->multiplier / 10.0) - ann->multiplier - 0.5),
               (fann_type)(1 - (fann_type)ann->multiplier));
  ann->sigmoid_symmetric_results[2] =
      fann_max((fann_type)((ann->multiplier / 2.0) - ann->multiplier - 0.5),
               (fann_type)(1 - (fann_type)ann->multiplier));
  ann->sigmoid_symmetric_results[3] =
      fann_min(ann->multiplier - (fann_type)(ann->multiplier / 2.0 + 0.5), ann->multiplier - 1);
  ann->sigmoid_symmetric_results[4] =
      fann_min(ann->multiplier - (fann_type)(ann->multiplier / 10.0 + 0.5), ann->multiplier - 1);
  ann->sigmoid_symmetric_results[5] =
      fann_min(ann->multiplier - (fann_type)(ann->multiplier / 100.0 + 1.0), ann->multiplier - 1);

  for (i = 0; i < 6; i++) {
    ann->sigmoid_values[i] =
        (fann_type)(((log(ann->multiplier / (float)ann->sigmoid_results[i] - 1) *
                      (float)ann->multiplier) /
                     -2.0) *
                    (float)ann->multiplier);
    ann->sigmoid_symmetric_values[i] =
        (fann_type)(((log((ann->multiplier - (float)ann->sigmoid_symmetric_results[i]) /
                          ((float)ann->sigmoid_symmetric_results[i] + ann->multiplier)) *
                      (float)ann->multiplier) /
                     -2.0) *
                    (float)ann->multiplier);
  }
}
#endif

/* INTERNAL FUNCTION
   Allocates the main structure and sets some default values.
 */
struct fann *fann_allocate_structure(unsigned int num_layers) {
  struct fann *ann;

  if (num_layers < 2) {
#ifdef DEBUG
    printf("less than 2 layers - ABORTING.\n");
#endif
    return NULL;
  }

  /* allocate and initialize the main network structure */
  ann = (struct fann *)malloc(sizeof(struct fann));
  if (ann == NULL) {
    fann_error(NULL, FANN_E_CANT_ALLOCATE_MEM);
    return NULL;
  }

  ann->errno_f = FANN_E_NO_ERROR;
  ann->error_log = fann_default_error_log;
  ann->errstr = NULL;
  ann->learning_rate = 0.7f;
  ann->learning_momentum = 0.0;
  ann->total_neurons = 0;
  ann->total_connections = 0;
  ann->num_input = 0;
  ann->num_output = 0;
  ann->train_errors = NULL;
  ann->train_slopes = NULL;
  ann->prev_steps = NULL;
  ann->prev_train_slopes = NULL;
  ann->prev_weights_deltas = NULL;
  ann->training_algorithm = FANN_TRAIN_RPROP;
  ann->num_MSE = 0;
  ann->MSE_value = 0;
  ann->num_bit_fail = 0;
  ann->bit_fail_limit = (fann_type)0.35;
  ann->network_type = FANN_NETTYPE_LAYER;
  ann->train_error_function = FANN_ERRORFUNC_TANH;
  ann->train_stop_function = FANN_STOPFUNC_MSE;
  ann->callback = NULL;
  ann->user_data = NULL; /* User is responsible for deallocation */
  ann->weights = NULL;
  ann->connections = NULL;
  ann->output = NULL;
#ifndef FIXEDFANN
  ann->scale_mean_in = NULL;
  ann->scale_deviation_in = NULL;
  ann->scale_new_min_in = NULL;
  ann->scale_factor_in = NULL;
  ann->scale_mean_out = NULL;
  ann->scale_deviation_out = NULL;
  ann->scale_new_min_out = NULL;
  ann->scale_factor_out = NULL;
#endif

  /* variables used for cascade correlation (reasonable defaults) */
  ann->cascade_output_change_fraction = 0.01f;
  ann->cascade_candidate_change_fraction = 0.01f;
  ann->cascade_output_stagnation_epochs = 12;
  ann->cascade_candidate_stagnation_epochs = 12;
  ann->cascade_num_candidate_groups = 2;
  ann->cascade_weight_multiplier = (fann_type)0.4;
  ann->cascade_candidate_limit = (fann_type)1000.0;
  ann->cascade_max_out_epochs = 150;
  ann->cascade_max_cand_epochs = 150;
  ann->cascade_min_out_epochs = 50;
  ann->cascade_min_cand_epochs = 50;
  ann->cascade_candidate_scores = NULL;
  ann->cascade_activation_functions_count = 10;
  ann->cascade_activation_functions = (enum fann_activationfunc_enum *)calloc(
      ann->cascade_activation_functions_count, sizeof(enum fann_activationfunc_enum));
  if (ann->cascade_activation_functions == NULL) {
    fann_error(NULL, FANN_E_CANT_ALLOCATE_MEM);
    free(ann);
    return NULL;
  }

  ann->cascade_activation_functions[0] = FANN_SIGMOID;
  ann->cascade_activation_functions[1] = FANN_SIGMOID_SYMMETRIC;
  ann->cascade_activation_functions[2] = FANN_GAUSSIAN;
  ann->cascade_activation_functions[3] = FANN_GAUSSIAN_SYMMETRIC;
  ann->cascade_activation_functions[4] = FANN_ELLIOT;
  ann->cascade_activation_functions[5] = FANN_ELLIOT_SYMMETRIC;
  ann->cascade_activation_functions[6] = FANN_SIN_SYMMETRIC;
  ann->cascade_activation_functions[7] = FANN_COS_SYMMETRIC;
  ann->cascade_activation_functions[8] = FANN_SIN;
  ann->cascade_activation_functions[9] = FANN_COS;

  ann->cascade_activation_steepnesses_count = 4;
  ann->cascade_activation_steepnesses =
      (fann_type *)calloc(ann->cascade_activation_steepnesses_count, sizeof(fann_type));
  if (ann->cascade_activation_steepnesses == NULL) {
    fann_safe_free(ann->cascade_activation_functions);
    fann_error(NULL, FANN_E_CANT_ALLOCATE_MEM);
    free(ann);
    return NULL;
  }

  ann->cascade_activation_steepnesses[0] = (fann_type)0.25;
  ann->cascade_activation_steepnesses[1] = (fann_type)0.5;
  ann->cascade_activation_steepnesses[2] = (fann_type)0.75;
  ann->cascade_activation_steepnesses[3] = (fann_type)1.0;

  /* Variables for use with with Quickprop training (reasonable defaults) */
  ann->quickprop_decay = -0.0001f;
  ann->quickprop_mu = 1.75;

  /* Variables for use with with RPROP training (reasonable defaults) */
  ann->rprop_increase_factor = 1.2f;
  ann->rprop_decrease_factor = 0.5;
  ann->rprop_delta_min = 0.0;
  ann->rprop_delta_max = 50.0;
  ann->rprop_delta_zero = 0.1f;

  /* Variables for use with SARPROP training (reasonable defaults) */
  ann->sarprop_weight_decay_shift = -6.644f;
  ann->sarprop_step_error_threshold_factor = 0.1f;
  ann->sarprop_step_error_shift = 1.385f;
  ann->sarprop_temperature = 0.015f;
  ann->sarprop_epoch = 0;

  fann_init_error_data((struct fann_error *)ann);

#ifdef FIXEDFANN
  /* these values are only boring defaults, and should really
   * never be used, since the real values are always loaded from a file. */
  ann->decimal_point = 8;
  ann->multiplier = 256;
#endif

  /* allocate room for the layers */
  ann->first_layer = (struct fann_layer *)calloc(num_layers, sizeof(struct fann_layer));
  if (ann->first_layer == NULL) {
    fann_error(NULL, FANN_E_CANT_ALLOCATE_MEM);
    free(ann);
    return NULL;
  }

  ann->last_layer = ann->first_layer + num_layers;

  return ann;
}

/* INTERNAL FUNCTION
   Allocates room for the scaling parameters.
 */
int fann_allocate_scale(struct fann *ann) {
  /* todo this should only be allocated when needed */
#ifndef FIXEDFANN
  unsigned int i = 0;
#define SCALE_ALLOCATE(what, where, default_value)                             \
  ann->what##_##where = (float *)calloc(ann->num_##where##put, sizeof(float)); \
  if (ann->what##_##where == NULL) {                                           \
    fann_error(NULL, FANN_E_CANT_ALLOCATE_MEM);                                \
    fann_destroy(ann);                                                         \
    return 1;                                                                  \
  }                                                                            \
  for (i = 0; i < ann->num_##where##put; i++) ann->what##_##where[i] = (default_value);

  SCALE_ALLOCATE(scale_mean, in, 0.0)
  SCALE_ALLOCATE(scale_deviation, in, 1.0)
  SCALE_ALLOCATE(scale_new_min, in, -1.0)
  SCALE_ALLOCATE(scale_factor, in, 1.0)

  SCALE_ALLOCATE(scale_mean, out, 0.0)
  SCALE_ALLOCATE(scale_deviation, out, 1.0)
  SCALE_ALLOCATE(scale_new_min, out, -1.0)
  SCALE_ALLOCATE(scale_factor, out, 1.0)
#undef SCALE_ALLOCATE
#endif
  return 0;
}

/* INTERNAL FUNCTION
   Allocates room for the neurons.
 */
void fann_allocate_neurons(struct fann *ann) {
  struct fann_layer *layer_it;
  struct fann_neuron *neurons;
  unsigned int num_neurons_so_far = 0;
  unsigned int num_neurons = 0;

  /* all the neurons is allocated in one long array (calloc clears mem) */
  neurons = (struct fann_neuron *)calloc(ann->total_neurons, sizeof(struct fann_neuron));
  ann->total_neurons_allocated = ann->total_neurons;

  if (neurons == NULL) {
    fann_error((struct fann_error *)ann, FANN_E_CANT_ALLOCATE_MEM);
    return;
  }

  for (layer_it = ann->first_layer; layer_it != ann->last_layer; layer_it++) {
    num_neurons = (unsigned int)(layer_it->last_neuron - layer_it->first_neuron);
    layer_it->first_neuron = neurons + num_neurons_so_far;
    layer_it->last_neuron = layer_it->first_neuron + num_neurons;
    num_neurons_so_far += num_neurons;
  }

  ann->output = (fann_type *)calloc(num_neurons, sizeof(fann_type));
  if (ann->output == NULL) {
    fann_error((struct fann_error *)ann, FANN_E_CANT_ALLOCATE_MEM);
    return;
  }
}

/* INTERNAL FUNCTION
   Allocate room for the connections.
 */
void fann_allocate_connections(struct fann *ann) {
  ann->weights = (fann_type *)calloc(ann->total_connections, sizeof(fann_type));
  if (ann->weights == NULL) {
    fann_error((struct fann_error *)ann, FANN_E_CANT_ALLOCATE_MEM);
    return;
  }
  ann->total_connections_allocated = ann->total_connections;

  /* TODO make special cases for all places where the connections
   * is used, so that it is not needed for fully connected networks.
   */
  ann->connections =
      (struct fann_neuron **)calloc(ann->total_connections_allocated, sizeof(struct fann_neuron *));
  if (ann->connections == NULL) {
    fann_error((struct fann_error *)ann, FANN_E_CANT_ALLOCATE_MEM);
    return;
  }
}

#ifdef FANN_NO_SEED
int FANN_SEED_RAND = 0;
#else
int FANN_SEED_RAND = 1;
#endif

FANN_EXTERNAL void FANN_API fann_disable_seed_rand() { FANN_SEED_RAND = 0; }

FANN_EXTERNAL void FANN_API fann_enable_seed_rand() { FANN_SEED_RAND = 1; }

/* INTERNAL FUNCTION
   Seed the random function.
 */
void fann_seed_rand() {
#ifndef _WIN32
  FILE *fp = fopen("/dev/urandom", "r");
  unsigned int foo;
  struct timeval t;

  if (!fp) {
    gettimeofday(&t, NULL);
    foo = t.tv_usec;
#ifdef DEBUG
    printf("unable to open /dev/urandom\n");
#endif
  } else {
    if (fread(&foo, sizeof(foo), 1, fp) != 1) {
      gettimeofday(&t, NULL);
      foo = t.tv_usec;
#ifdef DEBUG
      printf("unable to read from /dev/urandom\n");
#endif
    }
    fclose(fp);
  }
  if (FANN_SEED_RAND) {
    srand(foo);
  }
#else
  /* COMPAT_TIME REPLACEMENT */
  if (FANN_SEED_RAND) {
    srand(GetTickCount());
  }
#endif
}
