package simulationplans; import static org.nuiton.i18n.I18n._; import java.io.File; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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.simulator.SimulationException; import fr.ifremer.isisfish.simulator.SimulationPlanContext; import fr.ifremer.isisfish.simulator.SimulationPlan; import fr.ifremer.isisfish.util.Doc; import scripts.ResultName; /** * RecuitSimule. * * 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 : "RecuitSimule" class RecuitSimule implements SimulationPlan{ /** to use log facility, just put in your code: log.info("..."); */ static private Log log = LogFactory .getLog(RecuitSimule.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 protected File exportHistoric = new File("Historic.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 = "2.42e-5;2.11e-6";// devient un parametre du plan d analyse/// Rentrer ici les bornes inférieruers de chaque paramètre. @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 = "2.42e-5;2.11e-6";// devient un parametre du plan d analyse/// Rentrer ici les bornes supérieruers de chaque paramètre. @Doc("Cooling schedule : choose between \"Van Laarhoven\", \"Huang\", \"Triki\", \"Geometric\", \"Lundy\", \"Constant\" and \"Linear\".") public String param_coolingSchedule = "Triki"; // Attention aux méthodes à valeurs à fixer par l'utilisateur. //public long param_seed = 1; String [] borneInf; String [] borneSup; 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 méthodes de hasard à  déterminer, à  placer en début de script. Pour le moment, elles ne font que de l'uniforme, à  nous de voir ce qui est le mieux et pourquoi public int hasard1(){ int result = random.nextInt(taille); return result; } public int hasard2(){ int result = random.nextInt(); return result; } public double hasard3(){ double result = random.nextDouble(); return result; } public double hasardUniforme(){ double result = random.nextDouble(); return result; } //int seuilTemperature=5;// Laissés au choix de l'utilisateur. Dans l'interface, proposer des méthodes pour faire évoluer les seuils? //int seuilAcceptance=3; double seuilArret=0.01; //Sera utilisé en fonction du critèe d'arrêt utilisé...son cas est encore à  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 = "Observedlandings2001-2003.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. //*** double temperature=???; //Initialisation de la température. Pour le moment, à  l'utilisateur de remplacer ??? par un double dans le script. //*** 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 // method: MatrixFactory.getInstance().create() // ***Then import your file in it // method : matrixDebarquement.importCSV(); log.info("MatrixDebarquement : " + matrixDebarquement); borneInf=param_borneInf.split(";"); borneSup=param_borneSup.split(";"); taille=borneInf.length; } /** *Crée (*@see createExperience) une nouvelle expérience (le contructeur modifie la paramétrisation parente ou en initialise une si c'est la première), baisse la température si nécessaire(*@see baisse), modifie la base de données(*@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 paramétrisation actuelle, on enregistre cette modification, on change la base de données. compteurSimus=getIteration(nextSimulation); //numéro de la simulation à  venir Experience expCurrent; if (compteurSimus==0){//En considérant que la numérotation des simulations commence à  0, on traite ici la premièe simulation. expCurrent = createExperience(compteurSimus, null); expCurrent.accepted = true; } else { Experience expPrev = historique.get(compteurSimus - 1); expCurrent = createExperience(compteurSimus, expPrev); expCurrent.temperature = baisse(expCurrent); } changeDB(expCurrent, nextSimulation); return true; } /** *Récupère les résultats de la simulation, calcule la fonction d'objectif(*@see calculFonctionObjectif), accepte ou non la nouvelle paramétrisation et décide la paramétrisation parente(*@see acceptationSolution) et vérifie le critère d'arrêt(*@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 évalue la nouvelle paramétrisation, on met en compétition cette paramétrisation //avec celle de référence (celle de l'itération current), on modifie la température, on vérifie que le //critèe d'arrêt de l'algorithme n'est pas atteint, on enregistre toutes les modifications dans la table historique. ResultStorage result = lastSimulation.getResultStorage(); boolean bool=true; compteurSimus=getIteration(lastSimulation); //numéro 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 paramétrisation à la meilleure paramétrisation expCurrent.best = expCurrent; } acceptationSolution(expCurrent);//On compare la nouvelle paramétrisation à la paramétrisation courante et on décide qui sera la courante. //if (isModifierTemperature()){//Pour le moment, les méthodes proposées modifient la température à chaque fois. On intégrera un système de phase et de vérification de critère de température plus tard, si besoin. /*} else{ historique.get(compteurSimus).temperature = getExperience(compteurSimus-1).temperature; }*/ bool=isCritereArretAtteint(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; } public Parameter copy() { return new Parameter(value, inf, sup); } } class Experience{// 1 objet Experience contient toutes les informations sur la passage de l'état current à  l'état suivant. public int id; public Experience parent; public Experience best; public boolean accepted; public double rho; public double rand; public Parameter[] parametrisation; //On enregistre la paramétrisation et les bornes, tout est nécessaire en cas de ré-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(); } else { best = parent.best; Parameter[] M1=copyM1(parent.getParametrisation()); modif(M1); 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 = ""; result += id + sep; result += accepted + sep; result += rand + sep; result += rho + sep; result += objective + sep; result += temperature + sep; result += parametrisation.value + sep; result += parent.id + sep; result += best.id + sep; return result; }*/ } //////////////////// //////////////////// /// /// /// METHODES /// /// /// //////////////////// //////////////////// /** *Retourne l'experience à l'index i dans la table historique, crée l'experience si elle n'existe pas encore. *@param i l'index de l'Experience demandée *@return l'Experience i */ public Experience createExperience(int id, Experience parent){ Experience result = new Experience(id, parent); historique.add(result); return result; } /** *Baisse la température selon une certaine méthode. Appellé par @see modifierTemperature *@param compteurSimus la simulation qui vient de s'achever *@return la température baissée */ //En faire une fonction de compteurSimus pour que toutes les méthdoes marchaent! public double baisse(Experience expCurrent){ double temperature = expCurrent.temperature; double result=0; //Méthode pour faire baisser la température. Il peut y en avoir plusieurs, l'idée étant de pouvoir basculer d'une méthode à  l'autre pendant l'algorithme; //Van Laarhoven et al. (1987) if (param_coolingSchedule.equals("Van Laarhoven")){ double delta=0.001;// Valeur à fixer par l'utilisateur double obj = expCurrent.objective; double sigmasquare=(obj*obj-obj)*(obj*obj-obj); double sigma=Math.sqrt(sigmasquare); result = temperature*(1/(1+(Math.log(1+delta))*temperature/(3*sigma))); } //Huang et al. (1986) if (param_coolingSchedule.equals("Huang")){ double lambda =0.5;// 0 On peut proposer plusieurs méthodes 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(); // reccupere la matrice de capturabilité // *** 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(); PopulationGroup group = (PopulationGroup) sem[0]; // *** exemple when q2 corresponds to the 12 first groups (groups 0 to 11) if (group.getId() < 12) { i.setValue(exp.parametrisation[1].value); } else { i.setValue(exp.parametrisation[0].value); } // *** exemple when it depends on seasons and groups /*PopulationSeasonInfo season = (PopulationSeasonInfo) sem[1]; if (season.getFirstMonth().after(Month.JULY) && group.getId() >=18){ //month >= aout && groupID >= 18 i.setValue(exp.q2); } else { i.setValue(exp.q1); } */ }//fin du for db.commitTransaction(); // effectue la modification db.closeContext(); // ferme le context } /** *Modifie une paramétrisation, utilise une méthode pour modifier paramètre par paramètre *@param M1 la paramétrisation à modifier *@see modifParameter */ public void modif(Parameter[] M1){ double nbModif=hasard1(); //Une méthode de tirage aléatoire à  coder dans une autre méthode (type getPoisson), on la mettre au début du script, while (nbModif >= 0){ //où plusieurs possibilités de tirages sont proposées. L'utilisateur peut y rajouter les siennes. int num=M1.length*hasard2(); //On tire au hasard entre 0 et M1.length-1 une position sur M1. modifParameter(num, M1); //Modification du paramètre nbModif-=1; } } /** *Modifie un paramètre selon une certaine méthode, appellée par @see modif *@param num la position dans la paramétrisation du paramètre à modifier */ public void modifParameter(int num, Parameter[] M1){ //Méthode 1 double amplitude = hasard3(); double valeur=M1[num].value; M1[num].value=amplitude*valeur; int MAX = 10000; while (MAX > 0 && (M1[num].valueM1[num].sup)){ //Tant qu'on est en dehors du domaine de définiton du paramètre, on modifie la valeur amplitude = hasard3(); M1[num].value=amplitude*valeur; MAX--; } /* if (MAX <= 0) { throws SimulationException("Can't find new value"); }*/ //Méthode 2 //etc. L'idée est de faire varier la manièe de faire varier delta en fcontion de l'avancement de l'algorithme. } /** *Met en compétition la nouvelle solution et la solution courante (calcul de la "probabilité" d'acceptation) et décide de qui sera la solution courante, le renseigne dans l'historique des simulations. *@param compteurSimus le numéro de la simulation qui vient de s'achever. *@see getProba */ public void acceptationSolution(Experience expCurrent){// Mise en compétition de l'état current et du nouvel état deltaEnergy= expCurrent.objective - expCurrent.parent.objective;//Valeur absolue double rhoValue = getProba(expCurrent, deltaEnergy); expCurrent.rho = rhoValue; double randValue; if (rhoValue>1){ expCurrent.rand = 0;// rand n'a pas été tiré à  cette itération. expCurrent.accepted = true; }else{ randValue=hasardUniforme(); if (randValueseuilArret) //seuil à  déterminer bool=true; //Méthode 4 numéro de phase...vraiment? */ //etc. return bool; } /** *Calcule ce qu'on appelle (abusivement) la probabilité d'accepter la nouvelle paramétrisation, appelée par @see acceptationSolution *@param deltaEnergy la différence de fonction d'objectif entre la nouvelle paramétrisation et la paramétrisation courante *@return la "probabilité" */ public double getProba(Experience expCurrent, double deltaEnergy){ double temp = expCurrent.temperature; double proba = Math.exp(deltaEnergy/temp); return proba; } /* /** *Vérifie si le critère de modification de la température est atteint, les critères pouvant varier selon les préférences de l'utilisateur. *@return true s'il faut modifier la température (@see modifierTemperature), false sinon. *:/ public boolean isModifierTemperature(){//Renvoie un booléen qui indique s'il faut changer ou non la température. boolean bool=false; if (compteurTemperature>seuilTemperature || compteurAccept>seuilAcceptance){//seuils à  fixer/déterminer bool=true; } return bool; } */ /** *Récupère le numéro de la simulation renseignée *@param simulation storage de la simulationr enseignée *@return numéro de la simulation renseignée */ public int getIteration(SimulationStorage simulation){ //Il n'y a pas de setIteration return simulation.getParameter().getSimulationPlanNumber(); } }