/*
 * Copyright (C) 2014 avigier, poussin
 *
 * 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.
 * 
 * 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.
 * 
 * You should have received a copy of the GNU General Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 */

package optimizations;

import static org.nuiton.i18n.I18n._;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import fr.ifremer.isisfish.util.Doc;
import fr.ifremer.isisfish.simulator.Optimization;
import fr.ifremer.isisfish.simulator.OptimizationContext;
import fr.ifremer.isisfish.datastore.SimulationStorage;
import fr.ifremer.isisfish.datastore.ResultStorage;
import fr.ifremer.isisfish.entities.Population;
import fr.ifremer.isisfish.entities.PopulationGroup;
import fr.ifremer.isisfish.entities.PopulationSeasonInfo;
import fr.ifremer.isisfish.types.Month;
import java.io.File;
import java.io.FileReader;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import org.apache.commons.io.FileUtils;
import org.nuiton.math.matrix.MatrixFactory;
import org.nuiton.math.matrix.MatrixIterator;
import org.nuiton.math.matrix.MatrixND;
import org.nuiton.topia.TopiaContext;
import org.nuiton.util.FileUtil;
import scripts.ResultName;

/**
 * RecuitSimuleVoisinageAdaptatifCristallisationT10Param10ansSCE.java
 *
 * // ////////////////////////////////////////////////////////////////////////
 * USER GUIDE
 *-------------------------------------------------------------------------
 * Script must be adapted to the case study (calibration data, catchability assumptions)
 * Script won't compile as it stands
 * Comments preceeded by /////*** explain where and how to adapt the script
 * Access to the APIs is free
 * ////////////////////////////////////////////////////////////////////////
 *
 * **You can modify class name if you want
 * **BUT attention : file name and class name must be the same (without the extention ".java"),
 * ie here :  "RecuitSimuleVoisinageAdaptatifCristallisationT10Param10ansSCE"
 * 
 *
 * Created: 4 avril 2014
 *
 * @author poussin <user.name@vcs.hostName>
 * @version $Revision: 1545 $
 * Last update: $Date: 4 avril 2014 $
 * by : $Author: poussin $
 */
public class RecuitSimuleVoisinageAdaptatifCristallisationT10Param10ansSCE implements Optimization {

    /** to use log facility, just put in your code: log.info("..."); */
    private static Log log = LogFactory.getLog(RecuitSimuleVoisinageAdaptatifCristallisationT10Param10ansSCE.class);

        /////***here must appear the path to export the historic file ("Historic.csv")
    //in which all information about each simulation is stored
    /////***Attention : before beginning a new calibration rename any potential
    //old Historic.csv files or they will be lost

    @Doc("Path to export the historic file. The root is the folder where the .bat is located.")
    public String param_exportPath="Output_essai_recuit/HistoricRecuitSimuleVoisinageAdaptatifCristallisationT10Param10ansSCE.csv";
    protected String exportHisto = "";

    @Doc("Population which parameters are calibrated")
    public Population param_Population = null;
    @Doc("Lower values of parameters, separated with semicolons: de la forme(\"xx1;xx2;xx3\")")
    public String param_borneInf = "0;0;0;0;0;0;0;0;0;0";// devient un parametre du plan d analyse/// Rentrer ici les bornes inferieruers de chaque parametre.
    @Doc("Upper values of parameters, separated with semicolons: de la forme(\"xx1;xx2;xx3\"); Keep the order used to fill borneInf.")
    public String param_borneSup = "0.1;0.1;0.1;0.1;0.1;0.1;0.1;0.1;0.1;0.1";// devient un parametre du plan d analyse/// Rentrer ici les bornes superieruers de chaque parametre.
    @Doc("Cooling schedule : choose between \"Van Laarhoven\", \"Huang\", \"Triki\", \"Geometric\", \"Lundy\", \"Constant\" and \"Linear\".")
    public String param_coolingSchedule = "Geometric"; // Attention aux methodes a valeurs a fixer par l'utilisateur.
    @Doc("Parameter associated with the cooling schedule (if \"Constant\" or \"Triki\", ignore this): delta for \"Van Laarhoven\", lambda for \"Huang\", alpha for \"Geometric\", beta for \"Lundy\" and coefficient for \"Linear\".")
    public double param_coolTemp= 0.9975;
    //public long param_seed = 1;
    @Doc("Delta Value for each parameter. Delta is the maximum amplitude of mutation for a parameter when going to the next state.")
    public String param_deltaValue="0.01;0.01;0.01;0.01;0.01;0.01;0.01;0.01;0.01;0.01";// Valeur de delta
    @Doc("Way to alter delta. For this simulationplan, the only choice is \"Aleatoire\" (we use Martins et al. adaptive simulated annealing).")
    public String param_variationsDelta="Aleatoire"; // plutot ne pas laisser le choix a l'utilisateur ce coup-ci?
    @Doc("This is the first paarmetrisation, so you can force the algorithm to start far or near the global optimum...or in the middle of nowhere!")
    public String param_valeursConseil = "0.0350368329556659 ; 0.020098804589361 ; 0.059386002831161 ; 0.0297067735344172 ; 0.0769115518778563 ; 0.0865450936136767 ; 0.0299217076972127 ; 0.0582443927880377 ; 0.0659311252413318 ; 0.0480358958244324" ;

