diff --git a/.gitlab/issue_templates/publier_nouvelle_version.md b/.gitlab/issue_templates/publier_nouvelle_version.md index 908e8149455cb1cbeb37724266ee3af7722c7a5b..9ba22f2a3d394eda3bba02ee000f087f73b4c11a 100644 --- a/.gitlab/issue_templates/publier_nouvelle_version.md +++ b/.gitlab/issue_templates/publier_nouvelle_version.md @@ -3,9 +3,10 @@ Pour passer de la version *SNAPSHOT* à la version stable : - [ ] créer une demande de fusion et une branche à partir du ticket - [ ] changer la version dans `pom.xml` ```sh - mvn versions:set -DnewVersion=2.0.2 + mvn versions:set -DnewVersion=2.1.0 mvn versions:commit ``` +- [ ] mettre à jour `src/site/markdown/release-notes-fr.md` - [ ] mettre à jour les fichiers de métadonnées du projet - [ ] fusionner - [ ] déployer sur Archiva diff --git a/pom.xml b/pom.xml index 73b6018727b7f81a89c45274b64432d1eac90fdc..c354dd7323d2d09f41f06bd2351af582917e309d 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ along with Indicators. If not, see <https://www.gnu.org/licenses/>. <name>Indicators</name> <description>Library of agro- and eco-climatic indicators.</description> <inceptionYear>2018</inceptionYear> - <version>2.0.3-SNAPSHOT</version> + <version>2.1.0-SNAPSHOT</version> <packaging>jar</packaging> <licenses> <license> @@ -382,11 +382,11 @@ along with Indicators. If not, see <https://www.gnu.org/licenses/>. </executions> </plugin> <!-- Attach source and javadoc artifacts --> - <!-- https://maven.apache.org/plugin-developers/cookbook/attach-source-javadoc-artifacts.html --> + <!-- https://maven.apache.org/plugins/maven-source-plugin/usage.html --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> - <version>3.3.0</version> + <version>3.3.1</version> <executions> <execution> <id>attach-sources</id> diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java index 8e78e9e41151c11b9af6a12f31bce47aaa6ed37c..e228def7d35866fadcca0f32a7f7cb2d8d4eecf9 100644 --- a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java +++ b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java @@ -16,8 +16,6 @@ */ package fr.inrae.agroclim.indicators.model; -import java.io.IOException; -import java.io.ObjectInputStream; import java.time.LocalDate; import java.util.ArrayList; import java.util.Collections; @@ -34,6 +32,7 @@ import java.util.stream.Collectors; import fr.inrae.agroclim.indicators.exception.IndicatorsException; import fr.inrae.agroclim.indicators.exception.type.ResourceErrorType; +import fr.inrae.agroclim.indicators.model.data.DailyData; import fr.inrae.agroclim.indicators.model.data.ResourceManager; import fr.inrae.agroclim.indicators.model.data.Variable; import fr.inrae.agroclim.indicators.model.data.Variable.Type; @@ -106,21 +105,6 @@ public final class Evaluation extends CompositeIndicator { }); } - /** - * Computed phases (or phases from file) used to compute indicators. - * - * TODO : supprimer et utiliser - * EvaluationResult::getPhaseResults::getAnnualPhase. - */ - @Getter - private transient List<AnnualPhase> computedPhases; - - /** - * Results of computation by year. - */ - @Getter - private transient Map<Integer, EvaluationResult> results = new LinkedHashMap<>(); - /** * All resources needed to run the evaluation. * @@ -129,6 +113,11 @@ public final class Evaluation extends CompositeIndicator { @Getter private final ResourceManager resourceManager; + /** + * Flag to ignore when climatic data is empty for a phase. + */ + private boolean ignoreEmptyClimaticData = false; + /** * Flag for state Saved. */ @@ -198,12 +187,7 @@ public final class Evaluation extends CompositeIndicator { throw new RuntimeException("This should never occur!", ex); } } - if (evaluation.computedPhases != null) { - computedPhases = new ArrayList<>(); - computedPhases.addAll(evaluation.computedPhases); - } isTranscient = evaluation.isTranscient; - results = evaluation.results; state = evaluation.state; } @@ -285,11 +269,26 @@ public final class Evaluation extends CompositeIndicator { } /** - * Clear results of indicators. + * Check data before running compute* methods. + * + * @param phases phenological phases to check + * @param climaticResource climatic resource to check + * @throws IndicatorsException exception */ - private void clearResults() { - results.clear(); - computedPhases = new ArrayList<>(); + private void checkBeforeCompute(final List<CompositeIndicator> phases, final ClimaticResource climaticResource) + throws IndicatorsException { + if (phases == null) { + throw new RuntimeException("Phase list is null!"); + } + if (phases.isEmpty()) { + throw new RuntimeException("Phase list is empty!"); + } + if (climaticResource.isEmpty()) { + throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY); + } + if (resourceManager.getPhenologicalResource().isEmpty()) { + throw new IndicatorsException(ResourceErrorType.PHENO_EMPTY); + } } @Override @@ -300,32 +299,30 @@ public final class Evaluation extends CompositeIndicator { /** * Compute indicator results. * + * @return Results of computation by year. * @throws IndicatorsException * from Indicator.compute() */ - public void compute() throws IndicatorsException { + public Map<Integer, EvaluationResult> compute() throws IndicatorsException { LOGGER.trace("start computing evaluation \"" + getName() + "\""); + this.ignoreEmptyClimaticData = false; fireIndicatorEvent(IndicatorEvent.Type.COMPUTE_START.event(this)); final List<CompositeIndicator> phases = getPhases(); - if (phases == null) { - throw new RuntimeException("Phase list is null!"); - } - if (phases.isEmpty()) { - throw new RuntimeException("Phase list is empty!"); - } - if (resourceManager.getClimaticResource().isEmpty()) { - throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY); - } - if (resourceManager.getPhenologicalResource().isEmpty()) { - throw new IndicatorsException(ResourceErrorType.PHENO_EMPTY); - } + final ClimaticResource climaticResource = resourceManager.getClimaticResource(); + checkBeforeCompute(phases, climaticResource); - clearResults(); + var results = compute(climaticResource, phases); + fireIndicatorEvent(IndicatorEvent.Type.COMPUTE_SUCCESS.event(this)); + return results; + } + + private Map<Integer, EvaluationResult> compute(final ClimaticResource climaticResource, + final List<CompositeIndicator> phases) throws IndicatorsException { + final Map<Integer, EvaluationResult> results = new LinkedHashMap<>(); /* Pour chaque phase */ - List<AnnualStageData> stageDatas; - stageDatas = getResourceManager().getPhenologicalResource().getData(); + final List<AnnualStageData> stageDatas = getResourceManager().getPhenologicalResource().getData(); for (final CompositeIndicator phase : phases) { final String phaseId = phase.getId(); if (phaseId == null) { @@ -348,8 +345,7 @@ public final class Evaluation extends CompositeIndicator { dateYear -= 1; } - if (!resourceManager.getClimaticResource().getYears() - .contains(dateYear)) { + if (!climaticResource.getYears().contains(dateYear)) { continue; } @@ -368,7 +364,6 @@ public final class Evaluation extends CompositeIndicator { annualPhase.setEndStage(endStageName); annualPhase.setStartStage(startStageName); annualPhase.setUid(phaseId); - computedPhases.add(annualPhase); final PhaseResult phaseResult = new PhaseResult(); phaseResult.setAnnualPhase(annualPhase); results.get(year).getPhaseResults().add(phaseResult); @@ -399,15 +394,19 @@ public final class Evaluation extends CompositeIndicator { } /* Données climatiques pendant la phase et l'année donnée */ - final ClimaticResource climaticData = resourceManager - .getClimaticResource().getClimaticDataByPhaseAndYear(startDate, endDate); + final ClimaticResource climaticData = climaticResource + .getClimaticDataByPhaseAndYear(startDate, endDate); if (climaticData.isEmpty()) { - if (resourceManager.getClimaticResource().isEmpty()) { + if (climaticResource.isEmpty()) { throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY); } + if (ignoreEmptyClimaticData) { + continue; + } final int yearToSearch = dateYear; - final List<ClimaticDailyData> ddataList = resourceManager.getClimaticResource().getData() - .stream().filter(f -> f.getYear() == yearToSearch).collect(Collectors.toList()); + final List<ClimaticDailyData> ddataList = climaticResource.getData().stream() // + .filter(f -> f.getYear() == yearToSearch) // + .collect(Collectors.toList()); final ClimaticDailyData startData = ddataList.get(0); final ClimaticDailyData endData = ddataList.get(ddataList.size() - 1); @@ -433,9 +432,40 @@ public final class Evaluation extends CompositeIndicator { } } - computeFaisability(); - fireIndicatorEvent(IndicatorEvent.Type.COMPUTE_SUCCESS.event(this)); + computeFaisability(results); LOGGER.trace("end of computing evaluation \"{}\"", getName()); + return results; + } + + /** + * Compute indicator results for each step of provided climatic data. + * + * @return Results of computation by year, for each step in climatic data: + * Climatic date ⮕ (year, {@link EvalutationResult}). + * @throws IndicatorsException + * from Indicator.compute() + */ + public Map<LocalDate, Map<Integer, EvaluationResult>> computeEachDate() throws IndicatorsException { + this.ignoreEmptyClimaticData = true; + final List<CompositeIndicator> phases = getPhases(); + final ClimaticResource climaticResource = resourceManager.getClimaticResource(); + checkBeforeCompute(phases, climaticResource); + + final List<ClimaticDailyData> dailyData = climaticResource.getData(); + // first, create Maps + final Map<LocalDate, Map<Integer, EvaluationResult>> allResults = new LinkedHashMap<>(); + dailyData.stream().map(DailyData::getLocalDate).forEach(date -> allResults.put(date, new LinkedHashMap<>())); + // then compute + final ClimaticResource resource = new ClimaticResource(); + resource.setMissingVariables(climaticResource.getMissingVariables()); + for (int i = 0; i < dailyData.size(); i++) { + final List<ClimaticDailyData> data = dailyData.subList(0, i); + resource.setData(data); + final Map<Integer, EvaluationResult> results = compute(resource, phases); + final LocalDate date = dailyData.get(i).getLocalDate(); + allResults.put(date, results); + } + return allResults; } /** @@ -448,9 +478,10 @@ public final class Evaluation extends CompositeIndicator { * Pour chaque année, Pour chaque phase, valeurs.onIndicatorAdd(valeur phase * p, année n); Fin pour aggregation(valeurs); Fin pour; * + * @param results Results of computation by year. * @throws IndicatorsException raised by AggregationFunction.aggregate() */ - private void computeFaisability() throws IndicatorsException { + private void computeFaisability(final Map<Integer, EvaluationResult> results) throws IndicatorsException { LOGGER.traceEntry(); if (getType() == EvaluationType.WITHOUT_AGGREGATION) { return; @@ -459,18 +490,14 @@ public final class Evaluation extends CompositeIndicator { // if only 1 phase, no need to aggregate // evaluation value = value of the phase results.values().forEach(result -> - result.setNormalizedValue( - result.getPhaseResults().get(0).getNormalizedValue() - ) - ); + result.setNormalizedValue(result.getPhaseResults().get(0).getNormalizedValue())); return; } else if (getAggregationFunction().getExpression() == null) { throw new IllegalStateException("An evaluation with more than 1 " + "phase must have a defined expression for aggregation!"); } - for (final Map.Entry<Integer, EvaluationResult> entry - : results.entrySet()) { + for (final Map.Entry<Integer, EvaluationResult> entry : results.entrySet()) { final EvaluationResult evaluationResult = entry.getValue(); final int year = entry.getKey(); if (evaluationResult == null) { @@ -872,22 +899,6 @@ public final class Evaluation extends CompositeIndicator { return result; } - /** - * Context: Deserialization does not initialize results. - * - * A final field must be initialized either by direct assignment of an initial value or in the constructor. During - * deserialization, neither of these are invoked, so initial values for transients must be set in the - * 'readObject()' private method that's invoked during deserialization. And for that to work, the transients must - * be non-final. - * - * @param ois input stream from deserialization - */ - private void readObject(final ObjectInputStream ois) throws ClassNotFoundException, IOException { - // perform the default de-serialization first - ois.defaultReadObject(); - results = new HashMap<>(); - } - /** * Set parameters (id and attributes) for the indicator and its criteria * from knowledge defined in settings. diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/data/HourlyData.java b/src/main/java/fr/inrae/agroclim/indicators/model/data/HourlyData.java index 5c6f5c2803a6bd8228fcdc9587325b508585d0ed..97ecc1477185353e784f38ad440b72ab98885c2d 100644 --- a/src/main/java/fr/inrae/agroclim/indicators/model/data/HourlyData.java +++ b/src/main/java/fr/inrae/agroclim/indicators/model/data/HourlyData.java @@ -21,6 +21,7 @@ import java.util.Calendar; import java.util.Date; import fr.inrae.agroclim.indicators.util.DateUtils; +import java.time.LocalDate; import lombok.Getter; /** @@ -174,6 +175,15 @@ public abstract class HourlyData implements Cloneable, Data, Serializable { return DateUtils.getDoy(getDate()); } + /** + * Create date object with {@code day}, {@code month}, {@code year} and {@code hour} attribute.<br> + * If at least one of these attributes is null, then the returned date is null. + * @return date object + */ + public LocalDate getLocalDate() { + return DateUtils.asLocalDate(getDate()); + } + /** * Get value for variable as it was set. * diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java index c0af4e5e5dbc4cfa4ad98f6a35129ec47a2437c0..b639fc7017df7c3cdbbdd415c9a71268afb205bf 100644 --- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java +++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java @@ -252,14 +252,14 @@ implements DataLoadingListener, Detailable, HasDataLoadingListener, Comparable<I } @Override - public final Double compute(final Resource<? extends DailyData> climRessource) throws IndicatorsException { - if (climRessource.getYears().isEmpty()) { + public final Double compute(final Resource<? extends DailyData> climResource) throws IndicatorsException { + if (climResource.getYears().isEmpty()) { throw new RuntimeException( String.format( "No years in ClimaticResource (%d dailyData)!", - climRessource.getData().size())); + climResource.getData().size())); } - final HashMap<String, Double> results = new HashMap<>(); + final Map<String, Double> results = new HashMap<>(); double valueAfterAggregation = 0; Double valueAfterNormalization; @@ -274,7 +274,7 @@ implements DataLoadingListener, Detailable, HasDataLoadingListener, Comparable<I } try { - final double value = indicator.compute(climRessource); + final Double value = indicator.compute(climResource); results.put(indicator.getId(), value); } catch (final IndicatorsException e) { throw new IndicatorsException(ComputationErrorType.COMPOSITE_COMPUTATION, e, indicator.getId()); @@ -651,18 +651,6 @@ implements DataLoadingListener, Detailable, HasDataLoadingListener, Comparable<I getIndicators().forEach(i -> i.setParametersValues(values)); } - /** - * Detect if aggregation function is needed but missing. - * - * @param fire fire events while checking - * @return true if aggregation function is needed but missing - * @deprecated use {@link CompositeIndicator#isAggregationMissing(boolean)}. - */ - @Deprecated(since = "2.0.1", forRemoval = true) - public final boolean toAggregate(final boolean fire) { - return isAggregationMissing(fire); - } - @Override public final String toStringTree(final String indent) { final StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Indicator.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Indicator.java index 22969b6ce942f706238ea43f43320a201c808da7..e383f79e9c58b43b21c9c899b37b8cd7e29e835a 100644 --- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Indicator.java +++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Indicator.java @@ -186,6 +186,7 @@ Serializable, UseVariables { * Normalized value. */ @XmlTransient + @Getter @Setter private Double value; @@ -225,8 +226,8 @@ Serializable, UseVariables { } } listeners.add(IndicatorListener.class, listener); - if (this instanceof CompositeIndicator) { - ((CompositeIndicator) this).getIndicators() + if (this instanceof CompositeIndicator compositeIndicator) { + compositeIndicator.getIndicators() .forEach(i -> i.addIndicatorListener(listener)); } } @@ -364,15 +365,6 @@ Serializable, UseVariables { */ public abstract EvaluationType getType(); - /** - * The normalized value. - * - * @return Normalized value - */ - public final Double getValue() { - return value; - } - /** * @param indicatorCategory indicator category */ diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/result/EvaluationResult.java b/src/main/java/fr/inrae/agroclim/indicators/model/result/EvaluationResult.java index 70782bf192970c64842148aac4ddf90283a4f9ce..d0fba97c8568481b5087dd470998c7a7d8449b02 100644 --- a/src/main/java/fr/inrae/agroclim/indicators/model/result/EvaluationResult.java +++ b/src/main/java/fr/inrae/agroclim/indicators/model/result/EvaluationResult.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; import lombok.Getter; +import lombok.ToString; /** * Normalized value of an evaluation for a year and values of composed phases. @@ -30,6 +31,7 @@ import lombok.Getter; * @author $Author$ * @version $Revision$ */ +@ToString public class EvaluationResult extends Result { /** diff --git a/src/site/markdown/release-notes-fr.md b/src/site/markdown/release-notes-fr.md new file mode 100644 index 0000000000000000000000000000000000000000..d9c7ccf1cd03dcce76a02deb4596a4562c38db09 --- /dev/null +++ b/src/site/markdown/release-notes-fr.md @@ -0,0 +1,32 @@ +--- +title: Notes de version +description: Modifications de la bibliothèque d'indicateurs. +keywords: "version" +date: 2024-08-19 +--- + +# [v2.1.0](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.1.0) − + +- Ajouter la méthode `Evaluation.computeEachDate()`. +- Renvoyer les résultats de calcul par la méthode `Evaluation.compute()` et ne plus les stocker dans `Evaluation`. +- Supprimer `Evaluation#getResults()`. +- Supprimer `Evaluation#getComputedPhases()`. +- Supprimer de `CompositeIndicator#toAggregate(boolean)`. + +# [v2.0.2](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.0.2) − 11 juillet 2024 + +- Passer à Jakarta XML Binding. + +# [v2.0.1](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.0.1) − 5 avril 2024 + +- Ajouter le modèle phénologique Richardson. +- Renommer `CompositeIndicator.toAggregate(boolean)`. +- Corriger l'id et la référence d'indicateurs THI. +- Dépréciation `CompositeIndicator#toAggregate(boolean)`. + +# [v2.0.0](https://forgemia.inra.fr/agroclim/Indicators/indicators-java/-/releases/v2.0.0) − 18 janvier 2024 + +- Gestion des données climatiques manquantes. +- Changement de gestion des exceptions et affichage de codes d'erreurs. +- Passage sous GitLab. +- Mise en ligne de la documentation. diff --git a/src/site/site.xml b/src/site/site.xml index 0f490a9957e4395f831950839cbe92b575cd2423..89ea402eb4e21c771e16fafa29ee2fbd3f684924 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -30,6 +30,7 @@ <item name="Indicateurs journaliers" href="indicators-daily-fr.html" /> <item name="Codes d'erreurs" href="errors-fr.html" /> <item name="Modèles phénologiques" href="pheno-fr.html" /> + <item name="Notes de version" href="release-notes-fr.html" /> <item name="Documentation in English" href="en/index.html" /> </menu> <menu ref="modules" inherit="top" /> diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java new file mode 100644 index 0000000000000000000000000000000000000000..95dfba5c3926f3406d45bedd21e916b01560fc13 --- /dev/null +++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationEachDateTest.java @@ -0,0 +1,178 @@ +package fr.inrae.agroclim.indicators.model; + +import fr.inrae.agroclim.indicators.exception.IndicatorsException; +import fr.inrae.agroclim.indicators.model.data.DataTestHelper; +import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator; +import fr.inrae.agroclim.indicators.model.indicator.Indicator; +import fr.inrae.agroclim.indicators.model.result.EvaluationResult; +import fr.inrae.agroclim.indicators.model.result.IndicatorResult; +import fr.inrae.agroclim.indicators.model.result.PhaseResult; +import fr.inrae.agroclim.indicators.util.DateUtils; +import java.io.File; +import java.time.LocalDate; +import java.time.Month; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Test {@link Evaluation#computeEachDate()}. + * + * @author Olivier Maury + */ +public class EvaluationEachDateTest extends DataTestHelper { + + /** + * The only year in climatic data. + */ + private static final Integer YEAR = 2015; + + /** + * Evaluation from good XML file. + */ + private static Evaluation evaluation; + + /** + * Computation results. + */ + private static Map<LocalDate, Map<Integer, EvaluationResult>> results; + + /** + * DOY of Stage 0. + */ + private static final int S0 = 120; + + /** + * DOY of Stage 0 plus 1 day. + */ + private static final int S0_PLUS_1 = S0 + 1; + + /** + * DOY of Stage 1. + */ + private static final int S1 = 140; + + /** + * Set up Evaluation to test. + * + * Check if evaluation computes. + */ + @BeforeClass + public static void beforeTest() { + final File xmlFile = getFile("xml/evaluation-phalen.gri"); + evaluation = getEvaluation(xmlFile); + assertTrue("Evaluation must not be null!", evaluation != null); + try { + evaluation.initializeResources(); + results = evaluation.computeEachDate(); + } catch (final IndicatorsException e) { + final String error = e.getClass() + " : " + e.getLocalizedMessage(); + fail(error); + } + } + + /** + * Ensure only 2015 has data. + */ + @Test + public void years() { + assertNotNull(results); + + assertEquals(evaluation.getClimaticResource().getData().size(), results.size()); + final List<Integer> years = results.values().stream().flatMap(m -> m.keySet().stream()).distinct().toList(); + assertNotNull(years); + assertEquals(1, years.size()); + assertEquals(YEAR, years.get(0)); + } + + /** + * Ensure only 1 indicator is computed. + */ + @Test + public void indicators() { + // s0s1 : phalen + final List<Indicator> indicators = new ArrayList<>(); + final List<Indicator> allIndicators = new ArrayList<>(); + allIndicators.addAll(evaluation.getIndicators()); + Indicator indicator = allIndicators.remove(0); + while (indicator != null) { + if (indicator instanceof CompositeIndicator c) { + allIndicators.addAll(c.getIndicators()); + } else { + indicators.add(indicator); + } + if (!allIndicators.isEmpty()) { + indicator = allIndicators.remove(0); + } else { + indicator = null; + } + } + final int nbOfIndicators = indicators.size(); + assertEquals(1, nbOfIndicators); + } + + /** + * Ensure no data is returned out of s0s1. + */ + @Test + public void noDataOutOfPhase() { + // no data on 1st january 2015 as no phase + final Optional<LocalDate> firstDateOptional = results.keySet().stream().findFirst(); + assertTrue(firstDateOptional.isPresent()); + final LocalDate firstDate = firstDateOptional.get(); + final LocalDate expectedFirstDate = LocalDate.of(YEAR, Month.JANUARY, 1); + assertEquals(expectedFirstDate, firstDate); + assertFalse(results.get(firstDate).containsKey(YEAR)); + } + + /** + * Ensure data are return each date of s0s1. + */ + @Test + public void dataInPhase() { + for (int doy = S0_PLUS_1; doy < S1; doy++) { + final LocalDate date = DateUtils.asLocalDate(DateUtils.getDate(YEAR, doy)); + final List<PhaseResult> phaseResults = results.get(date).get(YEAR).getPhaseResults(); + assertFalse(phaseResults.isEmpty()); + assertEquals(1, phaseResults.size()); + final List<IndicatorResult> processResults = phaseResults.get(0).getIndicatorResults(); + assertNotNull("On " + date + "/" + doy + ", process results must not be null!", processResults); + assertEquals(1, processResults.size()); + assertEquals("growth", processResults.get(0).getIndicatorId()); + + // practices > growth > phalength > phalen + final List<IndicatorResult> practicesResults = processResults.get(0).getIndicatorResults(); + assertEquals("phalength", practicesResults.get(0).getIndicatorId()); + List<IndicatorResult> indicatorResults = practicesResults.get(0).getIndicatorResults(); + assertEquals("phalen", indicatorResults.get(0).getIndicatorId()); + Double value = indicatorResults.get(0).getRawValue(); + assertNotNull("On " + date + "/" + doy + ", phalen must not be null!", value); + } + } + + /** + * Check computed values for each date. + */ + @Test + public void checkData() { + for (int doy = S0_PLUS_1; doy < S1; doy++) { + final LocalDate date = DateUtils.asLocalDate(DateUtils.getDate(YEAR, doy)); + final List<PhaseResult> phaseResults = results.get(date).get(YEAR).getPhaseResults(); + final List<IndicatorResult> processResults = phaseResults.get(0).getIndicatorResults(); + final List<IndicatorResult> practicesResults = processResults.get(0).getIndicatorResults(); + final List<IndicatorResult> indicatorResults = practicesResults.get(0).getIndicatorResults(); + final Double value = indicatorResults.get(0).getRawValue(); + assertNotNull("On " + date + "/" + doy + ", phalen must not be null!", value); + final Double expected = Double.valueOf(doy - S0); + assertEquals("On " + date + "/" + doy + ", phalen must equal " + expected, expected, value); + } + } +} diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java index f90f64f02a78d61b89621df94754f98da23efe15..0de0bdb9c049098fddb1ae4afb2103d9b69972a1 100644 --- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java +++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java @@ -2,8 +2,8 @@ package fr.inrae.agroclim.indicators.model; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.File; import java.util.List; @@ -52,16 +52,16 @@ public class EvaluationHourlyTest extends DataTestHelper { @Test public void compute() { assertTrue("Evaluation must not be null!", evaluation != null); - String error = null; + final Map<Integer, EvaluationResult> results; try { evaluation.initializeResources(); - evaluation.compute(); + results = evaluation.compute(); } catch (final IndicatorsException e) { - error = e.getClass() + " : " + e.getLocalizedMessage(); + final String error = e.getClass() + " : " + e.getLocalizedMessage(); + fail(error); + return; } - assertNull(error, error); - final Map<Integer, EvaluationResult> results = evaluation.getResults(); assertNotNull(results); assertEquals(1, results.keySet().size()); assertTrue(results.containsKey(2015)); diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java index 8e99a6bcc81a37985ea5f3912e28807b5bf8f31c..38d6032269a66be0b9c3aaac50641feaf54f894f 100644 --- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java +++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java @@ -20,8 +20,8 @@ package fr.inrae.agroclim.indicators.model; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.File; import java.util.Arrays; @@ -80,16 +80,16 @@ public class EvaluationRobertTest extends DataTestHelper { @Test public void compute() { assertTrue("Evaluation must not be null!", evaluation != null); - String error = null; + final Map<Integer, EvaluationResult> results; try { evaluation.initializeResources(); - evaluation.compute(); + results = evaluation.compute(); } catch (final IndicatorsException e) { - error = e.getClass() + " : " + e.getLocalizedMessage(); + final String error = e.getClass() + " : " + e.getLocalizedMessage(); + fail(error); + return; } - assertNull(error, error); - final Map<Integer, EvaluationResult> results = evaluation.getResults(); assertNotNull(results); assertEquals(2, results.keySet().size()); assertTrue(results.containsKey(1991)); diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java index a3f975d440362218c78939090d20dd93caa464e9..0603a053344e58168965dcd3287b09f62e9bd5f3 100644 --- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java +++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.File; import java.io.FileOutputStream; @@ -184,8 +185,11 @@ public final class EvaluationTest extends DataTestHelper { String error = null; try { evaluation.initializeResources(); - evaluation.compute(); - final List<AnnualPhase> phases = evaluation.getComputedPhases(); + final Map<Integer, EvaluationResult> results = evaluation.compute(); + final List<AnnualPhase> phases = results.values().stream() // + .flatMap(r -> r.getPhaseResults().stream()) // + .map(r -> r.getAnnualPhase()) // + .toList(); final int nbOfPhases = evaluation.getIndicators().size(); final int nbOfYears = evaluation.getClimaticResource().getYears().size(); assertEquals(nbOfPhases * nbOfYears, phases.size()); @@ -238,8 +242,7 @@ public final class EvaluationTest extends DataTestHelper { final String fmt = "%s | %4d | %s-%s | %f | %f"; try { evaluation.initializeResources(); - evaluation.compute(); - final Map<Integer, EvaluationResult> results = evaluation.getResults(); + final Map<Integer, EvaluationResult> results = evaluation.compute(); assertNotNull("Results must not be null!", results); results.entrySet().forEach((entryER) -> { final Integer year = entryER.getKey(); @@ -420,15 +423,16 @@ public final class EvaluationTest extends DataTestHelper { public void setParameters() { assertTrue("Evaluation must not be null!", evaluation != null); String error = null; + Map<Integer, EvaluationResult> results; try { evaluation.initializeResources(); - evaluation.compute(); + results = evaluation.compute(); } catch (final IndicatorsException e) { error = e.getClass() + " : " + e.getLocalizedMessage(); + fail(error); + return; } - assertNull(error, error); - final Map<Integer, EvaluationResult> results = evaluation.getResults(); final Double initValue = getResultValue("s0s1", "heat", 2015, results); assertNotNull("value of climatic effect \"heat\" must not be null", initValue); @@ -438,11 +442,11 @@ public final class EvaluationTest extends DataTestHelper { parameters.put("Theat", 25.); evaluation.setParametersValues(parameters); try { - evaluation.compute(); + results = evaluation.compute(); } catch (IndicatorsException e) { - error = e.getClass() + " : " + e.getLocalizedMessage(); + fail(error); + return; } - assertNull(error, error); final Double newValue = getResultValue("s0s1", "heat", 2015, results); assertNotEquals("init = " + initValue + ", new = " + newValue, initValue, newValue); diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java index d226c65e8f14753ba7ba49ca7baa7d4aa46b4f81..d02a249d7e78c0e67eee34eb894431e37387fea8 100644 --- a/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java +++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java @@ -20,8 +20,8 @@ package fr.inrae.agroclim.indicators.model; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.File; import java.util.Map; @@ -63,15 +63,15 @@ public class EvalutationCustomHeadersTest extends DataTestHelper { @Test public void compute() { assertTrue("Evaluation must not be null!", evaluation != null); - String error = null; + final Map<Integer, EvaluationResult> results; try { evaluation.initializeResources(); - evaluation.compute(); + results = evaluation.compute(); } catch (final IndicatorsException e) { - error = e.getClass() + " : " + e.getLocalizedMessage(); + final String error = e.getClass() + " : " + e.getLocalizedMessage(); + fail(error); + return; } - assertNull(error, error); - final Map<Integer, EvaluationResult> results = evaluation.getResults(); assertNotNull("Results must not be null!", results); assertEquals("One year en climate file", 1, results.keySet().size()); results.values().stream().flatMap(v -> v.getPhaseResults().stream()).forEach(phaseResult -> { diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java index 826138824f84f7314d2a0e33ed80c815b6e18e27..a102b23ac2427707322be6657ca861ae5270836c 100644 --- a/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java +++ b/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java @@ -17,7 +17,6 @@ package fr.inrae.agroclim.indicators.model; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.File; @@ -35,6 +34,7 @@ import fr.inrae.agroclim.indicators.model.result.EvaluationResult; import fr.inrae.agroclim.indicators.model.result.IndicatorResult; import fr.inrae.agroclim.indicators.model.result.PhaseResult; import lombok.extern.log4j.Log4j2; +import static org.junit.Assert.fail; /** * Test that Marine Marjou's Getari file computes. @@ -76,16 +76,17 @@ public class MMarjouTest extends DataTestHelper { @Test public void raidaysInfNbDaysInPhase() { assertTrue("Evaluation must not be null!", evaluation != null); - String error = null; + final Map<Integer, EvaluationResult> results; try { evaluation.initializeResources(); long start = System.currentTimeMillis(); - evaluation.compute(); + results = evaluation.compute(); LOGGER.info("Evaluation.compute() last {} seconds", (System.currentTimeMillis() - start) / 1000.); } catch (final IndicatorsException e) { - error = e.getClass() + " : " + e.getLocalizedMessage(); + final String error = e.getClass() + " : " + e.getLocalizedMessage(); + fail(error); + return; } - assertNull(error, error); // extract stages to compute number of days Map<Integer, Map<String, Integer>> stageDates = new HashMap<>(); List<AnnualStageData> aSDatas; @@ -100,9 +101,8 @@ public class MMarjouTest extends DataTestHelper { }); }); // check excraidays, hraidays and raidays - assertFalse(evaluation.getResults().isEmpty()); + assertFalse(results.isEmpty()); String fmt = "%d | %s %s : %d | %s=%.4f"; - Map<Integer, EvaluationResult> results = evaluation.getResults(); for (Map.Entry<Integer, EvaluationResult> entry : results.entrySet()) { Integer year = entry.getKey(); if (!stageDates.containsKey(year)) { diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java index c9c8ba33ee6693c1c93fb225b2730447543df802..c31942d796a984e3d3eaf7bc831108afcb43255d 100644 --- a/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java +++ b/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java @@ -19,8 +19,8 @@ package fr.inrae.agroclim.indicators.model; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; @@ -208,8 +208,8 @@ public class RaidayMeantTest extends DataTestHelper { @Test public void computeUsingPhenologyCalculator() { assertTrue("Evaluation must not be null!", evaluation != null); - String error = null; - List<AnnualStageData> annualStageDatas = null; + final List<AnnualStageData> annualStageDatas; + final Map<Integer, EvaluationResult> results; try { final EvaluationSettings settings = evaluation.getSettings(); // wheat Soissons @@ -221,25 +221,29 @@ public class RaidayMeantTest extends DataTestHelper { settings.getPhenologyLoader().setCalculator(calc); settings.getPhenologyLoader().setFile(null); evaluation.initializeResources(); - evaluation.compute(); + results = evaluation.compute(); annualStageDatas = evaluation.getResourceManager().getPhenologicalResource().getData(); } catch (final IndicatorsException | IOException e) { - error = e.getClass() + " : " + e.getLocalizedMessage(); + final String error = e.getClass() + " : " + e.getLocalizedMessage(); + fail(error); + return; } - assertNull(error, error); // first year in phenology file final int firstYear = 1959; assertNotNull(annualStageDatas); assertFalse(annualStageDatas.isEmpty()); assertTrue(firstYear == annualStageDatas.get(0).getYear()); - final List<AnnualPhase> computedPhases = evaluation.getComputedPhases(); + final List<AnnualPhase> computedPhases = results.values().stream() // + .flatMap(r -> r.getPhaseResults().stream()) // + .map(r -> r.getAnnualPhase()) // + .toList(); assertNotNull(computedPhases); assertFalse(computedPhases.isEmpty()); assertTrue(firstYear == computedPhases.get(0).getHarvestYear()); compareStagesAndPhases(annualStageDatas, computedPhases); - assertFalse(evaluation.getResults().isEmpty()); + assertFalse(results.isEmpty()); } /** @@ -248,14 +252,15 @@ public class RaidayMeantTest extends DataTestHelper { @Test public void compute() { assertTrue("Evaluation must not be null!", evaluation != null); - String error = null; + final Map<Integer, EvaluationResult> results; try { evaluation.initializeResources(); - evaluation.compute(); + results = evaluation.compute(); } catch (final IndicatorsException e) { - error = e.getClass() + " : " + e.getLocalizedMessage(); + final String error = e.getClass() + " : " + e.getLocalizedMessage(); + fail(error); + return; } - assertNull(error, error); // extract stages to compute number of days final Map<Integer, Map<String, Integer>> stageDates = new HashMap<>(); List<AnnualStageData> aSDatas; @@ -271,9 +276,9 @@ public class RaidayMeantTest extends DataTestHelper { }); // check excraidays, hraidays and raidays - assertFalse(evaluation.getResults().isEmpty()); + assertFalse(results.isEmpty()); final String fmt = "%d | %s %s : %d | %s=%.4f"; - for (final Map.Entry<Integer, EvaluationResult> entry : evaluation.getResults().entrySet()) { + for (final Map.Entry<Integer, EvaluationResult> entry : results.entrySet()) { final Integer year = entry.getKey(); if (!stageDates.containsKey(year)) { LOGGER.trace("No stage date for year {}", year); @@ -317,14 +322,16 @@ public class RaidayMeantTest extends DataTestHelper { } assertNotNull(aSDatas); - final List<AnnualPhase> computedPhases = evaluation.getComputedPhases(); + final List<AnnualPhase> computedPhases = results.values().stream() // + .flatMap(r -> r.getPhaseResults().stream()) // + .map(r -> r.getAnnualPhase()) // + .toList(); compareStagesAndPhases(aSDatas, computedPhases); - Map<String, Map<Integer, Map<String, Double>>> computation; - computation = new HashMap<>(); + final Map<String, Map<Integer, Map<String, Double>>> computation = new HashMap<>(); - evaluation.getResults().forEach((year, results) -> { - results.getPhaseResults().forEach((phase) -> { + results.forEach((year, r) -> { + r.getPhaseResults().forEach((phase) -> { final String phaseId = phase.getPhaseId(); if (!computation.containsKey(phaseId)) { computation.put(phaseId, new HashMap<>()); diff --git a/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri b/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri new file mode 100644 index 0000000000000000000000000000000000000000..b11858c38bd5999cb95fee74f30d9e58d242736b --- /dev/null +++ b/src/test/resources/fr/inrae/agroclim/indicators/xml/evaluation-phalen.gri @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!DOCTYPE evaluationSettings PUBLIC + "-//INRAE AgroClim.//DTD Evaluation 1.1//EN" + "https://agroclim.inrae.fr/getari/dtd/1.1/evaluation.dtd"> +<evaluationSettings timescale="DAILY" timestamp="2024-08-19T14:26:01.505524546" version="2.0.1+20240405062628"> + <climate> + <file path="../model/data/climate/climate-2015.csv"> + <separator> </separator> + <header></header> + <header>year</header> + <header>month</header> + <header>day</header> + <header>tmin</header> + <header>tmax</header> + <header>tmean</header> + <header></header> + <header>radiation</header> + <header>rain</header> + <header>rh</header> + <header>wind</header> + <header>ETP</header> + <midnight>0</midnight> + </file> + </climate> + <notes/> + <phenology> + <file path="../model/data/phenology/pheno_sample.csv"> + <separator>;</separator> + <header>year</header> + <header>s0</header> + <header>s1</header> + <header>s2</header> + <header>s3</header> + <header>s4</header> + </file> + </phenology> + <name>evaluation-phalen</name> + <type>WITH_AGGREGATION</type> + <compositeIndicator> + <name>root-test</name> + <name xml:lang="en">evaluation-test</name> + <id>root-evaluation</id> + <timescale>DAILY</timescale> + <tag>practices</tag> + <aggregationFunction xsi:type="jexlFunction" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> + <indicator xsi:type="compositeIndicator" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <name>s1</name> + <id>s0s1</id> + <category>pheno</category> + <color>#5F9EA0</color> + <timescale>DAILY</timescale> + <tag>pheno-s0-s1</tag> + <indicator xsi:type="compositeIndicator"> + <name>s0</name> + <id>pheno_s0</id> + <category>pheno</category> + <color>#5F9EA0</color> + <timescale>DAILY</timescale> + <normalizationFunction xsi:type="exponential" expA="0.0" expB="0.0"/> + <tag>pheno-s0-s1</tag> + </indicator> + <indicator xsi:type="compositeIndicator"> + <name xml:lang="en">Crop growth</name> + <name xml:lang="fr">Développement cultural</name> + <id>growth</id> + <category>ecoprocesses</category> + <color>#5F9EA0</color> + <timescale>DAILY</timescale> + <indicator xsi:type="compositeIndicator"> + <name xml:lang="fr">Durée de la phase</name> + <name xml:lang="en">Phase length</name> + <id>phalength</id> + <category>climatic</category> + <color>#5F9EA0</color> + <timescale>DAILY</timescale> + <indicator xsi:type="phaseLength"> + <description xml:lang="fr">Durée de la phase.</description> + <description xml:lang="en">Phase length.</description> + <name xml:lang="fr">Durée de la phase</name> + <name xml:lang="en">Phase length</name> + <id>phalen</id> + <category>indicator</category> + <color>#5F9EA0</color> + <timescale>DAILY</timescale> + <normalizationFunction xsi:type="sigmoid" sigmoidA="30.0" sigmoidB="4.0"/> + <unit>day</unit> + </indicator> + </indicator> + </indicator> + </indicator> + </compositeIndicator> +</evaluationSettings>