package MyPackage;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Ali Shahbazi
 */

import QuasiRandom.*; 
import java.util.Random;

public class ERandom {//evolved Random
    //types
    public static final int PSEUDO = 1;
    public static final int SOBOL = 2;
    public static final int NIEDERREITER = 3;
    public static final int HALTON = 4;
    public static final int UNIFORM = 5;
    public static final int RANDOMIZED_UNIFORM = 6;
    public static final int UNIFORM_ROOT = 7;

    public double[] dataLowerBound;
    public double[] dataHigherBound;
    
    private int Dim;
    private int Type;

    Sobol sobol;
    NX nx;
    Halton halton;
    Random pseudo;
    Random randomizedUniform;

    public ERandom(int dimension, int type){
        this.Dim=dimension;
        this.Type=type;
        
        if(type==PSEUDO){
            pseudo=new Random();
        }
        else if(type==SOBOL){
            sobol=new Sobol(dimension);
        }
        else if(type==NIEDERREITER){
            nx=new NX(dimension);
        }
        else if(type==HALTON){
            halton=new Halton(dimension);
        }
        else if(type==RANDOMIZED_UNIFORM){
            randomizedUniform=new Random();
        }
        else if(type==UNIFORM){
            //nothing
        }
        else if(type==UNIFORM_ROOT){
            //nothing
        }
        else{
            System.err.println("Error: ERandom: Random type has not been defined");
        }
        
  
        dataLowerBound=new double[dimension];
        dataHigherBound=new double[dimension];
        for(int d=0;d<dimension;d++){
            dataLowerBound[d]=0;
            dataHigherBound[d]=1;
        }
    }
    
    public void SetBound(double min, double max){
        for(int d=0;d<Dim;d++){
            dataLowerBound[d]=min;
            dataHigherBound[d]=max;
        }
    }
    
    private void scalePoint(double[] data){
        for(int d=0;d<data.length;d++){
            data[d]= (data[d])*(dataHigherBound[d]-dataLowerBound[d])+dataLowerBound[d];
        }
    }
    
    private void scalePoints(ArrayDouble2D array){
        //do we need scaling?
        boolean scale=false;
        for(int d=0;d<Dim;d++){
            if(dataLowerBound[d]!=0 || dataHigherBound[d]!=1){
                scale=true;
                break;
            }
        }

        //scale
        if(scale==true){
            for (int i = 0; i < array.GetSize(); i++) {
                scalePoint(array.Arr[i]);
            }
        }
    }
    
    public void SetSeed(long seed){

        if(Type==PSEUDO){
            this.pseudo.setSeed(seed);
        }
        else if(Type==SOBOL){
            this.sobol.restart();
        }
        else if(Type==NIEDERREITER){
            this.nx.restart();
        }
        else if(Type==HALTON){
            this.halton.restart();
        }
        else if(Type==RANDOMIZED_UNIFORM){
            this.randomizedUniform.setSeed(seed);
        }
        
    }
    
    public double[] NextPoint(){
        int i;
        double[] data=new double[this.Dim];
        
        if(Type==PSEUDO){
            for(i=0;i<this.Dim;i++)
                data[i]=this.pseudo.nextDouble();
        }
        else if(Type==SOBOL){
            this.sobol.nextPoint(data);
        }
        else if(Type==NIEDERREITER){
            this.nx.nextPoint(data);
        }
        else if(Type==HALTON){
            this.halton.nextPoint(data);
        }
        else{
            System.err.println("Error: ERandom.NextPoint() Random type has not been defined");
        }
        
        scalePoint(data);
        return data;
    }
    