    String [] borneInf;
    String [] borneSup;
    String [] valeursConseil;
    String [] deltaValue;
    int taille;

	int c1;
	int c2;
    int current;
    int compteurSimus;
    int compteurTemperature;
    int compteurAccept;
    double deltaEnergy;
    SecureRandom random;
    double rhoValue;
    boolean bool;
    double obj;
    
	//int seuilTemperature=5;// Laisses au choix de l'utilisateur. Dans l'interface, proposer des methodes pour faire evoluer les seuils?
	//int seuilAcceptance=3;
	double seuilArret=0.01; //Sera utilise en fonction du critee d'arret trancher.

   	// ***put here the path and name of the file containing the data used to calibrate
   	// your fishery ( here observed landings per season and age groups)
  	@Doc(value = "file name and path of observed landings")
  	public String param_nomfichier_debarquements = "Input_essai_recuit/Inputlandings10param10ansglob.csv";
  	protected File debarquementsObserves;
   	protected MatrixND matrixDebarquement;

   	ArrayList<Experience> historique = new ArrayList<Experience>(); //historique va contenir tout l'historique de l'algorithme, c'est un ArrayList d'Experience.


    protected String[] necessaryResult = {
        ResultName.MATRIX_CATCH_WEIGHT_PER_STRATEGY_MET_PER_ZONE_POP
    };

    @Override
    public String[] getNecessaryResult() {
        return this.necessaryResult;
    }

    /**
     * Permet d'afficher a l'utilisateur une aide sur le plan.
     * @return L''aide ou la description du plan
     */
    @Override
    public String getDescription() throws Exception {
        return _("Calibration using simulated annealing: user" +
                 "gives a file of observations (here catches)(.csv), simulated output" +
                 "will try to approach oservations by changing the values of catchability");
    }

    /**
     * Appele lors de l'initialisation. La premiere generation doit etre construite
     * dans l'init via des appels a context.addSimulation(...)
     *
     * @param context
     */
    public void init(OptimizationContext context) throws Exception {
        random = new SecureRandom();
        //random.setSeed(param_seed);

        if (param_nomfichier_debarquements == null
                || "".equals(param_nomfichier_debarquements)) {
            debarquementsObserves = FileUtil.getFile(".*.csv",
                    "fichier csv separateur ';'");
        } else {
            debarquementsObserves = new File(param_nomfichier_debarquements);
        }

        // ***Create the matrix named matrixDebarquement that will contain your observed landings
        int[] dimMatrix = {120,1};
        matrixDebarquement=MatrixFactory.getInstance().create(dimMatrix); //date est un int donnant le nombre de mois sur lequel on a simule.
        // ***Then import your file in it
		FileReader in = new FileReader(debarquementsObserves);
        matrixDebarquement.importCSV(in,new int []{0,0});
		in.close();
        matrixDebarquement= matrixDebarquement.reduce(); //Indispensable pour pouvoir travailler sur la matrice et avoir les memes dimensions que les simulations.
        //log.info("MatrixDebarquement : " + matrixDebarquement);

        borneInf=param_borneInf.split(";");
        borneSup=param_borneSup.split(";");
        valeursConseil=param_valeursConseil.split(";");
        deltaValue = param_deltaValue.split(";");
        taille=borneInf.length;
    }

