//
//	3 Layer Back Propagation Neural Network Class (BPN.class)
//
//	Author: Patocchi L.(patol@info.isbiel.ch, lorenz@cerfim.ch)
//	Date:		21st Mai 1996
//	
//	This class simulates a 3 layered neural network providing learning
//	functions and in future also disk IOs 
//

import java.util.*;
import java.io.*;

public class BPN {

	public static final double	FIRE =			0.999;
	public static final double 	NEUTRAL =		0.0;
	public static final double 	DOWN =			-0.999;
	
	public static final double 	INIT =			-1.0;
	public static final double	DELTA =			0.0;
	public static final double	OUTPUT =		1.0;

	public double instantError;
	public double totalError;
	public double absoluteError;

	public double		inpA[];		/* activations 									*/
	private double		hidA[];		/* activations 									*/
	private double		hidN[];		/* sum of products 								*/
	private double		hidD[];		/* output error 								*/
	private double		hidW[][];	/* connection weights 							*/
	public double		outA[];		/* activations 									*/
	private double		outN[];		/* sum of products 								*/
	private double		outD[];		/* output error 								*/
	private double		oldD[];		/* old output error 							*/
	private double		outW[][];	/* connection weights 							*/
	private int				Ninp	;		/* number of neurons on input  layer 	*/
	private int				Nhid	;		/* number of neurons on hidden layer 	*/
	private int				Nout	;		/* number of neurons on output layer 	*/
	public double		eida 	;		/* learning rate 							*/
	private double		theta	;		/* sigmoid thresold							*/
	private double		elast	;		/* elastics of sigmoid						*/
	private double		momentum;
	
	public BPN(int i,int h,int o, double ei, double th, double el, double mo){
		
		Ninp = i;
		Nhid = h;
		Nout = o;
		
		this.inpA = new double[i];
		
		this.hidW = new double[h][i];

		this.hidA = new double[h];
		this.hidN = new double[h];
		this.hidD = new double[h];
		
		this.outW = new double[o][h];

		this.outA = new double[o];
		this.outN = new double[o];
		this.outD = new double[o];
		this.oldD = new double[o];
		
		eida 	= ei;
		theta	= th;
		elast	= el;
		momentum = mo;
		
		this.init();
		
	}

	private double sigmoid( double x){
  	// sig = 1.0 - Math.exp(-1.5* x + theta);

  	double sig = ( 1.0/ (1.0 + Math.exp(-1.0*elast* x + theta))*2.0-1.0);
  	return sig; // (sig < -1.0)? (-1.0):(sig> 1.0)? (1.0): sig;
	}

	private double d1sigmoid(double x){
  //double sig = sigmoid(n,x);
  
  	return 2.0 * Math.exp(-1.0 * elast * x -  theta)/(1+Math.exp(-2.0 * elast * x -  theta)); 
	}

	public void feedForward(){
		int 	i,j;
		double	sum2;

		for(i=0; i < Nhid; i++){
			sum2 = 0.0;
			for(j=0; j < Ninp; j++) sum2 += hidW[i][j]* inpA[j];
			hidN[i] = sum2;
			hidA[i] = sigmoid(sum2);
		}
		
		for(i=0; i < Nout; i++){
			sum2 = 0.0;
			for(j=0; j < Nhid; j++) sum2 += outW[i][j]* hidA[j];
			outN[i] = sum2;
		}
	}
	
	public double computeDelta(int m){
		int	i;

		outD[m] = (outA[m] - sigmoid(outN[m]))*(d1sigmoid(outN[m])+0.1);

		for(i=0; i < Nhid; i++) outW[m][i] += outD[m]* hidA[i]* eida ;

		return outD[m]; // /(sig1(n,n->outN[m])+0.1);
	}
	
	public void updateWeights()
	{
		int 	i,m;
		double	sum2;

		for(m=0;m < Nhid; m++){
			sum2 = 0.0;
			for(i=0;i < Nout;i++){ 
				sum2 += outD[i]* outW[i][m];
			};
			sum2 *= d1sigmoid(hidN[m]);
			for(i=0;i < Ninp;i++) hidW[m][i] += eida * sum2 * inpA[i];
		}
	}

	public double frandom(double min, double max){
		return Math.random()*(max - min) + min;
	}

	public void propagate(double[] vector) throws ArrayIndexOutOfBoundsException{
  	int		i,j;
  	double	sum2;
  	
  	if(vector.length != Ninp)
  		throw new ArrayIndexOutOfBoundsException("Error: Vector size don't match Network input size !");
  	
    for(i=0; i<Ninp; i++) inpA[i] = vector[i];
    	
		for(i=0;i < Nhid ; i++){
			sum2 = 0.0;
			for(j=0;j < Ninp;j++) sum2 += hidW[i][j] * inpA[j];
			hidA[i] = sigmoid(sum2);
		}
		for(i=0;i < Nout;i++){
			sum2 = 0.0;
			for(j=0;j < Nhid;j++) sum2 += outW[i][j] * hidA[j];
			outA[i] = sigmoid(sum2);
		}	
	}