    public void NextPoints(ArrayDouble2D array){
        int i,j;
        if(array.GetDimension()<this.Dim){
            System.err.println("Error: ERandom.NextPoints() the input array dimension is low");
            return;
        }

        if(Type==PSEUDO){
            for(i=array.GetStartIndex();i<(array.GetStartIndex()+array.GetSize());i++){
                for(j=0;j<this.Dim;j++)
                    array.Arr[i][j]=this.pseudo.nextDouble();
            }
        }
        else if(Type==SOBOL){
            for(i=array.GetStartIndex();i<(array.GetStartIndex()+array.GetSize());i++)
                this.sobol.nextPoint(array.Arr[i]);
        }
        else if(Type==NIEDERREITER){
            for(i=array.GetStartIndex();i<(array.GetStartIndex()+array.GetSize());i++)
                this.nx.nextPoint(array.Arr[i]);
        }
        else if(Type==HALTON){
            for(i=array.GetStartIndex();i<(array.GetStartIndex()+array.GetSize());i++)
                this.halton.nextPoint(array.Arr[i]);
        }
        else if(Type==UNIFORM){
            this.UniformGrid(array,0.5);
        }
        else if(Type==UNIFORM_ROOT){
            this.UniformGrid(array,0);
        }
        else if(Type==RANDOMIZED_UNIFORM){
            this.RandomizedUniformGrid(array);
        }
        
        scalePoints(array);
        
    }

    private int getEachDimensionNum(int originalTestSetSize){
        int Num=(int)Math.pow(originalTestSetSize, 1.0/this.Dim); //number of points in each dimention
        //
        int temp=1;
        for(int a=0;a<this.Dim;a++){
            temp*=(Num+1);
        }
        if(temp<=originalTestSetSize)
            Num++;
        return Num;
    }
    private void UniformGrid(ArrayDouble2D array, double startCordinate){//startCordinate: 0~1


        int i,j,k;
        int Num=getEachDimensionNum(array.GetSize());
        int total_num=(int)Math.pow(Num, this.Dim);

        if(array.GetDimension()<this.Dim){
            System.err.println("Error: ERandom.UniformGrid() the input array dimension is low");
            return;
        }
        if(array.GetSize()<total_num){
            System.err.println("Error: ERandom.UniformGrid() the input array size is low");
            return;
        }
        array.SetSize(total_num, array.GetStartIndex());

        //points
        //init
        double step=1/(double)Num;
        double start=step*startCordinate;
        double[] arr=new double[this.Dim];
        for(i=0;i<this.Dim;i++)
            arr[i]=start;
        //alg
        for(i=0;i<total_num;i++){
            //copy result
            for(k=0;k<this.Dim;k++)
                array.Arr[i+array.GetStartIndex()][k]=arr[k];
            
            for(j=0;j<this.Dim;j++){
                arr[j]+=step;
                if(arr[j]>0.9999999999)
                    arr[j]=start;
                else
                    break;
            }
        }

    }

    int RandInt(int start, int end){//between start and end-1
        int res=(int)(randomizedUniform.nextDouble()*(end-start)+start);
        if(res==end)
            res=end-1;
        return res;
    }
    void ArrayTwoMemberReplace(ArrayDouble2D array,int i,int j){
        double[] arr=new double[array.GetDimension()];

        System.arraycopy(array.Arr[i], 0, arr, 0, array.GetDimension());
        System.arraycopy(array.Arr[j], 0, array.Arr[i], 0, array.GetDimension());
        System.arraycopy(arr, 0, array.Arr[j], 0, array.GetDimension());
    }
    void SlightModify(double[] arr, double var){
        double v;
        for(int i=0;i<arr.length;i++){
            v=(randomizedUniform.nextDouble()-0.5)*var;
            arr[i]+=v;
        }
    }
    public void RandomizedUniformGrid(ArrayDouble2D array){//LHS: latin hypercube sampling


        int i,j,k;
        int Num=getEachDimensionNum(array.GetSize()); //number of points in each dimention
        this.UniformGrid(array, 0.5);

        double variation=1.0/Num;

        for(i=array.GetStartIndex();i<array.GetEndIndex();i++){
            k=RandInt(i,array.GetEndIndex());
            //replace
            ArrayTwoMemberReplace(array,i,k);
            SlightModify(array.Arr[i],variation);
        }

    }

}
