/*
 * Created on Nov 22, 2004
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package jone;

import java.util.Random;

/**
 * @author brian
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class NNLayer {

	NNLayer(int inputsize, int numNeurons, int neuronType)
	{
		numinputs = inputsize;
		numneurons = numNeurons;
		type = neuronType;
		wts = new double[numinputs][numNeurons];
		initializeLayer();
		
		
	}
	NNLayer(NNLayer nn)
	{
		this.numinputs = nn.numinputs;
		this.numneurons = nn.numneurons;
		this.type = nn.type;
		this.wts = (double [][]) nn.wts.clone(); 
	}
	/**
	 * initializes each layer to contain wts with small random values
	 * that are gaussian normally distributed with a mean of 0 and a std
	 * of 1
	 *
	 */
	void initializeLayer()
	{
		Random random = RandomSingleton.getDefaultInstance();
    	double scalar = .1;
			
    	for(int i = 0; i < numinputs; i++) {
			for(int j = 0; j < numneurons; j++) {
				wts[i][j] = random.nextGaussian()*scalar;	
			}
		}
    	
	}
	/**
	 * prints the neuron weights in this layer
	 *
	 */
	void print()
	{
		for(int i = 0; i < numinputs; i++) {
			for(int j = 0; j < numneurons; j++) {
				System.out.print(NeuralNetTBox.printDouble(wts[i][j],6) + " "); 
				
			}
			System.out.println("");
		}
		
	}
	
	/**
	 * computes the netk given the weights and this sample  
	 * @param inputs sample pattern
	 * @return the netkoutput with no activation function applied
	 */
	double [] computeNetk(double [] inputs)
	{	
		netk = NeuralNetTBox.multvectorbymatrix(wts, inputs, numneurons);
		return netk;
	}
	
	/**
	 * performs weight adjustment using sensitivities calculated earlier
	 * 
	 * @param netkprev the net of each neuron in the previous layer
	 * @param prevlayertype layer type of the previous layer
	 * @param firstlayer true if this is the first layer
	 * @param nu learning rate affects the amount of change performed on weights
	 */
	void adjustWeights(double [] netkprev, int prevlayertype, boolean firstlayer, double nu)
	{
		for(int p = 0; p < numneurons; p++) {
			for(int r = 0; r < numinputs-1; r++) {
				double yi =  firstlayer ? netkprev[r] : NeuralNetTBox.f(netkprev[r],prevlayertype,true); // is this input or net of previous layer
				wts[r][p] -= nu*sensitivities[p]*yi;         						
			}
			
			/* adjusts for the bias, the bias input is -1, so we add here */
			wts[numinputs-1][p] -= nu*sensitivities[p]; // sign?
		}
	}
	
	/**
	 * computes the sensitivities for the output layer based on the error
	 * @param error (desired - nn output) vector
	 */
	void computeOutputSens(double [] error)
	{
		/* the sensitivities in the output layer are calculated as
     	 * deltaK = -2*eK * f'(netK)
     	 */
     	for(int k = 0; k < numneurons; k++) {
     		
     		sensitivities[k] = -2*error[k]*NeuralNetTBox.df(netk[k],type,true);
     	}
	}
	
	/**
	 * computes sensitivities for this layer
	 * @param next next layer in neural net
	 */
	void computeHiddenSens(NNLayer next)
	{		
		sensitivities = next.computeSensForPreviousLayer(netk,type);
	}
		
	/**
	 * computes sensitivities for the previous layer
	 * @param net1k netk for previous layer
	 * @param type neuron type of previous layer
	 * @return computed sensitivities
	 */
	double [] computeSensForPreviousLayer(double [] net1k, int type)
	{
		double [] deltaprev = new double[net1k.length];
		for(int r = 0; r < net1k.length; r++) {
			double sum = 0.0;
			for(int p = 0; p < numneurons; p++) 
				sum += sensitivities[p] * wts[r][p];
			sum *= NeuralNetTBox.df(net1k[r],type,true);
			deltaprev[r] = sum;
		}
		return deltaprev;
	}
	
	NNLayer(double n1bias,NNLayer n1, NNLayer n2)
	{
		numinputs = n1.numinputs; 
		numneurons = n1.numneurons; 
		type = n1.type;
		wts = new double[numinputs][numneurons];
		Random random = RandomSingleton.getDefaultInstance();
		
		try {
		for(int p = 0; p < numneurons; p++) {
			for(int r = 0; r < numinputs; r++) {
				wts[r][p] = (random.nextDouble() > n1bias) ? n1.wts[r][p] : n2.wts[r][p];
			}
		}
		}
		catch (Exception e)
		{
			if(Arena.debug)
			{
				System.out.println("n1 is ");
				n1.print();
				System.out.println("n2 is ");
				n2.print();
			}
		}
	}
	
	public void swapWeights(double prob)
	{
		Random random = RandomSingleton.getDefaultInstance();
		double temp;
		int i, j, ii, jj;
		for(int p = 0; p < numneurons/2; p++) {
			for(int r = 0; r < numinputs/2; r++) {
				if (random.nextDouble() > prob) {
					i = random.nextInt(numinputs);
					ii = random.nextInt(numinputs);
					j = random.nextInt(numneurons);
					jj = random.nextInt(numneurons);
					temp = wts[i][j];
					wts[i][j] = wts[ii][jj];
					wts[ii][jj] = temp;		 
				}
			}
		}
	}
	
	public void addNoiseToWeights(double scalar, double prob)
	{
		Random random = RandomSingleton.getDefaultInstance();
		for(int p = 0; p < numneurons/2; p++) {
			for(int r = 0; r < numinputs/2; r++) {
				if(random.nextDouble() > prob) {
					wts[r][p] += scalar * random.nextGaussian();
				}
			}
		}
	}
	/**
	 * 
	 * @return returns the netk for this layer
	 */
	double [] getNetK()
	{
		return netk;
	}
	double [][] wts;
	double [] netk;
	double [] sensitivities;
	int numinputs, numneurons;
	int type;
}

