View Javadoc
1   /**
2    * This file is part of Indicators.
3    *
4    * Indicators is free software: you can redistribute it and/or modify
5    * it under the terms of the GNU General Public License as published by
6    * the Free Software Foundation, either version 3 of the License, or
7    * (at your option) any later version.
8    *
9    * Indicators is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with Indicators. If not, see <https://www.gnu.org/licenses/>.
16   */
17  package fr.inrae.agroclim.indicators.model.indicator;
18  
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.HashSet;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Objects;
25  import java.util.Optional;
26  import java.util.Set;
27  
28  import fr.inrae.agroclim.indicators.exception.IndicatorsException;
29  import fr.inrae.agroclim.indicators.model.Knowledge;
30  import fr.inrae.agroclim.indicators.model.Parameter;
31  import fr.inrae.agroclim.indicators.model.data.DailyData;
32  import fr.inrae.agroclim.indicators.model.data.Resource;
33  import fr.inrae.agroclim.indicators.model.data.Variable;
34  import fr.inrae.agroclim.indicators.model.data.climate.ClimaticDailyData;
35  import fr.inrae.agroclim.indicators.model.data.climate.ClimaticResource;
36  import fr.inrae.agroclim.indicators.util.Doublet;
37  import jakarta.xml.bind.annotation.XmlType;
38  import lombok.Getter;
39  import lombok.Setter;
40  import lombok.extern.log4j.Log4j2;
41  
42  /**
43   * % days matching sowing condition/period.
44   *
45   * Fréquence de jours où le semis est possible durant sow_period selon des
46   * conditions sur la température et la teneur en eau du sol après le semis
47   * (faisabilité écophysiologique) et selon la teneur en eau du sol le jour du
48   * semis (faisabilité technique du semis).
49   *
50   * @author jcufi
51   */
52  @XmlType(propOrder = {"nbDays", "rainThreshold",
53          "soilWaterContentAtFieldCapacity", "soilWaterContentThreshold",
54          "tmeanThreshold", "tminThreshold"})
55  @Log4j2
56  public final class PotentialSowingDaysFrequency extends SimpleIndicator implements Detailable {
57  
58      /**
59       * UUID for Serializable.
60       */
61      private static final long serialVersionUID = 6030595237342422013L;
62  
63      /**
64       * Number of consecutive days after sowing during which conditions are
65       * observed for sowing decision.
66       */
67      @Getter
68      @Setter
69      private int nbDays;
70  
71      /**
72       * Maximal rain sum for emergence.
73       */
74      @Getter
75      @Setter
76      private double rainThreshold;
77  
78      /**
79       * Maximal soil water content for emergence.
80       */
81      @Getter
82      @Setter
83      private double soilWaterContentAtFieldCapacity;
84  
85      /**
86       * Minimum soil water content for emergence.
87       */
88      @Getter
89      @Setter
90      private double soilWaterContentThreshold;
91  
92      /**
93       * Mean temperature for sowing (°C).
94       */
95      @Getter
96      @Setter
97      private double tmeanThreshold;
98  
99      /**
100      * Minimal temperature for sowing (°C).
101      */
102     @Getter
103     @Setter
104     private double tminThreshold;
105 
106     /**
107      * Constructor.
108      */
109     public PotentialSowingDaysFrequency() {
110         super();
111     }
112 
113     /**
114      * @param index index of first day
115      * @param res climatic data
116      * @return sum of rain > rainThreshold
117      */
118     private boolean checkSumOfRain(final int index,
119             final ClimaticResource res) {
120         final List<ClimaticDailyData> data = res.getData();
121         double sum = 0;
122         for (int i = index; i <= index + nbDays; i++) {
123             sum += data.get(i).getValue(Variable.RAIN);
124         }
125         return sum >= rainThreshold;
126     }
127 
128     /**
129      * @param index index of first day
130      * @param res climatic data
131      * @return tmin <= tminThreshold && tmean <= tmeanThreshold
132      */
133     private boolean checkTminTmoy(final int index, final ClimaticResource res) {
134         final List<ClimaticDailyData> data = res.getData();
135         for (int i = index; i <= index + nbDays; i++) {
136             final double tmin = data.get(i).getValue(Variable.TMIN);
137             final double tmean = data.get(i).getValue(Variable.TMEAN);
138             if (!(tmin > tminThreshold && tmean > tmeanThreshold)) {
139                 return false;
140             }
141         }
142         return true;
143     }
144 
145     @Override
146     public PotentialSowingDaysFrequency clone()
147             throws CloneNotSupportedException {
148         PotentialSowingDaysFrequency clone;
149         clone = (PotentialSowingDaysFrequency) super.clone();
150         clone.soilWaterContentThreshold = soilWaterContentThreshold;
151         clone.soilWaterContentAtFieldCapacity = soilWaterContentAtFieldCapacity;
152         clone.nbDays = nbDays;
153         clone.tmeanThreshold = tmeanThreshold;
154         clone.tminThreshold = tminThreshold;
155         clone.rainThreshold = rainThreshold;
156         return clone;
157     }
158 
159     @Override
160     public double computeSingleValue(final Resource<? extends DailyData> resource) throws IndicatorsException {
161         Objects.requireNonNull(resource, "Resource must not be null!");
162         if (!(resource instanceof ClimaticResource)) {
163             throw new IllegalArgumentException(
164                     getClass().getName()
165                     + ".computeSingleValue() handles "
166                     + "only ClimaticResource, not "
167                     + resource.getClass().getName());
168         }
169         final ClimaticResource res = (ClimaticResource) resource;
170         int cpt = 0;
171         final List<ClimaticDailyData> data = res.getData();
172         if (data.size() >= nbDays) {
173             for (int index = 0; index < res.getData().size(); index++) {
174                 final double soilWaterContent = data.get(index)
175                         .getValue(Variable.SOILWATERCONTENT);
176                 if (soilWaterContent >= soilWaterContentThreshold
177                         && soilWaterContent < soilWaterContentAtFieldCapacity
178                         && index + nbDays < data.size()
179                         && checkTminTmoy(index, res)
180                         && checkSumOfRain(index, res)) {
181                     cpt += 1;
182                 }
183             }
184         }
185         final double percent = 100.;
186         return cpt * percent / data.size();
187     }
188 
189     private Map<String, Number> getAttributeValues() {
190         return Map.of(//
191                 "soilWaterContentThreshold", soilWaterContentThreshold, //
192                 "nbDays", nbDays, //
193                 "rainThreshold", rainThreshold, //
194                 "soilWaterContentAtFieldCapacity", soilWaterContentAtFieldCapacity, //
195                 "tmeanThreshold", tmeanThreshold, //
196                 "tminThreshold", tminThreshold
197         );
198     }
199 
200     @Override
201     public List<Doublet<Parameter, Number>> getParameterDefaults() {
202         if (getParameters() == null) {
203             return List.of();
204         }
205         final List<Doublet<Parameter, Number>> val = new ArrayList<>();
206         getAttributeValues().forEach((k, v) -> {
207             final Optional<Parameter> found = getParameters().stream() //
208                 .filter(a -> k.equals(a.getAttribute())) //
209                 .findFirst();
210             if (found.isPresent()) {
211                 val.add(Doublet.of(found.get(), v));
212             }
213         });
214         return val;
215     }
216 
217     /**
218      * @return parameters (id and attribute) for the indicator.
219      */
220     @Override
221     public List<Parameter> getParameters() {
222         if (super.getParameters() != null) {
223             return super.getParameters();
224         } else {
225             return List.of();
226         }
227     }
228 
229     @Override
230     public Map<String, Double> getParametersValues() {
231         final Map<String, Double> val = new HashMap<>();
232         // if no substitution is defined
233         if (getParameters() == null) {
234             return val;
235         }
236         final Map<String, Number> attributeValues = getAttributeValues();
237         for (final Parameter param : getParameters()) {
238             if (param == null) {
239                 continue;
240             }
241             final Number value = attributeValues.get(param.getId());
242             if (value != null) {
243                 val.put(param.getId(), value.doubleValue());
244             }
245         }
246         return val;
247     }
248 
249     @Override
250     public Set<Variable> getVariables() {
251         final Set<Variable> variables = new HashSet<>();
252         variables.add(Variable.RAIN);
253         variables.add(Variable.SOILWATERCONTENT);
254         variables.add(Variable.TMEAN);
255         variables.add(Variable.TMIN);
256         return variables;
257     }
258 
259     @Override
260     public boolean isComputable(final Resource<? extends DailyData> resource) {
261         if (resource == null) {
262             throw new IllegalArgumentException("resource must no be null!");
263         }
264         if (!(resource instanceof ClimaticResource)) {
265             throw new IllegalArgumentException(getClass().getName()
266                     + " handles only ClimaticResource, not "
267                     + resource.getClass().getName());
268         }
269         return true;
270     }
271 
272     @Override
273     public void setParametersFromKnowledge(final Knowledge knowledge) {
274         final Indicator indicator = knowledge.getIndicator(getId());
275         setParameters(indicator.getParameters());
276     }
277 
278     @Override
279     public void setParametersValues(final Map<String, Double> values) {
280         // if no substitution is defined
281         if (getParameters() == null) {
282             return;
283         }
284         getParameters().forEach(param -> {
285             final String id = param.getId();
286             if (values.containsKey(id)) {
287                 switch (param.getAttribute()) {
288                 case "soilWaterContentThreshold":
289                     soilWaterContentThreshold = values.get(id);
290                     break;
291                 case "soilWaterContentAtFieldCapacity":
292                     soilWaterContentAtFieldCapacity = values.get(id);
293                     break;
294                 case "nbDays":
295                     if (values.get(id) != null) {
296                         nbDays = values.get(id).intValue();
297                     }
298                     break;
299                 case "tmeanThreshold":
300                     tmeanThreshold = values.get(id);
301                     break;
302                 case "tminThreshold":
303                     tminThreshold = values.get(id);
304                     break;
305                 case "rainThreshold":
306                     rainThreshold = values.get(id);
307                     break;
308                 default:
309                     break;
310                 }
311             }
312         });
313     }
314 
315     @Override
316     public String toStringTree(final String indent) {
317         return toStringTreeBase(indent);
318     }
319 
320     @Override
321     public void removeParameter(final Parameter param) {
322         if (getParameters() != null) {
323             getParameters().remove(param);
324         }
325     }
326 
327 }