From 6c0d56242e3ee5352d3b5d9afb7896a8316865a3 Mon Sep 17 00:00:00 2001 From: AlexisDrogoul Date: Sat, 28 Sep 2024 23:17:22 +0700 Subject: [PATCH] Add some safeguards to prevent infinite looping in AExplorationAlgorithm --- .../exploration/AExplorationAlgorithm.java | 325 +++++++++--------- 1 file changed, 168 insertions(+), 157 deletions(-) diff --git a/gama.core/src/gama/core/kernel/batch/exploration/AExplorationAlgorithm.java b/gama.core/src/gama/core/kernel/batch/exploration/AExplorationAlgorithm.java index 595c249838..aec60241d0 100644 --- a/gama.core/src/gama/core/kernel/batch/exploration/AExplorationAlgorithm.java +++ b/gama.core/src/gama/core/kernel/batch/exploration/AExplorationAlgorithm.java @@ -1,12 +1,11 @@ /******************************************************************************************************* * - * AExplorationAlgorithm.java, in gama.core, is part of the source code of the - * GAMA modeling and simulation platform . + * AExplorationAlgorithm.java, in gama.core, is part of the source code of the GAMA modeling and simulation platform . * * (c) 2007-2024 UMI 209 UMMISCO IRD/SU & Partners (IRIT, MIAT, TLU, CTU) * * Visit https://github.com/gama-platform/gama for license information and contacts. - * + * ********************************************************************************************************/ package gama.core.kernel.batch.exploration; @@ -70,39 +69,40 @@ @inside ( kinds = { ISymbolKind.EXPERIMENT }) public abstract class AExplorationAlgorithm extends Symbol implements IExploration { - + /** The current experiment. */ protected BatchAgent currentExperiment; - + /** The outputs expression. */ protected IExpression outputsExpression; - + /** The automatic output batch file */ protected IExpression outputFilePath; - + /** The sample size. */ protected int sample_size = 132; - + public static final String CSV_SEP = ","; - + @Override - public void initializeFor(IScope scope, BatchAgent agent) throws GamaRuntimeException { + public void initializeFor(final IScope scope, final BatchAgent agent) throws GamaRuntimeException { this.currentExperiment = agent; } - + /** * Instantiates a new a exploration algorithm. * - * @param desc the desc + * @param desc + * the desc */ - public AExplorationAlgorithm(final IDescription desc) { + public AExplorationAlgorithm(final IDescription desc) { super(desc); - if (hasFacet(IKeyword.BATCH_VAR_OUTPUTS)) {outputsExpression = getFacet(IKeyword.BATCH_VAR_OUTPUTS);} + if (hasFacet(IKeyword.BATCH_VAR_OUTPUTS)) { outputsExpression = getFacet(IKeyword.BATCH_VAR_OUTPUTS); } if (hasFacet(IKeyword.BATCH_OUTPUT)) { outputFilePath = getFacet(IKeyword.BATCH_OUTPUT); } } @Override - public void addParametersTo(List exp, BatchAgent agent) { + public void addParametersTo(final List exp, final BatchAgent agent) { exp.add(new ParameterAdapter("Exploration method", BatchAgent.EXPLORATION_EXPERIMENT, IType.STRING) { @Override public Object value() { @@ -112,13 +112,16 @@ public Object value() { } }); - if (getOutputs()!=null) { + if (getOutputs() != null) { exp.add(new ParameterAdapter("Outputs of interest", BatchAgent.EXPLORATION_EXPERIMENT, IType.STRING) { - @Override public Object value() { return getOutputs().literalValue(); } + @Override + public Object value() { + return getOutputs().literalValue(); + } }); } } - + @Override public void run(final IScope scope) { try { @@ -130,89 +133,101 @@ public void run(final IScope scope) { @Override public boolean isFitnessBased() { return false; } - + // MAIN ABSTRACTION - + /** * Main method that launch the exploration - * + * * @param scope */ public abstract void explore(IScope scope); - + /** - * Return the specific report for this exploration - * TODO : has been specified for calibration - to be removed or used consistently across experiment; see {@link ExperimentAgent} + * Return the specific report for this exploration TODO : has been specified for calibration - to be removed or used + * consistently across experiment; see {@link ExperimentAgent} */ - public String getReport() {return "";} - + public String getReport() { return ""; } + /** * Gives the list of variables the exploration method is targeting - * + * * @return {@link IExpression} */ - public IExpression getOutputs() {return outputsExpression;} - + @Override + public IExpression getOutputs() { return outputsExpression; } + /** - * Construct the experimental plan based on the given the proper modeler input: from sampling methods, from a file or explicit list of points - * + * Construct the experimental plan based on the given the proper modeler input: from sampling methods, from a file + * or explicit list of points + * * @param parameters * @param scope * @return List of {@link ParametersSet} */ - public List getExperimentPlan(List parameters, IScope scope) { - String method = hasFacet(Exploration.METHODS) ? - Cast.asString(scope, getFacet(Exploration.METHODS).value(scope)) - : (hasFacet(IKeyword.FROM) ? Exploration.FROM_FILE - : hasFacet(IKeyword.WITH) ? Exploration.FROM_LIST - : ""); - + public List getExperimentPlan(final List parameters, final IScope scope) { + String method = hasFacet(Exploration.METHODS) ? Cast.asString(scope, getFacet(Exploration.METHODS).value(scope)) + : hasFacet(IKeyword.FROM) ? Exploration.FROM_FILE : hasFacet(IKeyword.WITH) ? Exploration.FROM_LIST + : ""; + return switch (method) { - case IKeyword.MORRIS: yield MorrisSampling.makeMorrisSamplingOnly(hasFacet(MorrisExploration.NB_LEVELS) ? - Cast.asInt(scope, getFacet(MorrisExploration.NB_LEVELS)) : Morris.DEFAULT_LEVELS, sample_size, parameters, scope); - case IKeyword.LHS: yield LatinhypercubeSampling.latinHypercubeSamples(sample_size, parameters, scope.getRandom().getGenerator(), scope); - case IKeyword.ORTHOGONAL: yield OrthogonalSampling.orthogonalSamples(sample_size, - hasFacet(Exploration.ITERATIONS) ? Cast.asInt(scope, getFacet(Exploration.ITERATIONS).value(scope)) : OrthogonalSampling.DEFAULT_ITERATION, - parameters, scope.getRandom().getGenerator(), scope); - case IKeyword.SALTELLI: yield SaltelliSampling.makeSaltelliSampling(scope, sample_size, parameters); - case IKeyword.UNIFORM: yield RandomSampling.uniformSampling(scope, sample_size, parameters); - case IKeyword.FACTORIAL: yield hasFacet(Exploration.SAMPLE_FACTORIAL) ? - RandomSampling.factorialUniformSampling(scope, getFactorial(scope, parameters), parameters) - : RandomSampling.factorialUniformSampling(scope, sample_size, parameters); - case Exploration.FROM_LIST: yield buildParameterFromMap(scope); - case Exploration.FROM_FILE: yield buildParametersFromCSV(scope, Cast.asString(scope, getFacet(IKeyword.FROM).value(scope))); - default: yield buildParameterSets(scope, new ArrayList<>(), 0); + case IKeyword.MORRIS: + yield MorrisSampling.makeMorrisSamplingOnly(hasFacet(MorrisExploration.NB_LEVELS) + ? Cast.asInt(scope, getFacet(MorrisExploration.NB_LEVELS)) : Morris.DEFAULT_LEVELS, sample_size, + parameters, scope); + case IKeyword.LHS: + yield LatinhypercubeSampling.latinHypercubeSamples(sample_size, parameters, + scope.getRandom().getGenerator(), scope); + case IKeyword.ORTHOGONAL: + yield OrthogonalSampling.orthogonalSamples(sample_size, + hasFacet(Exploration.ITERATIONS) + ? Cast.asInt(scope, getFacet(Exploration.ITERATIONS).value(scope)) + : OrthogonalSampling.DEFAULT_ITERATION, + parameters, scope.getRandom().getGenerator(), scope); + case IKeyword.SALTELLI: + yield SaltelliSampling.makeSaltelliSampling(scope, sample_size, parameters); + case IKeyword.UNIFORM: + yield RandomSampling.uniformSampling(scope, sample_size, parameters); + case IKeyword.FACTORIAL: + yield hasFacet(Exploration.SAMPLE_FACTORIAL) + ? RandomSampling.factorialUniformSampling(scope, getFactorial(scope, parameters), parameters) + : RandomSampling.factorialUniformSampling(scope, sample_size, parameters); + case Exploration.FROM_LIST: + yield buildParameterFromMap(scope); + case Exploration.FROM_FILE: + yield buildParametersFromCSV(scope, Cast.asString(scope, getFacet(IKeyword.FROM).value(scope))); + default: + yield buildParameterSets(scope, new ArrayList<>(), 0); }; - + } - + /** * Gives the factorial plan based on SAMPLE_FACTORIAL facets of experiment + * * @return */ - @SuppressWarnings("unchecked") - public int[] getFactorial(IScope scope, List parameters) { - + @SuppressWarnings ("unchecked") + public int[] getFactorial(final IScope scope, final List parameters) { + IList fact = Cast.asList(scope, getFacet(Exploration.SAMPLE_FACTORIAL).value(scope)); if (fact.size() < parameters.size()) { - fact.addAll(Collections.nCopies(parameters.size()-fact.size(), Exploration.DEFAULT_FACTORIAL)); - } else if (fact.size() > parameters.size()) { - fact = Cast.asList(scope, fact.subList(0,parameters.size())); - } - + fact.addAll(Collections.nCopies(parameters.size() - fact.size(), Exploration.DEFAULT_FACTORIAL)); + } else if (fact.size() > parameters.size()) { fact = Cast.asList(scope, fact.subList(0, parameters.size())); } + return IntStreamEx.of(fact).toArray(); } - + /** * Main method to build the set of points to visit during the exploration of a model - * + * * @param scope * @param sets * @param index * @return */ @SuppressWarnings ("rawtypes") - public List buildParameterSets(IScope scope, List sets, int index) { + public List buildParameterSets(final IScope scope, final List sets, final int index) { if (sets == null) throw GamaRuntimeException.error("Cannot build a sample with empty parameter set", scope); final List variables = currentExperiment.getParametersToExplore(); List sets2 = new ArrayList<>(); @@ -230,46 +245,46 @@ public List buildParameterSets(IScope scope, List if (index == variables.size() - 1) return sets2; return buildParameterSets(scope, sets2, index + 1); } - + /** - * - * Save the raw results of simulations with targeted outputs specified in the facet {@value gama.core.common.interfaces.IKeyword#BATCH_VAR_OUTPUTS} and - * file destination in the facet {@value gama.core.common.interfaces.IKeyword#BATCH_OUTPUT} - * + * + * Save the raw results of simulations with targeted outputs specified in the facet + * {@value gama.core.common.interfaces.IKeyword#BATCH_VAR_OUTPUTS} and file destination in the facet + * {@value gama.core.common.interfaces.IKeyword#BATCH_OUTPUT} + * * WARNING : file are erased if same path is passed - * + * * @param scope * @param results */ - public void saveRawResults(final IScope scope, IMap>> results) { + public void saveRawResults(final IScope scope, final IMap>> results) { String path_to = Cast.asString(scope, outputFilePath.value(scope)); final File fo = new File(FileUtils.constructAbsoluteFilePath(scope, path_to, false)); final File parento = fo.getParentFile(); - if (!parento.exists()) { + if (!parento.exists()) { try { - if(!parento.mkdirs()) { - throw new Exception("Unknown reason"); - } + if (!parento.mkdirs()) throw new Exception("Unknown reason"); } catch (Exception e) { - throw GamaRuntimeException.error("Cannot create a folder at " + parento.toString() + " because: "+e.getMessage(), scope); + throw GamaRuntimeException.error( + "Cannot create a folder at " + parento.toString() + " because: " + e.getMessage(), scope); } } if (fo.exists()) { - try { - if (!fo.delete()) { - throw new Exception("Unknown reason"); - } + try { + if (!fo.delete()) throw new Exception("Unknown reason"); } catch (Exception e) { - throw GamaRuntimeException.error("File " + fo.toString() + " cannot be deleted because: "+e.getMessage(), scope); - } + throw GamaRuntimeException + .error("File " + fo.toString() + " cannot be deleted because: " + e.getMessage(), scope); + } } try (FileWriter fw = new FileWriter(fo, StandardCharsets.UTF_8, false)) { fw.write(buildSimulationCsv(results, scope)); } catch (Exception e) { - throw GamaRuntimeException.error("File " + fo.toString() + " cannot be found to save "+currentExperiment.getName()+" experiment results", scope); + throw GamaRuntimeException.error("File " + fo.toString() + " cannot be found to save " + + currentExperiment.getName() + " experiment results", scope); } } - + // ############################################################ // Private ways to read file or manual input experimental plans @@ -290,12 +305,14 @@ private List buildParameterFromMap(final IScope scope) { return buildParametersSetList(scope, parameterSets); } - private List buildParametersSetList(IScope scope, List> parameterSets) { + private List buildParametersSetList(final IScope scope, + final List> parameterSets) { var sets = new ArrayList(); for (Map parameterSet : parameterSets) { ParametersSet p = new ParametersSet(); for (Entry entry : parameterSet.entrySet()) { - p.put(entry.getKey(), entry.getValue() instanceof IExpression ? ((IExpression) entry.getValue()).value(scope) : entry.getValue()); + p.put(entry.getKey(), entry.getValue() instanceof IExpression + ? ((IExpression) entry.getValue()).value(scope) : entry.getValue()); } sets.add(p); } @@ -309,10 +326,12 @@ private List buildParametersSetList(IScope scope, List buildParametersFromCSV(final IScope scope, final String path) throws GamaRuntimeException { + private List buildParametersFromCSV(final IScope scope, final String path) + throws GamaRuntimeException { List> parameters = new ArrayList<>(); - try (FileReader fr = new FileReader(new File(path), StandardCharsets.UTF_8); BufferedReader br = new BufferedReader(fr)) { + try (FileReader fr = new FileReader(new File(path), StandardCharsets.UTF_8); + BufferedReader br = new BufferedReader(fr)) { String line = " "; String[] tempArr; List list_name = new ArrayList<>(); @@ -335,32 +354,36 @@ private List buildParametersFromCSV(final IScope scope, final Str return buildParametersSetList(scope, parameters); } - + /** * Rebuild simulations ouptuts to be written in a file - * + * * @param Outputs * @param scope * @return */ - private String buildSimulationCsv(final IMap>> results, IScope scope) { + private String buildSimulationCsv(final IMap>> results, + final IScope scope) { StringBuilder sb = new StringBuilder(); - - @SuppressWarnings("unchecked") - List outputs = Cast.asList(scope, getFacet(IKeyword.BATCH_VAR_OUTPUTS).value(scope)); + + @SuppressWarnings ("unchecked") List outputs = + Cast.asList(scope, getFacet(IKeyword.BATCH_VAR_OUTPUTS).value(scope)); List inputs = results.getKeys().anyValue(scope).getKeys(); // Write the header sb.append(String.join(CSV_SEP, inputs)); sb.append(CSV_SEP); sb.append(String.join(CSV_SEP, outputs)); - + // Find results and append to global string for (var entry : results.entrySet()) { Map> res = entry.getValue(); var ps = entry.getKey(); int nbr = res.values().stream().findAny().get().size(); - if (!res.values().stream().allMatch(r -> r.size()==nbr)) { - GAMA.reportAndThrowIfNeeded(scope, GamaRuntimeException.warning("Not all sample of stochastic analysis have the same number of replicates", scope), false); + if (!res.values().stream().allMatch(r -> r.size() == nbr)) { + GAMA.reportAndThrowIfNeeded(scope, + GamaRuntimeException.warning( + "Not all sample of stochastic analysis have the same number of replicates", scope), + false); continue; } @@ -368,15 +391,13 @@ private String buildSimulationCsv(final IMap ps.get(i).toString()).collect(Collectors.joining(CSV_SEP))); - for (var entrySet : res.entrySet()) { - sb.append(CSV_SEP).append(entrySet.getValue().get(r)); - } - } + for (var entrySet : res.entrySet()) { sb.append(CSV_SEP).append(entrySet.getValue().get(r)); } + } } return sb.toString(); } - + // ##################### Methods to determine possible values based on exhaustive ###################### /** @@ -387,21 +408,16 @@ private String buildSimulationCsv(final IMap getParameterSwip(final IScope scope, final Batch var) { - switch (var.getType().id()) { - case IType.INT: - return getIntParameterSwip(scope, var); - case IType.FLOAT: - return getFloatParameterSwip(scope, var); - case IType.DATE: - return getDateParameterSwip(scope, var); - case IType.POINT: - return getPointParameterSwip(scope, var); - default: - return getDefaultParameterSwip(scope, var); - } + return switch (var.getType().id()) { + case IType.INT -> getIntParameterSwip(scope, var); + case IType.FLOAT -> getFloatParameterSwip(scope, var); + case IType.DATE -> getDateParameterSwip(scope, var); + case IType.POINT -> getPointParameterSwip(scope, var); + default -> getDefaultParameterSwip(scope, var); + }; } - private List getDateParameterSwip(IScope scope, Batch var) { + private List getDateParameterSwip(final IScope scope, final Batch var) { List res = new ArrayList<>(); GamaDate dateValue = GamaDateType.staticCast(scope, var.getMinValue(scope), null, false); GamaDate maxDateValue = GamaDateType.staticCast(scope, var.getMaxValue(scope), null, false); @@ -417,15 +433,15 @@ private List getDateParameterSwip(IScope scope, Batch var) { } return res; } - private List getPointParameterSwip(IScope scope, Batch var) { + + private List getPointParameterSwip(final IScope scope, final Batch var) { List res = new ArrayList<>(); GamaPoint pointValue = Cast.asPoint(scope, var.getMinValue(scope)); GamaPoint maxPointValue = Cast.asPoint(scope, var.getMaxValue(scope)); Double stepV = null; - GamaPoint increment = new GamaPoint( (maxPointValue.x - pointValue.x) / 10.0, - (maxPointValue.y - pointValue.y) / 10.0, - (maxPointValue.z - pointValue.z) / 10.0); + GamaPoint increment = new GamaPoint((maxPointValue.x - pointValue.x) / 10.0, + (maxPointValue.y - pointValue.y) / 10.0, (maxPointValue.z - pointValue.z) / 10.0); if (var.getStepValue(scope) != null) { increment = GamaPointType.staticCast(scope, var.getStepValue(scope), true); @@ -438,7 +454,7 @@ private List getPointParameterSwip(IScope scope, Batch var) { } } - + while (pointValue.smallerThanOrEqualTo(maxPointValue)) { if (stepV == null || stepV > 0) { res.add(pointValue); @@ -450,26 +466,29 @@ private List getPointParameterSwip(IScope scope, Batch var) { } return res; } - - private List getFloatParameterSwip(IScope scope, Batch var) { + + private List getFloatParameterSwip(final IScope scope, final Batch var) { List res = new ArrayList<>(); double minFloatValue = Cast.asFloat(scope, var.getMinValue(scope)); double maxFloatValue = Cast.asFloat(scope, var.getMaxValue(scope)); double stepFloatValue = 0.1; - double df = Exploration.DEFAULT_FACTORIAL-1; - - + double df = Exploration.DEFAULT_FACTORIAL - 1; + if (hasFacet(Exploration.SAMPLE_FACTORIAL)) { List b = currentExperiment.getParametersToExplore(); - df = getFactorial(scope,b)[b.indexOf(var)]; + df = getFactorial(scope, b)[b.indexOf(var)]; stepFloatValue = (maxFloatValue - minFloatValue) / df; } else if (var.getStepValue(scope) != null) { - stepFloatValue = Cast.asFloat(scope, var.getStepValue(scope))-1; + stepFloatValue = Cast.asFloat(scope, var.getStepValue(scope)); // - 1; AD: Why -1 ?? } else { stepFloatValue = (maxFloatValue - minFloatValue) / df; } - + // AD: Addition of tests to avoid infinite loops (cf # + if (stepFloatValue < 0) { + stepFloatValue *= -1; + } else if (stepFloatValue == 0) { stepFloatValue = 0.1; } + while (minFloatValue <= maxFloatValue) { minFloatValue += stepFloatValue; res.add(minFloatValue); @@ -478,65 +497,57 @@ private List getFloatParameterSwip(IScope scope, Batch var) { return res; } - private List getIntParameterSwip(IScope scope, Batch var) { + private List getIntParameterSwip(final IScope scope, final Batch var) { List res = new ArrayList<>(); int minValue = Cast.asInt(scope, var.getMinValue(scope)); int maxValue = Cast.asInt(scope, var.getMaxValue(scope)); double stepValue = 1; - double df = Exploration.DEFAULT_FACTORIAL-1; - + double df = Exploration.DEFAULT_FACTORIAL - 1; + if (hasFacet(Exploration.SAMPLE_FACTORIAL)) { List b = currentExperiment.getParametersToExplore(); - df = getFactorial(scope,b)[b.indexOf(var)]; - if (maxValue - minValue > df) { - stepValue = (maxValue - minValue) / df; - } + df = getFactorial(scope, b)[b.indexOf(var)]; + if (maxValue - minValue > df) { stepValue = (maxValue - minValue) / df; } } else if (var.getStepValue(scope) != null) { stepValue = Cast.asInt(scope, var.getStepValue(scope)); - } else if (maxValue - minValue > df) { - stepValue = (maxValue - minValue) / df; - } - + } else if (maxValue - minValue > df) { stepValue = (maxValue - minValue) / df; } + int nbIterNeeded = 0; - //This means if we have min=0 max=4 and step=3, we will get [0, 3] in res - nbIterNeeded = Math.abs((int)((maxValue - minValue) / stepValue)); + // This means if we have min=0 max=4 and step=3, we will get [0, 3] in res + nbIterNeeded = Math.abs((int) ((maxValue - minValue) / stepValue)); double start = stepValue >= 0 ? minValue : maxValue; - for(int i = 0 ; i <= nbIterNeeded ; i++) { - res.add(start + (int)(stepValue * i)); - } + for (int i = 0; i <= nbIterNeeded; i++) { res.add(start + (int) (stepValue * i)); } return res; } - - private List getDefaultParameterSwip(IScope scope, Batch var) { + + private List getDefaultParameterSwip(final IScope scope, final Batch var) { List res = new ArrayList<>(); double varValue = Cast.asFloat(scope, var.getMinValue(scope)); double maxVarValue = Cast.asFloat(scope, var.getMaxValue(scope)); double floatcrement = 1; - double dfactor = (Exploration.DEFAULT_FACTORIAL-1); - + double dfactor = Exploration.DEFAULT_FACTORIAL - 1; + if (hasFacet(Exploration.SAMPLE_FACTORIAL)) { List b = currentExperiment.getParametersToExplore(); - dfactor = getFactorial(scope,b)[b.indexOf(var)]; + dfactor = getFactorial(scope, b)[b.indexOf(var)]; floatcrement = (maxVarValue - varValue) / dfactor; } else if (var.getStepValue(scope) != null) { floatcrement = Cast.asFloat(scope, var.getStepValue(scope)); } else { floatcrement = (maxVarValue - varValue) / dfactor; } - + double v = floatcrement >= 0 ? varValue : maxVarValue; - - while (varValue <= maxVarValue) { + + while (v <= maxVarValue) { if (var.getType().id() == IType.INT) { res.add((int) v); - } else if (var.getType().id() == IType.FLOAT) { - res.add(v); - } + } else if (var.getType().id() == IType.FLOAT) { res.add(v); } v += floatcrement; } return res; } - + }