    /**
     * La premiere generation doit etre construite dans cette methode
     * via des appels a context.addSimulation(...)
     *
     * @param context
     */
    public void firstSimulation(OptimizationContext context) throws Exception {
        SimulationStorage nextSimulation = context.newSimulation();
        compteurSimus = context.getCurrentGeneration(); //numero de la simulation a venir
		log.error("compteurSimus firstsimu: " + compteurSimus);
        Experience expCurrent = createExperience(compteurSimus, null);
        expCurrent.accepted = true;
        changeDB(expCurrent, nextSimulation);
    }

    /**
     * Genere une nouvelle serie de simulation suivant le context d'optimisation.
     * Pour cela vous devez appeler context.addSimulation(...) pour ajouter
     * des simulations pour la prochaine generation.
     *
     * @param context context
     * @param region region
     * @return simulations serie
     */
    public void nextSimulation(OptimizationContext context) throws Exception {
        Experience expParent;
        SimulationStorage nextSimulation = context.newSimulation();
		compteurSimus = context.getCurrentGeneration(); // compteurSimus = numero de la simu qui va commencer
		log.error("compteurSimus nextsimu: " + compteurSimus); // Si le compteurSimus bouge a nouveau, bien tout rechanger comme il faut
		log.error("c1 " + c1);
		log.error("c2 " + c2);
        if (historique.get(compteurSimus - 1).accepted){
            expParent = historique.get(compteurSimus - 1);
        }
        else{
            expParent = historique.get(compteurSimus - 1).parent;
        }
        Experience expCurrent = createExperience(compteurSimus, expParent);
		if (expCurrent.id > 1){
			expCurrent.temperature = baisse(expCurrent);
        }
		log.error("expCurrent : experience numero " + expCurrent.id);
		log.error("parametrisation expCurrent" + expCurrent.parametrisation[0].value + " " + expCurrent.parametrisation[1].value + " " + expCurrent.parametrisation[2].value + " " + expCurrent.parametrisation[3].value + " " + expCurrent.parametrisation[4].value + " " + expCurrent.parametrisation[5].value + " " + expCurrent.parametrisation[6].value + " " + expCurrent.parametrisation[7].value + " " + expCurrent.parametrisation[8].value + " " + expCurrent.parametrisation[9].value);

        changeDB(expCurrent, nextSimulation);

    }

    /**
     * Cette methode est appelee apres chaque serie de simulation
     * soit apres startSimulation et nextSimulation
     * @param context
     */
    public void endSimulation(OptimizationContext context) throws Exception {
        //Dans l'after, on evalue la nouvelle parametrisation, on met en competition cette parametrisation
        //avec celle de reference (celle de l'iteration current), on modifie la temperature, on verifie que le
        //critere d'arret de l'algorithme n'est pas atteint, on enregistre toutes les modifications dans la table historique.
        SimulationStorage lastSimulation = context.getLastSimulation();


        File exportHistoric = new File(param_exportPath);
        ResultStorage result = lastSimulation.getResultStorage();
        boolean bool=true;
        compteurSimus=context.getCurrentGeneration() - 1; //numero de la simulation qui vient de se terminer
		log.error("compteurSimus endsimu : " + compteurSimus); // Si le compteurSimus bouge a nouveau, bien tout rechanger comme il faut
        Experience expCurrent = historique.get(compteurSimus); // SOUCI DE ARRAYOUTOFBOUNDEXCEPTION A L ITERATION 0
        double obj=calculFonctionObjectif(result);
        expCurrent.objective = obj;
        if (compteurSimus > 0){
            if (obj < expCurrent.best.objective){//On compare la nouvelle parametrisation a la meilleure parametrisation
                expCurrent.best = expCurrent;
            }
            acceptationSolution(expCurrent);//On compare la nouvelle parametrisation a la parametrisation courante et on decide qui sera la courante.
            //if (isModifierTemperature()){//Pour le moment, les methodes proposees modifient la temperature a chaque fois. On integrera un systeme de phase et de verification de critere de temperature plus tard, si besoin.
            /*}
            else{
            historique.get(compteurSimus).temperature = getExperience(compteurSimus-1).temperature;
            }*/
            //bool=isCritereArretAtteint(expCurrent);
        }
        if (compteurSimus==0){
            expCurrent.parent=expCurrent;
			expCurrent.temperature = 0;
        }
		if (compteurSimus==1){ // (Autin (2006))
			expCurrent.temperature = 10*(expCurrent.objective - historique.get(0).objective);
		}
        exportHisto+=expCurrent.toCSV(); //On laisse sous cette forme tant qu'il faut aller consulter les logs, mais c'est lourd. Une fois le script fini, passer l'ecriture du fichier au moment de l'arret complet de l'algorithme
        FileUtils.writeStringToFile(exportHistoric,exportHisto);
        if (compteurSimus >= 2000){
            bool = false;
        }
        log.info("Faut-il faire encore une simulation? " + bool);
        if(compteurSimus > 50){// Modification du voisinage
            changeDelta(expCurrent);
        }
		lastSimulation.closeStorage();
	}

