sascha@3775: package de.intevation.flys.artifacts.model.extreme; sascha@3775: teichmann@4259: import org.apache.commons.logging.Log; teichmann@4259: import org.apache.commons.logging.LogFactory; teichmann@4259: teichmann@4259: import org.apache.commons.math.MathException; teichmann@4259: teichmann@4259: import org.apache.commons.math.optimization.fitting.CurveFitter; teichmann@4259: teichmann@4259: import org.apache.commons.math.optimization.general.LevenbergMarquardtOptimizer; teichmann@4259: sascha@3775: import de.intevation.flys.artifacts.access.ExtremeAccess; sascha@3775: teichmann@4259: import de.intevation.flys.artifacts.math.Utils; teichmann@4259: ingo@3785: import de.intevation.flys.artifacts.math.fitting.Function; ingo@3785: import de.intevation.flys.artifacts.math.fitting.FunctionFactory; ingo@3785: sascha@3775: import de.intevation.flys.artifacts.model.Calculation; sascha@3775: import de.intevation.flys.artifacts.model.CalculationResult; ingo@3785: import de.intevation.flys.artifacts.model.RangeWithValues; ingo@3785: import de.intevation.flys.artifacts.model.RiverFactory; teichmann@4259: import de.intevation.flys.artifacts.model.WQKms; ingo@3785: import de.intevation.flys.artifacts.model.WstValueTable; ingo@3785: import de.intevation.flys.artifacts.model.WstValueTableFactory; ingo@3785: ingo@3785: import de.intevation.flys.model.River; ingo@3785: ingo@3785: import de.intevation.flys.utils.DoubleUtil; teichmann@4259: import de.intevation.flys.utils.KMIndex; teichmann@4259: teichmann@4259: import gnu.trove.TDoubleArrayList; ingo@3785: ingo@3785: import java.util.List; sascha@3775: felix@4040: /** Calculate extrapolated W. */ sascha@3775: public class ExtremeCalculation sascha@3775: extends Calculation sascha@3775: { teichmann@4259: private static final Log log = teichmann@4259: LogFactory.getLog(ExtremeCalculation.class); teichmann@4259: ingo@3785: protected String river; ingo@3785: protected String function; ingo@3785: protected double from; ingo@3785: protected double to; ingo@3785: protected double step; ingo@3785: protected double percent; ingo@3785: protected List ranges; ingo@3785: sascha@3775: public ExtremeCalculation() { sascha@3775: } sascha@3775: sascha@3775: public ExtremeCalculation(ExtremeAccess access) { ingo@3785: String river = access.getRiver(); ingo@3785: String function = access.getFunction(); ingo@3785: Double from = access.getFrom(); ingo@3785: Double to = access.getTo(); ingo@3785: Double step = access.getStep(); ingo@3785: Double percent = access.getPercent(); ingo@3785: List ranges = access.getRanges(); ingo@3785: ingo@3785: if (river == null) { ingo@3785: // TODO: i18n ingo@3785: addProblem("extreme.no.river"); ingo@3785: } ingo@3785: ingo@3785: if (function == null) { ingo@3785: // TODO: i18n ingo@3785: addProblem("extreme.no.function"); ingo@3785: } ingo@3785: ingo@3785: if (from == null) { ingo@3785: // TODO: i18n ingo@3785: addProblem("extreme.no.from"); ingo@3785: } ingo@3785: ingo@3785: if (to == null) { ingo@3785: // TODO: i18n ingo@3785: addProblem("extreme.no.to"); ingo@3785: } ingo@3785: ingo@3785: if (step == null) { ingo@3785: // TODO: i18n ingo@3785: addProblem("extreme.no.step"); ingo@3785: } ingo@3785: ingo@3785: if (percent == null) { ingo@3785: // TODO: i18n ingo@3785: addProblem("extreme.no.percent"); ingo@3785: } ingo@3785: ingo@3785: if (ranges == null) { ingo@3785: // TODO: i18n ingo@3785: addProblem("extreme.no.ranges"); ingo@3785: } ingo@3785: ingo@3785: if (!hasProblems()) { ingo@3785: this.river = river; ingo@3785: this.function = function; ingo@3785: this.from = Math.min(from, to); ingo@3785: this.to = Math.max(from, to); ingo@3785: this.step = Math.max(0.001d, Math.abs(step)/1000d); ingo@3785: this.percent = Math.max(0d, Math.min(100d, percent)); ingo@3785: this.ranges = ranges; ingo@3785: } sascha@3775: } sascha@3775: felix@4059: felix@4059: /** Calculate an extreme curve (extrapolate). */ sascha@3775: public CalculationResult calculate() { ingo@3785: ingo@3785: WstValueTable wst = null; ingo@3785: ingo@3785: River river = RiverFactory.getRiver(this.river); ingo@3785: if (river == null) { ingo@3785: // TODO: i18n ingo@3785: addProblem("extreme.no.such.river", this.river); sascha@3775: } ingo@3785: else { ingo@3785: wst = WstValueTableFactory.getTable(river); ingo@3785: if (wst == null) { ingo@3785: // TODO: i18n ingo@3785: addProblem("extreme.no.wst.table"); ingo@3785: } ingo@3785: } ingo@3785: ingo@3785: Function function = ingo@3785: FunctionFactory.getInstance().getFunction(this.function); ingo@3785: if (function == null) { ingo@3785: // TODO: i18n ingo@3785: addProblem("extreme.no.such.function", this.function); ingo@3785: } ingo@3785: ingo@3785: return hasProblems() ingo@3785: ? new CalculationResult(this) ingo@3785: : innerCalculate(wst, function); ingo@3785: } ingo@3785: teichmann@4259: protected String wqkmsName(int i) { teichmann@4259: StringBuilder sb = new StringBuilder("W("); teichmann@4259: boolean already = false; teichmann@4259: for (RangeWithValues r: ranges) { teichmann@4259: double [] values = r.getValues(); teichmann@4259: if (i < values.length) { teichmann@4259: if (already) { teichmann@4259: sb.append(", "); teichmann@4259: } teichmann@4259: else { teichmann@4259: already = true; teichmann@4259: } teichmann@4259: // TODO: i18n teichmann@4259: sb.append(values[i]); teichmann@4259: } teichmann@4259: } teichmann@4259: return sb.append(')').toString(); teichmann@4259: } teichmann@4259: teichmann@4259: protected WQKms [] allocWQKms() { teichmann@4259: int max = 0; teichmann@4259: for (RangeWithValues r: ranges) { teichmann@4259: double [] values = r.getValues(); teichmann@4259: if (values.length > max) { teichmann@4259: max = values.length; teichmann@4259: } teichmann@4259: } teichmann@4259: WQKms [] wqkms = new WQKms[max]; teichmann@4259: for (int i = 0; i < max; ++i) { teichmann@4259: wqkms[i] = new WQKms(wqkmsName(i)); teichmann@4259: } teichmann@4259: return wqkms; teichmann@4259: } teichmann@4259: felix@4059: felix@4059: /** Calculate an extreme curve (extrapolate). */ ingo@3785: protected CalculationResult innerCalculate( ingo@3785: WstValueTable wst, ingo@3785: Function function ingo@3785: ) { ingo@3785: RangeWithValues range = null; ingo@3785: teichmann@4259: double [] chiSqr = { 0d }; teichmann@4259: teichmann@4259: KMIndex curves = new KMIndex(); teichmann@4259: WQKms [] wqkms = allocWQKms(); teichmann@4259: teichmann@4259: for (double km = from; km <= to; km += step) { ingo@3785: double currentKm = DoubleUtil.round(km); ingo@3785: ingo@3785: if (range == null || !range.inside(currentKm)) { ingo@3785: for (RangeWithValues r: ranges) { ingo@3785: if (r.inside(currentKm)) { ingo@3785: range = r; ingo@3785: break; ingo@3785: } ingo@3785: } ingo@3785: // TODO: i18n felix@4293: addProblem(currentKm, "extreme.no.range.inner"); teichmann@4259: continue; ingo@3785: } ingo@3785: ingo@3785: double [][] wqs = wst.interpolateTabulated(currentKm); ingo@3785: if (wqs == null) { ingo@3785: // TODO: i18n ingo@3785: addProblem(currentKm, "extreme.no.raw.data"); ingo@3785: continue; ingo@3785: } ingo@3785: ingo@3785: // XXX: This should not be necessary for model data. ingo@3785: if (!DoubleUtil.isValid(wqs)) { ingo@3785: // TODO: i18n ingo@3785: addProblem(currentKm, "extreme.invalid.data"); ingo@3785: continue; ingo@3785: } teichmann@4259: teichmann@4259: double [][] fitWQs = extractPointsToFit(wqs); teichmann@4259: if (fitWQs == null) { teichmann@4259: // TODO: i18n teichmann@4259: addProblem(currentKm, "extreme.too.less.points"); teichmann@4259: continue; teichmann@4259: } teichmann@4259: teichmann@4259: double [] coeffs = doFitting(function, wqs, chiSqr); teichmann@4259: if (coeffs == null) { teichmann@4259: // TODO: i18n teichmann@4259: addProblem(currentKm, "extreme.fitting.failed"); teichmann@4259: } teichmann@4259: teichmann@4259: Curve curve = new Curve( teichmann@4259: wqs[1], wqs[0], teichmann@4259: function.getName(), teichmann@4259: coeffs, teichmann@4259: chiSqr[0]); teichmann@4259: teichmann@4259: curves.add(currentKm, curve); teichmann@4259: teichmann@4259: double [] values = range.getValues(); teichmann@4259: teichmann@4259: int V = Math.min(values.length, wqkms.length); teichmann@4259: for (int i = 0; i < V; ++i) { teichmann@4259: double q = values[i]; teichmann@4259: double w = curve.value(q); teichmann@4259: if (Double.isNaN(w)) { teichmann@4259: // TODO: i18n teichmann@4259: addProblem(currentKm, "extreme.evaluate.failed", values[i]); teichmann@4259: } teichmann@4259: else { felix@4294: wqkms[i].add(w, q, currentKm); teichmann@4259: } teichmann@4259: } ingo@3785: } ingo@3785: teichmann@4259: ExtremeResult result = new ExtremeResult(curves, wqkms); sascha@3775: return new CalculationResult(result, this); sascha@3775: } teichmann@4259: teichmann@4259: protected static double [] doFitting( teichmann@4259: Function function, teichmann@4259: double [][] wqs, teichmann@4259: double [] chiSqr teichmann@4259: ) { teichmann@4259: LevenbergMarquardtOptimizer lmo = null; teichmann@4259: teichmann@4259: double [] coeffs = null; teichmann@4259: teichmann@4259: double [] ws = wqs[0]; teichmann@4259: double [] qs = wqs[1]; teichmann@4259: teichmann@4259: for (double tolerance = 1e-10; tolerance < 1e-3; tolerance *= 10d) { teichmann@4259: lmo = new LevenbergMarquardtOptimizer(); teichmann@4259: lmo.setCostRelativeTolerance(tolerance); teichmann@4259: lmo.setOrthoTolerance(tolerance); teichmann@4259: lmo.setParRelativeTolerance(tolerance); teichmann@4259: teichmann@4259: CurveFitter cf = new CurveFitter(lmo); teichmann@4259: teichmann@4259: for (int i = ws.length-1; i >= 0; --i) { teichmann@4259: cf.addObservedPoint(qs[i], ws[i]); teichmann@4259: } teichmann@4259: teichmann@4259: try { teichmann@4259: coeffs = cf.fit(function, function.getInitialGuess()); teichmann@4259: break; teichmann@4259: } teichmann@4259: catch (MathException me) { teichmann@4259: if (log.isDebugEnabled()) { teichmann@4259: log.debug("tolerance " + tolerance + " + failed."); teichmann@4259: } teichmann@4259: } teichmann@4259: } teichmann@4259: if (coeffs == null) { teichmann@4259: return null; teichmann@4259: } teichmann@4259: chiSqr[0] = lmo.getChiSquare(); teichmann@4259: return coeffs; teichmann@4259: } teichmann@4259: teichmann@4259: protected static double [][] extractPointsToFit(double [][] wqs) { teichmann@4259: TDoubleArrayList ows = new TDoubleArrayList(); teichmann@4259: TDoubleArrayList oqs = new TDoubleArrayList(); teichmann@4259: teichmann@4259: double [] ws = wqs[0]; teichmann@4259: double [] qs = wqs[1]; teichmann@4259: teichmann@4259: int N = Math.min(ws.length, qs.length); teichmann@4259: teichmann@4259: if (N < 2) { teichmann@4259: log.warn("Too less points for fitting"); teichmann@4259: return null; teichmann@4259: } teichmann@4259: teichmann@4259: double q2 = qs[N-1]; teichmann@4259: double w2 = ws[N-1]; teichmann@4259: double q1 = qs[N-2]; teichmann@4259: double w1 = ws[N-2]; teichmann@4259: teichmann@4259: oqs.add(q2); oqs.add(q1); teichmann@4259: ows.add(w2); ows.add(w1); teichmann@4259: teichmann@4259: int orientation = Utils.epsilonEquals(w1, w1) teichmann@4259: ? 0 teichmann@4259: : w1 > w2 teichmann@4259: ? -1 teichmann@4259: : +1; teichmann@4259: teichmann@4259: for (int i = N-3; i >= 0; --i) { teichmann@4259: double cq = qs[i]; teichmann@4259: double cw = qs[i]; teichmann@4259: int newOrientation = Utils.relativeCCW( teichmann@4259: q2, w2, teichmann@4259: q1, w1, teichmann@4259: cq, cw); teichmann@4259: teichmann@4259: if (newOrientation != 0 && newOrientation != orientation) { teichmann@4259: break; teichmann@4259: } teichmann@4259: oqs.add(cq); teichmann@4259: ows.add(cw); teichmann@4259: teichmann@4259: if (newOrientation != 0) { teichmann@4259: // rotate last out teichmann@4259: q2 = q1; teichmann@4259: w2 = w1; teichmann@4259: } teichmann@4259: q1 = cq; teichmann@4259: w1 = cw; teichmann@4259: } teichmann@4259: teichmann@4259: // XXX: Not really needed for fitting. teichmann@4259: // oqs.reverse(); teichmann@4259: // ows.reverse(); teichmann@4259: teichmann@4259: return new double [][] { teichmann@4259: ows.toNativeArray(), teichmann@4259: oqs.toNativeArray() teichmann@4259: }; teichmann@4259: } sascha@3775: } sascha@3775: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :