/* * Copyright (C) 2014 avigier * * 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 * . */ package simulationplans; import static org.nuiton.i18n.I18n._; import java.io.File; import java.io.FileReader; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.*; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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 org.nuiton.util.StringUtil; import fr.ifremer.isisfish.datastore.ResultStorage; import fr.ifremer.isisfish.datastore.SimulationStorage; import fr.ifremer.isisfish.entities.Population; import fr.ifremer.isisfish.entities.PopulationGroup; import fr.ifremer.isisfish.entities.PopulationSeasonInfo; import fr.ifremer.isisfish.simulator.SimulationException; import fr.ifremer.isisfish.simulator.SimulationPlanContext; import fr.ifremer.isisfish.simulator.SimulationPlan; import fr.ifremer.isisfish.types.Month; import fr.ifremer.isisfish.util.Doc; import scripts.ResultName; /** * RecuitSimuleDeltaAdaptatifAdaptatif. * * Created: * * @author * @version * * Last update: * by : */ // //////////////////////////////////////////////////////////////////////// // 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 : "RecuitSimuleDeltaAdaptatifAdaptatif" public class RecuitSimuleDeltaAdaptatifAdaptatif implements SimulationPlan{ /** to use log facility, just put in your code: log.info("..."); */ static private Log log = LogFactory .getLog(RecuitSimuleDeltaAdaptatifAdaptatif.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/HistoricRecuitSimule3paramValeursConseil.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";// 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";// devient un parametre du plan d analyse/// Rentrer ici les bornes superieruers de chaque parametre. @Doc("Temperature (double)") public String param_temperature= "100"; @Doc("Cooling schedule : choose between \"Van Laarhoven\", \"Huang\", \"Triki\", \"Geometric\", \"Lundy\", \"Constant\" and \"Linear\".") public String param_coolingSchedule = "Linear"; // Attention aux methodes a  valeurs a  fixer par l'utilisateur. //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.00001;0.00001;0.00001";// 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"; // Prend les valeurs "Fixe" ou "Aleatoire" @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.00008;0.0002;0.00032" ; String [] borneInf; String [] borneSup; String [] valeursConseil; String [] deltaValue; int taille; int current; int compteurSimus; int compteurTemperature; int compteurAccept; double deltaEnergy; SecureRandom random; double rhoValue; boolean bool; double obj; /////***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.info("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.info("result hasardAlea " + result); return result; } //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'arrêt utilise...son cas est encore a  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/Inputlandings.csv"; protected File debarquementsObserves; protected MatrixND matrixDebarquement; ArrayList historique = new ArrayList(); //historique va contenir tout l'historique de l'algorithme, c'est un ArrayList d'Experience. //*** write the name of the simulated matrix that contains the data corresponding // to your observations (here MATRIX_CATCH_WEIGHT_PER_STRATEGY_MET_PER_ZONE_POP) public String[] necessaryResult = { ResultName.MATRIX_CATCH_WEIGHT_PER_STRATEGY_MET_PER_ZONE_POP }; 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 */ 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 au demarrage de la simulation, cette methode permet d'initialiser * des valeurs * @param context La simulation pour lequel on utilise cette regle */ public void init(SimulationPlanContext 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 = {12,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 matrixDebarquement.importCSV(new FileReader(debarquementsObserves),new int []{0,0}); matrixDebarquement= matrixDebarquement.reduce(); //Indispensable pour pouvoir travailler sur la matrice et avoir les mêmes 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; } /** *Cree (*@see createExperience) une nouvelle experience (le contructeur modifie la parametrisation parente ou en initialise une si c'est la premiere), baisse la temperature si necessaire(*@see baisse), modifie la base de donnees(*@see changeDB). *@param context plan context *@param nextSimulation storage for the next simulation *@return true if we must do next simulation, false to stop plan *@throws Exception */ public boolean beforeSimulation(SimulationPlanContext context, SimulationStorage nextSimulation) throws Exception {//Dans le before, on modifie la parametrisation actuelle, on enregistre cette modification, on change la base de donnees. compteurSimus=getIteration(nextSimulation); //numero de la simulation a  venir Experience expCurrent; if (compteurSimus==0){//En considerant que la numerotation des simulations commence a  0, on traite ici la premiee simulation. expCurrent = createExperience(compteurSimus, null); expCurrent.accepted = true; } else { Experience expParent; if (historique.get(compteurSimus-1).accepted){ expParent = historique.get(compteurSimus - 1); } else{ expParent = historique.get(compteurSimus - 1).parent; } log.info("parent.id : "+expParent.id); expCurrent = createExperience(compteurSimus, expParent); log.info("Temperature avant baisse : "+expCurrent.temperature); expCurrent.temperature = baisse(expCurrent); log.info("J'ai baisse la temperature a  :"+expCurrent.temperature); } changeDB(expCurrent, nextSimulation); return true; } /** *Recupere les resultats de la simulation, calcule la fonction d'objectif(*@see calculFonctionObjectif), accepte ou non la nouvelle parametrisation et decide la parametrisation parente(*@see acceptationSolution) et verifie le critere d'arret(*@see isCritereArretAtteint). *@param context plan context *@param lastSimulation storage for the next simulation *@return true if we must do next simulation, false to stop plan *@throws Exception *@see getIteration */ public boolean afterSimulation(SimulationPlanContext context, SimulationStorage lastSimulation) 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. File exportHistoric = new File(param_exportPath); ResultStorage result = lastSimulation.getResultStorage(); boolean bool=true; compteurSimus=getIteration(lastSimulation); //numero de la simulation qui vient de se terminer Experience expCurrent = historique.get(compteurSimus); 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; } 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'arrêt 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){ changeDelta(expCurrent); } return bool; } //////////////////// //////////////////// /// /// ///AUTRES CLASSES/// /// /// //////////////////// //////////////////// protected Parameter[] copyM1(Parameter[]M1){ Parameter[] result = new Parameter[M1.length]; for (int i=0,maxi=M1.length; i= 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) { best = this; parametrisation = initM1(); temperature = Double.parseDouble(param_temperature); 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.info("APPEL MODIF DANS LE CONSTRUCTEUR"); modif(M1, cristallisation); parametrisation = M1; } } /** * 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 0 && (M1[num].valueM1[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.info("APPEL MODIFPARAMETER"); double amplitude = hasardAlea(num, M1, cristallisation); double valeur=M1[num].value; M1[num].value=amplitude+valeur; log.info("J'ai change la valeur"); int MAX = 10000; while (MAX > 0 && (M1[num].valueM1[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 valParam = new LinkedList(); for (int i =0; i 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, à 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 (temperatureseuilArret) //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(); } }