    /**
     * Cette methode est appelee lorsqu'il n'y a plus de simulation a faire
     * (init ou getNextSimulation n'ont pas fait appel a context.addSimulation)
     * @param context
     */
    public void finish(OptimizationContext context) throws Exception {

    }

    /////***hasard1,2,3, etc => des methodes de hasard a determiner, a placer en debut de script. Pour le moment, elles ne font que de l'uniforme, a nous de voir ce qui est le mieux et pourquoi
    public int hasard1(){
        int result = random.nextInt(taille);
        return result;
    }
    public int hasard2(int max){
        int result = random.nextInt(max);
        log.info("result : "+result);
        return result;
    }
    public double hasard3(int num, Parameter[] M1, double cristallisation){// POUR UN DELTA FIXE
        double result = M1[num].delta*(2*random.nextInt(2)-1);
        return result;
    }
    public double hasardUniforme(){
        double result = random.nextDouble();
        return result;
    }

    public double hasardAlea(int num, Parameter[] M1, double cristallisation){
        log.error("Je calcule : (" + random.nextDouble()+"*2*" + M1[num].delta + "-" + M1[num].delta +")/" + cristallisation);
        double result = (random.nextDouble()*2*M1[num].delta-M1[num].delta)/cristallisation;
        log.error("result hasardAlea " + result);
        return result;
    }

////////////////////
////////////////////
///              ///
///AUTRES CLASSES///
///              ///
////////////////////
////////////////////

    protected Parameter[] copyM1(Parameter[]M1){
        Parameter[] result =  new Parameter[M1.length];
        for (int i=0,maxi=M1.length; i<maxi; i++) {
            result[i] = M1[i].copy();
        }
        return result;
    }

    class Parameter {
        double value;
        double inf;
        double sup;
        double delta;

        public Parameter (double valeur, double borneinf, double bornesup, double deltaValue){
            /*if (borneinf >= bornesup) {
            throws SimulationException(String.format("Error: inf(%s) >= sup(%s)", borneinf, bornesup));
            }*/
            value=valeur;
            inf=borneinf;
            sup=bornesup;
            delta=deltaValue;
        }

        public Parameter copy() {
            return new Parameter(value, inf, sup, delta);
        }
    }

    class Experience{// 1 objet Experience contient toutes les informations sur la passage de l'etat current a l'etat suivant.
        public int id;
        public Experience parent;
        public Experience best;
        public boolean accepted;
        public double cristallisation;
        public double rho;
        public double rand;
        public Parameter[] parametrisation;		//On enregistre la parametrisation et les bornes, tout est necessaire en cas de re-utilisation.
        public double objective;
        public double temperature;

