1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package fr.inrae.agroclim.indicators.model.indicator;
20
21 import java.io.IOException;
22 import java.io.ObjectInputStream;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Objects;
28 import java.util.Set;
29 import java.util.function.Function;
30 import java.util.stream.DoubleStream;
31
32 import fr.inrae.agroclim.indicators.exception.IndicatorsException;
33 import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
34 import fr.inrae.agroclim.indicators.exception.type.ResourceErrorType;
35 import fr.inrae.agroclim.indicators.model.ExpressionParameter;
36 import fr.inrae.agroclim.indicators.model.JEXLFormula;
37 import fr.inrae.agroclim.indicators.model.Knowledge;
38 import fr.inrae.agroclim.indicators.model.Parameter;
39 import fr.inrae.agroclim.indicators.model.data.DailyData;
40 import fr.inrae.agroclim.indicators.model.data.Resource;
41 import fr.inrae.agroclim.indicators.model.data.Variable;
42 import fr.inrae.agroclim.indicators.util.Doublet;
43 import jakarta.xml.bind.annotation.XmlElement;
44 import jakarta.xml.bind.annotation.XmlType;
45 import lombok.EqualsAndHashCode;
46 import lombok.Getter;
47 import lombok.RequiredArgsConstructor;
48 import lombok.Setter;
49
50
51
52
53
54
55
56
57
58 @XmlType(propOrder = {"aggregation", "expression", "expressionParameters"})
59 @EqualsAndHashCode(
60 callSuper = false,
61 of = {"aggregation", "expression", "expressionParameters"}
62 )
63 public final class Formula extends SimpleIndicator implements Detailable {
64
65
66
67
68 @RequiredArgsConstructor
69 public enum Aggregation {
70
71
72
73 AVERAGE(s -> s.average().orElse(0)),
74
75
76
77 COUNT(s -> Double.valueOf(s.count())),
78
79
80
81 MAX(s -> s.max().orElse(0)),
82
83
84
85 MIN(s -> Double.valueOf(s.count())),
86
87
88
89 SUM(s -> s.sum());
90
91
92
93 @Getter
94 private final Function<DoubleStream, Double> function;
95 }
96
97
98
99
100 private static final long serialVersionUID = 6030595237342422020L;
101
102
103
104
105 @Getter
106 @Setter
107 private Aggregation aggregation;
108
109
110
111
112 @Getter
113 @Setter
114 private String expression;
115
116
117
118
119 @Getter
120 @Setter
121 @XmlElement(name = "expressionParameter")
122 private List<ExpressionParameter> expressionParameters;
123
124
125
126
127 private transient JEXLFormula formula = new JEXLFormula();
128
129
130
131
132 @Setter
133 @Getter
134 private transient Map<String, Double> parametersValues = new HashMap<>();
135
136 @Override
137 public double computeSingleValue(final Resource<? extends DailyData> res) throws IndicatorsException {
138 if (aggregation == null) {
139 throw new IndicatorsException(ComputationErrorType.FORMULA_AGGREGATION_NULL);
140 }
141 Objects.requireNonNull(res, "Resource must not be null!");
142 if (res.getData() == null) {
143 throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY);
144 }
145 final Set<Variable> dataVariables = getVariables();
146 final List<Double> computedValues = new ArrayList<>();
147 for (final DailyData data : res.getData()) {
148 final Map<String, Double> values = new HashMap<>();
149 dataVariables.forEach(variable -> values.put(variable.name(), data.getValue(variable)));
150 if (parametersValues != null) {
151 values.putAll(parametersValues);
152 }
153 if (expressionParameters != null) {
154 expressionParameters.forEach(p -> values.put(p.getName(), p.getValue()));
155 }
156 computedValues.add(formula.evaluate(values, Double.class));
157 }
158 final DoubleStream stream = computedValues.stream().mapToDouble(Double::doubleValue);
159 return aggregation.getFunction().apply(stream);
160 }
161
162 @Override
163 public List<Doublet<Parameter, Number>> getParameterDefaults() {
164
165 return List.of();
166 }
167
168 @Override
169 public List<Parameter> getParameters() {
170 final List<Parameter> params = new ArrayList<>();
171 if (super.getParameters() != null) {
172 params.addAll(super.getParameters());
173 }
174 if (parametersValues != null) {
175 parametersValues.forEach((paramId, paramValue) -> {
176 final Parameter param = new Parameter();
177 param.setId(paramId);
178 param.setAttribute(paramId);
179 params.add(param);
180 });
181 }
182 return params;
183 }
184
185 @Override
186 public Set<Variable> getVariables() {
187 Objects.requireNonNull(expression, "expression must not be null!");
188 formula.setExpression(expression);
189 return formula.getDataVariables();
190 }
191
192 @Override
193 public boolean isComputable(final Resource<? extends DailyData> res) {
194 if (res == null) {
195 throw new IllegalArgumentException("resource must no be null!");
196 }
197 formula.setExpression(expression);
198 return formula.isValid();
199 }
200
201
202
203
204
205
206
207
208
209
210
211 private void readObject(final ObjectInputStream ois) throws ClassNotFoundException, IOException {
212
213 ois.defaultReadObject();
214 formula = new JEXLFormula();
215 }
216
217 @Override
218 public void setParametersFromKnowledge(final Knowledge knowledge) {
219 final Indicator indicator = knowledge.getIndicator(getId());
220 setParameters(indicator.getParameters());
221 }
222
223 @Override
224 public String toStringTree(final String indent) {
225 final StringBuilder sb = new StringBuilder();
226 sb.append(toStringTreeBase(indent));
227 sb.append(indent).append(" expression: ").append(expression).append("\n");
228 return sb.toString();
229 }
230
231 @Override
232 public void removeParameter(final Parameter param) {
233 if (parametersValues != null) {
234 parametersValues.remove(param.getId());
235 }
236 }
237
238 }