content: package agents.sergeyPolikarpov;
  
  import java.util.Random;
  
  /**
   * Created by Eclipse Java EE IDE.
   * User: Sergey V. Polikarpov
   * Date: Aug 29, 2009
   * Time: 16:09:00
   * Controller for marioai, based on CyberNeuron (see technical details at http://arxiv.org/abs/0907.0229)
   * Copyright (C) 2009  Sergey V. Polikarpov, Konstantin E. Rumyantsev
   * Contact: polikarpovsv*gmail.com
   * <p>
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   * <p>
   * This program 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 General Public License for more details.
   * <p>
   * You should have received a copy of the GNU General Public License
   * along with this program.  If not, see &lt;http://www.gnu.org/licenses/>.
   */
  
  public class CyberNeuron {
  
      private float[][][] sbox;
  
      private int[] inputs_for_sbox;
      private int[] outputs_for_sbox;
      private double[] outputs;
      private double[] inputs;
      public final int parallel_inputs = 1;
  
      private int num_bits_in_input = 10; //****//
      private int num_of_cells_in_sbox = (int) Math.pow(2, num_bits_in_input); //****//
      private int[] powstwo = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536};
  
      private int threshold;
  
      private final Random random = new Random();
      private double learningRate = 0.05;
  
      public CyberNeuron(int numberOfInputs, int numberOfOutputs) {
          threshold = (int) (0.65 * (127 * numberOfInputs * parallel_inputs / num_bits_in_input));
          sbox = new float[numberOfInputs * parallel_inputs / num_bits_in_input][num_of_cells_in_sbox][numberOfOutputs];   //****//
          inputs_for_sbox = new int[sbox.length];
          outputs_for_sbox = new int[sbox[0][0].length];
  
          outputs = new double[numberOfOutputs];
          inputs = new double[numberOfInputs];
          initialize_sbox(sbox);
      }
  
      public CyberNeuron(float[][][] sbox, int numberOfOutputs) {
          this.sbox = sbox;
          inputs = new double[sbox.length * num_bits_in_input];
          outputs = new double[numberOfOutputs];
      }
  
      protected void initialize_sbox(float[][][] sbox) {
          for (int i = 0; i < sbox.length; i++) {
              for (int j = 0; j < sbox[i].length; j++) {
                  for (int m = 0; m < sbox[i][j].length; m++) {
                      sbox[i][j][m] = 0;
                  }
              }
          }
      }
  
  
      public CyberNeuron getNewInstance() {
          return new CyberNeuron(sbox.length * num_bits_in_input / parallel_inputs, outputs.length);
      }
  
      public CyberNeuron copy() {
          CyberNeuron copy = new CyberNeuron(copy(sbox), outputs.length);
          //copy.setMutationMagnitude(mutationMagnitude);
          return copy;
      }
  
      private float[][][] copy(float[][][] original) {  // need to correct
          float[][][] copy = new float[original.length][original[0].length][original[0][0].length];
          for (int i = 0; i < copy.length; i++) {
              for (int j = 0; j < copy[i].length; j++) {
                  for (int m = 0; m < copy[i][j].length; m++) {
                      copy[i][j][m] = original[i][j][m];
                  }
              }
          }
          return copy;
      }
  
      public double[] propagate(double[] inputIn) {
          if (inputs != inputIn) {
              System.arraycopy(inputIn, 0, this.inputs, 0, inputIn.length);
          }
          if (inputIn.length < inputs.length)
              System.out.println("NOTE: only " + inputIn.length + " inputs out of " + inputs.length + " are used in the network");
  
          int blocksize = sbox.length / parallel_inputs;
          int[] tmp_inputs = new int[blocksize];
          for (int m = 0; m < blocksize; m++) {
              tmp_inputs[m] = 0;
              for (int n = 0; n < num_bits_in_input; n++) {
                  if (inputs[m * num_bits_in_input + n] == 1) {
                      tmp_inputs[m] += powstwo[n];
                  }
              }
          }
  
          for (int p = 0; p < parallel_inputs; p++) {
              for (int m = 0; m < blocksize; m++) {                                   //****//
                  inputs_for_sbox[p * blocksize + m] = tmp_inputs[m];
              }
          }
  
          for (int i = 0; i < sbox[0][0].length; i++) {                                //****//
              outputs_for_sbox[i] = 0;
              for (int m = 0; m < sbox.length; m++) {
                  outputs_for_sbox[i] += sbox[m][inputs_for_sbox[m]][i];
              }
  
              double tmp_value;
              if (outputs_for_sbox[i] > this.threshold) {
                  tmp_value = this.threshold;
              } else {
                  tmp_value = outputs_for_sbox[i];
              }
  
              outputs[i] = tmp_value / (double) this.threshold;
          }
          return outputs;
      }
  
  
      public double backPropagate(double[] targetOutputs) {
          double[] outputError = new double[outputs.length];
  
          for (int i = 0; i < targetOutputs.length; i++) {
              /*if(targetOutputs[i] == 1) {
              	if(outputs[i]*this.threshold < this.threshold) {outputError[i] = (targetOutputs[i] - outputs[i])*this.threshold;}
              	else {outputError[i] = 0;}
              }
              if(targetOutputs[i] == 0) {
              	if(outputs[i]*this.threshold < this.threshold_min) {outputError[i] = 0;}
              	else {outputError[i] = (targetOutputs[i] - outputs[i])*this.threshold;}
              }*/
              outputError[i] = (targetOutputs[i] - outputs[i]) * this.threshold;
  
              if (Double.isNaN(outputError[i])) {
                  System.out.println("Problem at output " + i);
                  System.out.println(outputs[i] + " " + targetOutputs[i]);
                  System.exit(0);
              }
          }
  
          ////////////////////////////////////////////////////////////////////////////
          //CELLS UPDATE
          ///////////////////////////////////////////////////////////////////////////
          for (int i = 0; i < sbox[0][0].length; i++) {
              int outputError_tmp = (int) (outputError[i] * learningRate);
              if (outputError_tmp == 0) {
                  if (outputError[i] > 0) {
                      outputError[i] = 1;
                  }
                  if (outputError[i] < 0) {
                      outputError[i] = -1;
                  }
                  if (outputError[i] == 0) {
                      outputError[i] = 0;
                  }
              } else {
                  outputError[i] = outputError_tmp;
              }
  
              if (outputError[i] > 0) {
                  for (int r = 0; r < outputError[i]; r++) {
                      int row_number = random.nextInt(sbox.length);
                      int cell_number = inputs_for_sbox[row_number];
                      //if(sbox[row_number][cell_number][i] < 126)
                      {
                          sbox[row_number][cell_number][i]++;
                      }
                  }
  
              } else {
                  for (int r = 0; r < Math.abs(outputError[i]); r++) {
                      int row_number = random.nextInt(sbox.length);
                      int cell_number = inputs_for_sbox[row_number];
                      //if(sbox[row_number][cell_number][i] > -125)
                      {
                          sbox[row_number][cell_number][i]--;
                      }
                  }
              }
  
          }
  
          double summedOutputError = 0.0;
          for (int k = 0; k < targetOutputs.length; k++) {
              summedOutputError += Math.abs(targetOutputs[k] - outputs[k]);
          }
          summedOutputError /= outputs.length;
  
          // Return something sensible
          return summedOutputError;
      }
  
      public double getMutationMagnitude() {
          return 0;
      }
  
      public void setMutationMagnitude(double mutationMagnitude) {
          //this.mutationMagnitude = mutationMagnitude;
      }
  
      public static void setInitParameters(double mean, double deviation) {
          System.out.println("PARAMETERS SET: " + mean + "  deviation: " + deviation);
      }
  
      public void println() {
          System.out.print("\n\n----------------------------------------------------" +
                  "-----------------------------------\n");
          System.out.print("----------------------------------------------------" +
                  "-----------------------------------\n");
      }
  
      public String toString() {
          return "CyberNeuron by Sergey V. Polikarpov";
      }
  
      public void ssetLearningRate(double learningRate) {
          this.learningRate = learningRate;
      }
  
      public double[] getOutputs() {
          double[] outputsCopy = new double[outputs.length];
          System.arraycopy(outputs, 0, outputsCopy, 0, outputs.length);
          return outputsCopy;
      }
  
      public double[] getWeightsArray() {
  
          double[] weights = new double[sbox.length * sbox[0].length * sbox[0][0].length];
  
          int k = 0;
          for (int i = 0; i < sbox.length; i++) {
              for (int j = 0; j < sbox[i].length; j++) {
                  for (int m = 0; m < sbox[i][j].length; m++) {
                      k = i + j + m;
                      weights[k] = sbox[i][j][m];
                  }
              }
          }
          return weights;
      }
  
      public void setWeightsArray(double[] weights) {
          int k = 0;
  
          for (int i = 0; i < sbox.length; i++) {
              for (int j = 0; j < sbox[i].length; j++) {
                  for (int m = 0; m < sbox[i][j].length; m++) {
                      k = i + j + m;
                      sbox[i][j][m] = (char) weights[k];
                  }
              }
          }
      }
  
      public int getNumberOfInputs() {
          return inputs.length;
      }
  
      public void randomise() {
      }
  
      public double[] getArray() {
          return getWeightsArray();
      }
  
      public void setArray(double[] array) {
          setWeightsArray(array);
      }
  
      public int[] getPackedInputsToInt(double[] inputs) {
          int[] tmp_inputs = new int[sbox.length];
          for (int m = 0; m < sbox.length; m++) {
              tmp_inputs[m] = 0;
              for (int n = 0; n < num_bits_in_input; n++) {
                  if (inputs[m * num_bits_in_input + n] == 1) {
                      tmp_inputs[m] += powstwo[n];
                  }
              }
          }
          return tmp_inputs;
      }
  
  } 
  