        public Experience(int id, Experience parent) {
            this.id = id;
            this.parent = parent;

            if (parent == null) {
				log.error("Pas de parent");
                best = this;
                parametrisation = initM1();
                cristallisation = 1;
            } else {
                Experience expPrev = historique.get(id-1);
                best = expPrev.best;
                if (expPrev.accepted == false){
                    cristallisation = 1;
                }else{
                    cristallisation = expPrev.cristallisation + 1;
                }
                temperature=expPrev.temperature;
                Parameter[] M1=copyM1(parent.getParametrisation());
                log.error("APPEL MODIF DANS LE CONSTRUCTEUR");
                modif(M1, cristallisation);
				log.error("parametrisation avant modif " + parent.parametrisation[0].value + " " + parent.parametrisation[1].value + " " + parent.parametrisation[2].value + " " + parent.parametrisation[3].value + " " + parent.parametrisation[4].value + " " + parent.parametrisation[5].value + " " + parent.parametrisation[6].value + " " + parent.parametrisation[7].value + " " + parent.parametrisation[8].value + " " + parent.parametrisation[9].value);
                parametrisation = M1;
				log.error("parametrisation apres modif " + parametrisation[0].value + " " + parametrisation[1].value + " " + parametrisation[2].value + " " + parametrisation[3].value + " " + parametrisation[4].value + " " + parametrisation[5].value + " " + parametrisation[6].value + " " + parametrisation[7].value + " " + parametrisation[8].value + " " + parametrisation[9].value);
            }
        }

        /**
         * recherche de la 1ere experience accepte et retourne parametrisation
         */
        public Parameter[] getParametrisation() {
            Parameter[] result = parametrisation;
            if (!accepted) {
                result = parent.getParametrisation();
            }
            return result;
        }

        public String toCSV() {
            String sep = ";";
            String result = "";
            String saut = "\n";

            result += id + sep;
            result += accepted + sep;
            result += rand + sep;
            result += rho + sep;
            result += objective + sep;
            result += temperature + sep;
            for (int i=0; i<taille; i++){
                result += parametrisation[i].value + sep;
            }
            result += parent.id + sep;
            result += best.id + sep;
            for (int i=0; i<taille; i++){
                result += parametrisation[i].delta + sep;
            }
            result += saut;

            return result;
        }
    }

////////////////////
////////////////////
///              ///
///   METHODES   ///
///              ///
////////////////////
////////////////////

    /**
     *Retourne l'experience a l'index i dans la table historique, cree l'experience si elle n'existe pas encore.
     *@param i l'index de l'Experience demandee
     *@return l'Experience i
     */

    public Experience createExperience(int id, Experience parent){
		if (parent == null){
			log.error("Nouvelle experience " + id + " : parent = null");
		} else{
			log.error("Nouvelle experience " + id + " : parent = " + parent.id);
		}
        Experience result = new Experience(id, parent);
        historique.add(result);
        return result;
    }

    /**
     *Baisse la temperature selon une certaine methode. Appelle par @see modifierTemperature
     *@param compteurSimus la simulation qui vient de s'achever
     *@return la temperature baissee
     */
    //En faire une fonction de compteurSimus pour que toutes les methdoes marchaent!
    public double baisse(Experience expCurrent){
        double temperature = expCurrent.temperature;
        log.info("La temperature est de "+ temperature +". Il fait chaud par ici!");
        double result=0;
        //Methode pour faire baisser la temperature. Il peut y en avoir plusieurs, l'idee etant de pouvoir basculer d'une methode a l'autre pendant l'algorithme;

        //Van Laarhoven et al. (1987)
        if (param_coolingSchedule.equals("Van Laarhoven")){
            double obj = expCurrent.objective;
            double sigmasquare=(obj*obj-obj)*(obj*obj-obj);
            double sigma=Math.sqrt(sigmasquare);
            result = temperature*(1/(1+(Math.log(1+param_coolTemp))*temperature/(3*sigma)));
        }
        //Huang et al. (1986)
        if (param_coolingSchedule.equals("Huang")){
            double obj = expCurrent.objective;
            double sigmasquare=(obj*obj-obj)*(obj*obj-obj);
            double sigma=Math.sqrt(sigmasquare);
            result=temperature*Math.exp(-param_coolTemp*temperature/sigma);
        }
        // Triki et al. (2005)
        if (param_coolingSchedule.equals("Triki")){
            double obj = expCurrent.objective;
            double objParent=historique.get(expCurrent.parent.id).objective;
            double delta=obj-objParent;
            double sigmasquare=(obj*obj-obj)*(obj*obj-obj);
            result= temperature*(1-temperature*delta/sigmasquare);
            log.info("result Triki: "+result);
        }
        // Geometrique (decrit dans Triki et al. (2005))
        if (param_coolingSchedule.equals("Geometric")){
            result=param_coolTemp*temperature;
            log.info("result geometric : "+result);
        }
        // Lundy et al. (1986)
        if (param_coolingSchedule.equals("Lundy")){
            result= temperature*(1/(1+param_coolTemp*temperature));
        }
        // Fixe
        if (param_coolingSchedule.equals("Constant")){
            result=temperature; //Il faut fixer une bonne temperature, voir Cohn et Connolly pour plus de renseignements.
        }
        //Lineaire
        if (param_coolingSchedule.equals("Linear")){
            result=temperature-param_coolTemp;
        }
        return result;
    }

