Mercurial > dive4elements > river
comparison flys-artifacts/src/main/java/org/dive4elements/river/artifacts/model/extreme/ExtremeCalculation.java @ 5831:bd047b71ab37
Repaired internal references
author | Sascha L. Teichmann <teichmann@intevation.de> |
---|---|
date | Thu, 25 Apr 2013 12:06:39 +0200 |
parents | flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/extreme/ExtremeCalculation.java@5b8919ef601d |
children |
comparison
equal
deleted
inserted
replaced
5830:160f53ee0870 | 5831:bd047b71ab37 |
---|---|
1 package org.dive4elements.river.artifacts.model.extreme; | |
2 | |
3 import org.apache.commons.logging.Log; | |
4 import org.apache.commons.logging.LogFactory; | |
5 | |
6 import org.apache.commons.math.MathException; | |
7 | |
8 import org.apache.commons.math.optimization.fitting.CurveFitter; | |
9 | |
10 import org.apache.commons.math.optimization.general.LevenbergMarquardtOptimizer; | |
11 | |
12 import org.dive4elements.river.artifacts.access.ExtremeAccess; | |
13 | |
14 import org.dive4elements.river.artifacts.math.Linear; | |
15 //import org.dive4elements.river.artifacts.math.Utils; | |
16 | |
17 import org.dive4elements.river.artifacts.math.fitting.Function; | |
18 import org.dive4elements.river.artifacts.math.fitting.FunctionFactory; | |
19 | |
20 import org.dive4elements.river.artifacts.model.Calculation; | |
21 import org.dive4elements.river.artifacts.model.CalculationResult; | |
22 import org.dive4elements.river.artifacts.model.RangeWithValues; | |
23 import org.dive4elements.river.artifacts.model.RiverFactory; | |
24 import org.dive4elements.river.artifacts.model.WQKms; | |
25 import org.dive4elements.river.artifacts.model.WstValueTable; | |
26 import org.dive4elements.river.artifacts.model.WstValueTableFactory; | |
27 | |
28 import org.dive4elements.river.model.River; | |
29 | |
30 import org.dive4elements.river.utils.DoubleUtil; | |
31 import org.dive4elements.river.utils.KMIndex; | |
32 | |
33 import gnu.trove.TDoubleArrayList; | |
34 | |
35 import java.util.List; | |
36 | |
37 import java.awt.geom.Line2D; | |
38 | |
39 /** Calculate extrapolated W. */ | |
40 public class ExtremeCalculation | |
41 extends Calculation | |
42 { | |
43 private static final Log log = | |
44 LogFactory.getLog(ExtremeCalculation.class); | |
45 | |
46 protected String river; | |
47 protected String function; | |
48 protected double from; | |
49 protected double to; | |
50 protected double step; | |
51 protected double percent; | |
52 protected List<RangeWithValues> ranges; | |
53 | |
54 public ExtremeCalculation() { | |
55 } | |
56 | |
57 public ExtremeCalculation(ExtremeAccess access) { | |
58 String river = access.getRiver(); | |
59 String function = access.getFunction(); | |
60 Double from = access.getFrom(); | |
61 Double to = access.getTo(); | |
62 Double step = access.getStep(); | |
63 Double percent = access.getPercent(); | |
64 List<RangeWithValues> ranges = access.getRanges(); | |
65 | |
66 if (river == null) { | |
67 // TODO: i18n | |
68 addProblem("extreme.no.river"); | |
69 } | |
70 | |
71 if (function == null) { | |
72 // TODO: i18n | |
73 addProblem("extreme.no.function"); | |
74 } | |
75 | |
76 if (from == null) { | |
77 // TODO: i18n | |
78 addProblem("extreme.no.from"); | |
79 } | |
80 | |
81 if (to == null) { | |
82 // TODO: i18n | |
83 addProblem("extreme.no.to"); | |
84 } | |
85 | |
86 if (step == null) { | |
87 // TODO: i18n | |
88 addProblem("extreme.no.step"); | |
89 } | |
90 | |
91 if (percent == null) { | |
92 // TODO: i18n | |
93 addProblem("extreme.no.percent"); | |
94 } | |
95 | |
96 if (ranges == null) { | |
97 // TODO: i18n | |
98 addProblem("extreme.no.ranges"); | |
99 } | |
100 | |
101 if (!hasProblems()) { | |
102 this.river = river; | |
103 this.function = function; | |
104 this.from = Math.min(from, to); | |
105 this.to = Math.max(from, to); | |
106 this.step = Math.max(0.001d, Math.abs(step)/1000d); | |
107 this.percent = Math.max(0d, Math.min(100d, percent)); | |
108 this.ranges = ranges; | |
109 } | |
110 } | |
111 | |
112 | |
113 /** Calculate an extreme curve (extrapolate). */ | |
114 public CalculationResult calculate() { | |
115 | |
116 WstValueTable wst = null; | |
117 | |
118 River river = RiverFactory.getRiver(this.river); | |
119 if (river == null) { | |
120 // TODO: i18n | |
121 addProblem("extreme.no.such.river", this.river); | |
122 } | |
123 else { | |
124 wst = WstValueTableFactory.getTable(river); | |
125 if (wst == null) { | |
126 // TODO: i18n | |
127 addProblem("extreme.no.wst.table"); | |
128 } | |
129 } | |
130 | |
131 Function function = | |
132 FunctionFactory.getInstance().getFunction(this.function); | |
133 if (function == null) { | |
134 // TODO: i18n | |
135 addProblem("extreme.no.such.function", this.function); | |
136 } | |
137 | |
138 return hasProblems() | |
139 ? new CalculationResult(this) | |
140 : innerCalculate(wst, function); | |
141 } | |
142 | |
143 | |
144 /** Name of wqkms like W(5000,6000) */ | |
145 protected String wqkmsName(int i) { | |
146 StringBuilder sb = new StringBuilder("W("); | |
147 boolean already = false; | |
148 for (RangeWithValues r: ranges) { | |
149 double [] values = r.getValues(); | |
150 if (i < values.length) { | |
151 if (already) { | |
152 sb.append(", "); | |
153 } | |
154 else { | |
155 already = true; | |
156 } | |
157 // TODO: i18n | |
158 sb.append(values[i]); | |
159 } | |
160 } | |
161 return sb.append(')').toString(); | |
162 } | |
163 | |
164 protected WQKms [] allocWQKms() { | |
165 int max = 0; | |
166 for (RangeWithValues r: ranges) { | |
167 double [] values = r.getValues(); | |
168 if (values.length > max) { | |
169 max = values.length; | |
170 } | |
171 } | |
172 WQKms [] wqkms = new WQKms[max]; | |
173 for (int i = 0; i < max; ++i) { | |
174 wqkms[i] = new WQKms(wqkmsName(i)); | |
175 } | |
176 return wqkms; | |
177 } | |
178 | |
179 | |
180 /** Calculate an extreme curve (extrapolate). */ | |
181 protected CalculationResult innerCalculate( | |
182 WstValueTable wst, | |
183 Function function | |
184 ) { | |
185 RangeWithValues range = null; | |
186 | |
187 double [] chiSqr = { 0d }; | |
188 | |
189 KMIndex<Curve> curves = new KMIndex<Curve>(); | |
190 WQKms [] wqkms = allocWQKms(); | |
191 | |
192 boolean debug = log.isDebugEnabled(); | |
193 | |
194 from = DoubleUtil.round(from); | |
195 to = DoubleUtil.round(to); | |
196 | |
197 for (double km = from; km <= to; km = DoubleUtil.round(km+step)) { | |
198 | |
199 if (debug) { | |
200 log.debug("km: " + km); | |
201 } | |
202 | |
203 boolean foundRange = false; | |
204 | |
205 if (range == null || !range.inside(km)) { | |
206 for (RangeWithValues r: ranges) { | |
207 if (r.inside(km)) { | |
208 range = r; | |
209 foundRange = true; | |
210 break; | |
211 } | |
212 } | |
213 // TODO: i18n | |
214 if (!foundRange) { | |
215 addProblem(km, "extreme.no.range.inner"); | |
216 continue; | |
217 } | |
218 } | |
219 | |
220 double [][] wqs = wst.interpolateTabulated(km); | |
221 if (wqs == null) { | |
222 // TODO: i18n | |
223 addProblem(km, "extreme.no.raw.data"); | |
224 continue; | |
225 } | |
226 | |
227 // XXX: This should not be necessary for model data. | |
228 if (!DoubleUtil.isValid(wqs)) { | |
229 // TODO: i18n | |
230 addProblem(km, "extreme.invalid.data"); | |
231 continue; | |
232 } | |
233 | |
234 double [][] fitWQs = extractPointsToFit(wqs); | |
235 if (fitWQs == null) { | |
236 // TODO: i18n | |
237 addProblem(km, "extreme.too.less.points"); | |
238 continue; | |
239 } | |
240 | |
241 double [] coeffs = doFitting(function, fitWQs, chiSqr); | |
242 if (coeffs == null) { | |
243 // TODO: i18n | |
244 addProblem(km, "extreme.fitting.failed"); | |
245 continue; | |
246 } | |
247 | |
248 Curve curve = new Curve( | |
249 wqs[1], wqs[0], | |
250 function.getName(), | |
251 coeffs, | |
252 chiSqr[0]); | |
253 | |
254 curves.add(km, curve); | |
255 | |
256 double [] values = range.getValues(); | |
257 | |
258 int V = Math.min(values.length, wqkms.length); | |
259 for (int i = 0; i < V; ++i) { | |
260 double q = values[i]; | |
261 double w = curve.value(q); | |
262 if (Double.isNaN(w)) { | |
263 // TODO: i18n | |
264 addProblem(km, "extreme.evaluate.failed", values[i]); | |
265 } | |
266 else { | |
267 wqkms[i].add(w, q, km); | |
268 } | |
269 } | |
270 } | |
271 | |
272 ExtremeResult result = new ExtremeResult(curves, wqkms); | |
273 return new CalculationResult(result, this); | |
274 } | |
275 | |
276 protected double [] doFitting( | |
277 Function function, | |
278 double [][] wqs, | |
279 double [] chiSqr | |
280 ) { | |
281 LevenbergMarquardtOptimizer lmo = null; | |
282 | |
283 double [] coeffs = null; | |
284 | |
285 double [] ws = wqs[0]; | |
286 double [] qs = wqs[1]; | |
287 | |
288 for (double tolerance = 1e-10; tolerance < 1e-3; tolerance *= 10d) { | |
289 lmo = new LevenbergMarquardtOptimizer(); | |
290 lmo.setCostRelativeTolerance(tolerance); | |
291 lmo.setOrthoTolerance(tolerance); | |
292 lmo.setParRelativeTolerance(tolerance); | |
293 | |
294 CurveFitter cf = new CurveFitter(lmo); | |
295 | |
296 for (int i = 0; i < ws.length; ++i) { | |
297 cf.addObservedPoint(qs[i], ws[i]); | |
298 } | |
299 | |
300 try { | |
301 coeffs = cf.fit(function, function.getInitialGuess()); | |
302 break; | |
303 } | |
304 catch (MathException me) { | |
305 if (log.isDebugEnabled()) { | |
306 log.debug("tolerance " + tolerance + " + failed."); | |
307 } | |
308 } | |
309 } | |
310 if (coeffs != null) { | |
311 chiSqr[0] = lmo.getChiSquare(); | |
312 } | |
313 return coeffs; | |
314 } | |
315 | |
316 protected double [][] extractPointsToFit(double [][] wqs) { | |
317 | |
318 double [] ws = wqs[0]; | |
319 double [] qs = wqs[1]; | |
320 | |
321 int N = Math.min(ws.length, qs.length); | |
322 | |
323 if (N < 2) { | |
324 log.warn("Too less points for fitting"); | |
325 return null; | |
326 } | |
327 | |
328 double q2 = qs[N-1]; | |
329 double w2 = ws[N-1]; | |
330 double q1 = qs[N-2]; | |
331 double w1 = ws[N-2]; | |
332 | |
333 boolean ascending = w2 > w1; | |
334 | |
335 TDoubleArrayList ows = new TDoubleArrayList(); | |
336 TDoubleArrayList oqs = new TDoubleArrayList(); | |
337 | |
338 oqs.add(q2); oqs.add(q1); | |
339 ows.add(w2); ows.add(w1); | |
340 | |
341 int lastDir = -2; | |
342 | |
343 for (int i = N-3; i >= 0; --i) { | |
344 double q = qs[i]; | |
345 double w = ws[i]; | |
346 | |
347 if ((ascending && w > w1) || (!ascending && w < w1)) { | |
348 break; | |
349 } | |
350 | |
351 int dir = Line2D.relativeCCW(q2, w2, q1, w1, q, w); | |
352 //int dir = Utils.relativeCCW(q2, w2, q1, w1, q, w); | |
353 if (lastDir == -2) { | |
354 lastDir = dir; | |
355 } | |
356 else if (lastDir != dir) { | |
357 break; | |
358 } | |
359 | |
360 oqs.add(q); | |
361 ows.add(w); | |
362 w2 = w1; | |
363 q2 = q1; | |
364 w1 = w; | |
365 q1 = q; | |
366 } | |
367 | |
368 oqs.reverse(); | |
369 ows.reverse(); | |
370 | |
371 boolean debug = log.isDebugEnabled(); | |
372 if (debug) { | |
373 log.debug("from table: " + N); | |
374 log.debug("after trim: " + oqs.size()); | |
375 } | |
376 | |
377 cutPercent(ows, oqs); | |
378 | |
379 if (debug) { | |
380 log.debug("after percent cut: " + oqs.size()); | |
381 } | |
382 | |
383 return new double [][] { | |
384 ows.toNativeArray(), | |
385 oqs.toNativeArray() | |
386 }; | |
387 } | |
388 | |
389 | |
390 protected void cutPercent(TDoubleArrayList ws, TDoubleArrayList qs) { | |
391 int N = qs.size(); | |
392 if (percent <= 0d || N == 0) { | |
393 return; | |
394 } | |
395 | |
396 double minQ = qs.getQuick(0); | |
397 double maxQ = qs.getQuick(N-1); | |
398 double factor = Math.min(Math.max(0d, percent/100d), 1d); | |
399 double cutQ = Linear.weight(factor, minQ, maxQ); | |
400 int cutIndex = 0; | |
401 for (; cutIndex < N; ++cutIndex) { | |
402 double q = qs.getQuick(cutIndex); | |
403 if (minQ < maxQ) { | |
404 if (q > cutQ) { | |
405 break; | |
406 } | |
407 } | |
408 else { | |
409 if (q < cutQ) { | |
410 break; | |
411 } | |
412 } | |
413 } | |
414 ws.remove(0, cutIndex); | |
415 qs.remove(0, cutIndex); | |
416 } | |
417 } | |
418 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 : |