	public void init(){
	int 	i,m;

		for(i=0; i < Ninp; i++) inpA[i] = frandom(-1.0,1.0);
		for(i=0; i < Nhid;i++){
			hidA[i] = frandom(-1.0,1.0);
			for(m=0; m < Ninp; m++) hidW[i][m] = frandom(-1.0,1.0);
		}
		for(i=0; i < Nout; i++) for(m=0; m < Nhid; m++) outW[i][m] = frandom(-1.0,1.0);
	
		totalError		= 0.0;
		absoluteError	= 0.0;
	}
	
	public void trickForlearning(double min, double max){
		int i;
		for(i=0; i<Ninp; i++) inpA[i] += frandom(min,max);
		inpA[i] = (inpA[i]>FIRE)? FIRE: (inpA[i]<DOWN)? DOWN: inpA[i];
	}
	
	public void learnVector(double[] in, double out []) throws ArrayIndexOutOfBoundsException{
		int i,j;
	  	if(in.length != Ninp)
  			throw new ArrayIndexOutOfBoundsException("Error: In Vector size don't match Network input size !");
  		if(out.length != Nout)
  			throw new ArrayIndexOutOfBoundsException("Error: Out Vector size don't match Network output size !");
  	
		for(i=0; i<Ninp; i++) inpA[i] = in[i];
		for(i=0; i<Nout; i++) outA[i] = out[i];
		
		this.feedForward();
		
		totalError		= 0.0;
		absoluteError	= 0.0;		
		for(j=0; j < Nout ; j++){
	  		this.instantError 	= this.computeDelta(j);
	  		this.totalError 	+= this.instantError;
	  		this.absoluteError	+= Math.abs(instantError);
	  	}
	  
	  this.updateWeights();
 	
		this.eida *= this.momentum;
	}

	public boolean saveNeuro(String path, String name){
		int i,j;
		if(name == null || path == null) return false;
		
		try{
			File rawfile = new File(path, name);
			try{
				RandomAccessFile file = new RandomAccessFile(rawfile,"rw");
			
				file.writeUTF("3LNW V1.0");
				file.writeInt(Ninp);
				file.writeInt(Nhid);
				file.writeInt(Nout);
				file.writeDouble(eida);
				file.writeDouble(theta);
				file.writeDouble(elast);
				file.writeDouble(momentum);
				for(i=0;i < Ninp; i++)
					for(j=0;j < Nhid; j++) file.writeDouble(hidW[j][i]);
				for(i=0;i < Nhid; i++)
					for(j=0;j < Nout; j++) file.writeDouble(outW[j][i]);
				
				file.close();
			}catch(IllegalArgumentException iae){/*I'M A GOOD PROGRAMMER*/;}
		}	
		catch(IOException ioe){return false;}
		catch(SecurityException se){return false;}

				return true;
	}
	
	public boolean loadNeuro(String path, String name){
		int i,j;
		if(name == null || path == null) return false;
		
		try{
			File rawfile = new File(path, name);
			try{
				RandomAccessFile file = new RandomAccessFile(rawfile,"r");
			
				if(file.readUTF().compareTo("3LNW V1.0") != 0){
					file.close();
					return false;
				}
				Ninp = file.readInt();
				Nhid = file.readInt();
				Nout = file.readInt();
				eida = file.readDouble();
				theta = file.readDouble();
				elast = file.readDouble();
				momentum = file.readDouble();
				
				//this(Ninp, Nhid, Nout, eida, theta, elast, momentum);
				this.inpA = new double[Ninp];
				
				this.hidW = new double[Nhid][Ninp];
		
				this.hidA = new double[Nhid];
				this.hidN = new double[Nhid];
				this.hidD = new double[Nhid];
				
				this.outW = new double[Nout][Nhid];
		
				this.outA = new double[Nout];
				this.outN = new double[Nout];
				this.outD = new double[Nout];
				this.oldD = new double[Nout];
				
				for(i=0;i < Ninp; i++)
					for(j=0;j < Nhid; j++) hidW[j][i] = file.readDouble();
				for(i=0;i < Nhid; i++)
					for(j=0;j < Nout; j++) outW[j][i] = file.readDouble();
				
				file.close();
			}catch(IllegalArgumentException iae){/*I'M A GOOD PROGRAMMER*/;}
		}
		catch(IOException ioe){return false;}
		catch(SecurityException se){return false;}		
		
		return true;
	}

}