    /**
     *Initialise une parametrisation avant la premiere simulation a partir des bornes de chaque parametre selon une loi uniforme
     *@return la parametrisation initialisee.
     */
    public Parameter[] initM1(){
        //Methode 1 : initialisation selon une loi uniforme entre les bornes de chaque parametre.
        Parameter M1[] = new Parameter[taille];
        for (int i=0; i<taille; i++){
            double inf = Double.parseDouble(borneInf[i]);
            double sup = Double.parseDouble(borneSup[i]);
            double val= Double.parseDouble(valeursConseil[i]);
            double delta = Double.parseDouble(deltaValue[i]);
            log.info("dans initM1, delta =" + deltaValue[i]);
            Parameter param = new Parameter(val, inf, sup, delta);
            M1[i]=param;
        }

        //Methode 2 => On peut proposer plusieurs methodes d'initiation dans l'interface? Lois de Poisson, Gauss, etc.
        //etc.
        return M1;
    }

    /**
     * Modify nextSimulation database with parameters in Experience exp.
     * @param exp the Experience in process
     * @param nextSimulation storage for the next simulation
     * @throws Exception
     */
    protected void changeDB(Experience exp, SimulationStorage nextSimulation)
            throws Exception {
        // methode appelee dans before simualtion
        TopiaContext db = nextSimulation.getStorage().beginTransaction();//ouvrir un context pour modifier les donnees
        Population pop = (Population) db.findByTopiaId(param_Population.getTopiaId()); //reccupere la pop ciblee
        MatrixND c = pop.getCapturability(); // recupere la matrice de capturabilite
        log.info("Voici a quoi ressemble la matrice de capturabilite : "+c);
        // *** that is where you explain how to fill the catchability matrix with q1 and q2
        for (MatrixIterator i = c.iterator(); i.hasNext();) {
            i.next();
            Object[] sem = i.getSemanticsCoordinates();
			log.info("Semantics : "+sem);
            PopulationGroup group = (PopulationGroup) sem[0];
			log.info("Groupe : "+group.getId());
            // On travaille avec des ca&pturabilites par groupe.
            i.setValue(exp.parametrisation[group.getId()].value); 
        }//fin du for*/

        db.commitTransaction(); // effectue la modification
        db.closeContext(); // ferme le context
    }

    /**
     *Modifie une parametrisation, utilise une methode pour modifier parametre par parametre
     *@param M1 la parametrisation a modifier
     *@see modifParameter
     */
    public void modif(Parameter[] M1, double cristallisation){
        log.error("APPEL MODIF");
        for (int i =0; i<taille ; i++){
            log.error("APPEL BOUCLE");
            modifParameter(i, M1, cristallisation);	//Modification du parametre
        }
    }

    /**
     *Modifie un parametre selon une certaine methode, appellee par @see modif
     *@param num la position dans la parametrisation du parametre a modifier
     */
    public void modifParameter(int num, Parameter[] M1, double cristallisation){
        //Methode 1
        if (param_variationsDelta.equals("Fixe")){
            double amplitude = hasard3(num, M1, cristallisation);
            double valeur=M1[num].value;
            M1[num].value=amplitude+valeur;
            int MAX = 10000;
            while (MAX > 0 && (M1[num].value<M1[num].inf || M1[num].value>M1[num].sup)){	//Tant qu'on est en dehors du domaine de definiton du parametre, on modifie la valeur
                amplitude = hasard3(num, M1, cristallisation);
                M1[num].value=amplitude+valeur;
                MAX--;
            }
        }
        if (param_variationsDelta.equals("Aleatoire")){
            log.error("APPEL MODIFPARAMETER");
            double amplitude = hasardAlea(num, M1, cristallisation);
            double valeur=M1[num].value;
			log.error ("Val avant modif " + valeur);
            M1[num].value=amplitude+valeur;
            log.error("J'ai change la valeur a " + M1[num].value);
            int MAX = 10000;
            while (MAX > 0 && (M1[num].value<M1[num].inf || M1[num].value>M1[num].sup)){	//Tant qu'on est en dehors du domaine de definiton du parametre, on modifie la valeur
                amplitude = hasardAlea(num, M1, cristallisation);
                M1[num].value=amplitude+valeur;
                MAX--;
            }
        }
        /*
        if (MAX <= 0) {
        throws SimulationException("Can't find new value");
        }*/
        //Methode 2
        //etc. L'idee est de faire varier la maniee de faire varier delta en  fcontion de l'avancement de l'algorithme.
    }

    /**
     *Met en competition la nouvelle solution et la solution courante (calcul de la "probabilite" d'acceptation) et decide de qui sera la solution courante, le renseigne dans l'historique des simulations.
     *@param compteurSimus le numero de la simulation qui vient de s'achever.
     *@see getProba
     */
    public void acceptationSolution(Experience expCurrent){// Mise en competition de l'etat current et du nouvel etat

        deltaEnergy= expCurrent.parent.objective - expCurrent.objective;//Valeur absolue
        double rhoValue = getProba(expCurrent, deltaEnergy);
        expCurrent.rho = rhoValue;
        double randValue;
        if (rhoValue>=1){
            expCurrent.rand = 0;// rand n'a pas ete tire a cette iteration.
            expCurrent.accepted = true;
        }else{
            randValue=hasardUniforme();
            if (randValue<rhoValue){
                expCurrent.rand = randValue;
                expCurrent.accepted = true;
            }
            else{
                expCurrent.rand = randValue;
                expCurrent.accepted = false;
            }
        }
    }

    /**
     *Calcule la valeur de la fonction d'objectif de la parametrisation utilisee pour la simulation qui vient de s'achever.
     *@param result le numero de la simulation venant de s'achever
     *@return la valeur de fonction d'objectif
     */
    public double calculFonctionObjectif(ResultStorage result){//Inclut l'import des donnees, des resultats de la simulation et le calcul de la fonction d'objectif;
        /////***import the matrix of simulated data (here landings) from the simulation result
        MatrixND L = result.getMatrix(param_Population,
                ResultName.MATRIX_CATCH_WEIGHT_PER_STRATEGY_MET_PER_ZONE_POP);
	log.error("Matrice L : " + L);
        /////*** extract, sum, etc to obtain the same format/data as your observation matrix
        // useful methods to work on matrix : sumOverDim(), getSubMatrix(), reduce()
        //Somme sur les strategies, metiers, groupes et zones
        L= L.sumOverDim(1);
        L= L.sumOverDim(2);
        L= L.sumOverDim(3);
        L= L.sumOverDim(4);
        L= L.reduce(); //Indispensable pour pouvoir travailler sur la matrice et avoir les memes dimensions que les observations.
        ///////////////////Calcul du critere//////////////////
        log.info("calcul de la fonction objectif");
        log.info("dim de L" + " " + Arrays.toString(L.getDim()));
        log.info("dim de obs" + " "
                + Arrays.toString(matrixDebarquement.getDim()));
		
		double obj = 0;
        // *** using the matrixIterator assumes that the observation and simulated
        // matrix are organised in the exact same way ( columns and rows corresponding in each of them)
        for (MatrixIterator g = L.iterator(); g.hasNext();) {
            g.next();
            int[] dim = g.getCoordinates();
            double obs = matrixDebarquement.getValue(dim);
            double simules = g.getValue();
            obj += Math.pow(0.001*(obs - simules), 2);// On prend le millieme au carre pour eviter les valeurs trop grandes
        }
        return obj;
    }

    public void changeDelta (Experience expCurrent){
        Experience aModifier = expCurrent ; //Si a refuse, ce sera le parent qui sera a modifier (si on change sur un expCurrent refuse, TSPCS on n'avait pas appele cette methode)
        if (expCurrent.accepted == false){
            aModifier = expCurrent.parent;
        }
        double moyenne = 0;
        double newValue = 0;
        double minimum;
        double maximum;
        double pente;
        LinkedList<Double> valParam = new LinkedList<Double>();
        for (int i =0; i<taille ; i++){//Pour tous les parametres, on va verifier s'il faut modifier la valeur de delta
            for (int j =0; j<50; j++){
                valParam.add(historique.get(expCurrent.id-j).parametrisation[i].value); //To be checked!
                moyenne =+ valParam.get(j);
            }
            moyenne = moyenne/50;// Calcul de la moyenne
            minimum = Collections.min(valParam);
            maximum = Collections.max(valParam);
            if((Math.abs(minimum -moyenne)<3*aModifier.parametrisation[i].delta)&&(Math.abs(maximum -moyenne)<3*aModifier.parametrisation[i].delta)){ //Si le min et le max sont au moins de 2*delta de la moyenne => baisser delta
                newValue = aModifier.parametrisation[i].delta;
                aModifier.parametrisation[i].delta = newValue/2;
            }
            pente = historique.get(expCurrent.id-20).parametrisation[i].value - expCurrent.parametrisation[i].value;
            if ((pente<-10*aModifier.parametrisation[i].delta) && (pente>10*aModifier.parametrisation[i].delta)){	//J'ai pris 20000 au pif, a adapter aux soubresauts du recuit simule...
                newValue = aModifier.parametrisation[i].delta;
                aModifier.parametrisation[i].delta = newValue*2;
            }
        }
    }

    /**
     *Verifie si le critere d'arret de l'algorithme est atteint, les criteres pouvant changer selon les preferences de l'utilisateur.
     *@param compteurSimus le numero de la simulation qui vient de s'achever
     *@return true si le critere est atteint, false sinon.
     */
    public boolean isCritereArretAtteint(Experience expCurrent){// Critee a choisir? A rendre variable au cours de l'algorithme? (autant sur le critee d'une methode que sur la methode employee?)
        /*Methode 1
        double difference = Math.abs(expCurrent.objective - expCurrent.parent.objective);
        boolean bool = difference < seuilArret; *///seuil a determiner
        /*boolean bool=true;
        if (expCurrent.id>100){
        bool=false;// Cette inversion de signification de booleen n'est que rustine temporaire...
        }
        /*
        if (temperature<seuilArret) //seuil a determiner
        double temperature = getExperience(compteurSimus).temperature
        bool=true;

        if (compteurSimus>seuilArret) //seuil a determiner
        bool=true;

        //Methode 4 numero de phase...vraiment?
        */
        //etc.
        return bool;
    }

    /**
     *Calcule ce qu'on appelle (abusivement) la probabilite d'accepter la nouvelle parametrisation, appelee par @see acceptationSolution
     *@param deltaEnergy la difference de fonction d'objectif entre la nouvelle parametrisation et la parametrisation courante
     *@return la "probabilite"
     */
    public double getProba(Experience expCurrent, double deltaEnergy){
        double temp = expCurrent.temperature;
        double proba = Math.exp(deltaEnergy/temp);
        return proba;
    }
    /*
    /**
    *Verifie si le critere de modification de la temperature est atteint, les criteres pouvant varier selon les preferences de l'utilisateur.
    *@return true s'il faut modifier la temperature (@see modifierTemperature), false sinon.
    *:/
    public boolean isModifierTemperature(){//Renvoie un booleen qui indique s'il faut changer ou non la temperature.
    boolean bool=false;
    if (compteurTemperature>seuilTemperature || compteurAccept>seuilAcceptance){//seuils a fixer/determiner
    bool=true;
    }
    return bool;
    }
    */

    /**
     *Recupere le numero de la simulation renseignee
     *@param simulation storage de la simulationr enseignee
     *@return numero de la simulation renseignee
     */
    public int getIteration(SimulationStorage simulation){			//Il n'y a pas de setIteration
        return simulation.getParameter().getSimulationPlanNumber() + 1;
    }

}