view artifacts/src/main/java/org/dive4elements/river/artifacts/model/fixings/FixCalculation.java @ 9099:850ce16034e9

2.3.4.1.10 Berechnung mit Start-km > End-km
author gernotbelger
date Mon, 28 May 2018 13:22:45 +0200
parents 6650485c2c9b
children 202fd59b4f21
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU AGPL (>=v3)
 * and comes with ABSOLUTELY NO WARRANTY! Check out the
 * documentation coming with Dive4Elements River for details.
 */

package org.dive4elements.river.artifacts.model.fixings;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.dive4elements.artifacts.common.utils.StringUtils;
import org.dive4elements.river.artifacts.access.FixAccess;
import org.dive4elements.river.artifacts.math.fitting.Function;
import org.dive4elements.river.artifacts.math.fitting.FunctionFactory;
import org.dive4elements.river.artifacts.model.Calculation;
import org.dive4elements.river.artifacts.model.CalculationResult;
import org.dive4elements.river.artifacts.model.FixingsColumn;
import org.dive4elements.river.artifacts.model.FixingsColumnFactory;
import org.dive4elements.river.artifacts.model.FixingsOverview;
import org.dive4elements.river.artifacts.model.FixingsOverview.Fixing;
import org.dive4elements.river.artifacts.model.FixingsOverview.Fixing.Filter;
import org.dive4elements.river.artifacts.model.FixingsOverview.IdsFilter;
import org.dive4elements.river.artifacts.model.FixingsOverviewFactory;
import org.dive4elements.river.artifacts.model.Parameters;
import org.dive4elements.river.utils.DoubleUtil;
import org.dive4elements.river.utils.KMIndex;

/** Calculation base class for fix. */
public abstract class FixCalculation extends Calculation {
    private static Logger log = Logger.getLogger(FixCalculation.class);

    public static final double EPSILON = 1e-4;

    public static final String[] STANDARD_COLUMNS = { "km", "chi_sqr", "max_q", "std-dev" };

    protected static class FitResult {

        protected Parameters parameters;
        protected KMIndex<QWD[]> referenced;
        protected KMIndex<QWI[]> outliers;

        public FitResult() {
        }

        public FitResult(final Parameters parameters, final KMIndex<QWD[]> referenced, final KMIndex<QWI[]> outliers) {
            this.parameters = parameters;
            this.referenced = referenced;
            this.outliers = outliers;
        }

        public Parameters getParameters() {
            return this.parameters;
        }

        public KMIndex<QWD[]> getReferenced() {
            return this.referenced;
        }

        public KMIndex<QWI[]> getOutliers() {
            return this.outliers;
        }
    } // class FitResult

    /**
     * Helper class to bundle the meta information of a column
     * and the real data.
     */
    protected static class Column {

        protected Fixing.Column meta;
        protected FixingsColumn data;
        protected int index;

        public Column() {
        }

        public Column(final Fixing.Column meta, final FixingsColumn data, final int index) {
            this.meta = meta;
            this.data = data;
            this.index = index;
        }

        public Date getDate() {
            return this.meta.getStartTime();
        }

        public String getDescription() {
            return this.meta.getDescription();
        }

        public int getIndex() {
            return this.index;
        }

        public int getId() {
            return this.meta.getId();
        }

        public boolean getQW(final double km, final double[] qs, final double[] ws, final int index) {
            qs[index] = this.data.getQ(km);
            return this.data.getW(km, ws, index);
        }

        public boolean getQW(final double km, final double[] wq) {
            this.data.getW(km, wq, 0);
            if (Double.isNaN(wq[0]))
                return false;
            wq[1] = this.data.getQ(km);
            return !Double.isNaN(wq[1]);
        }
    } // class Column

    /**
     * Helper class to find the data belonging to meta info more quickly.
     */
    protected static class ColumnCache {

        protected Map<Integer, Column> columns;

        public ColumnCache() {
            this.columns = new HashMap<>();
        }

        public Column getColumn(final Fixing.Column meta) {
            final Integer key = meta.getId();
            Column column = this.columns.get(key);
            if (column == null) {
                final FixingsColumn data = FixingsColumnFactory.getInstance().getColumnData(meta);
                if (data != null) {
                    column = new Column(meta, data, this.columns.size());
                    this.columns.put(key, column);
                }
            }
            return column;
        }
    } // class ColumnCache

    protected String river;
    protected double from;
    protected double to;
    protected double step;
    protected boolean preprocessing;
    protected String function;
    protected int[] events;
    protected int qSectorStart;
    protected int qSectorEnd;

    public FixCalculation() {
    }

    public FixCalculation(final FixAccess access) {
        final String river = access.getRiverName();
        final Double from = access.getLowerKm();
        final Double to = access.getUpperKm();
        final Double step = access.getStep();
        final String function = access.getFunction();
        final int[] events = access.getEvents();
        final Integer qSectorStart = access.getQSectorStart();
        final Integer qSectorEnd = access.getQSectorEnd();
        final Boolean preprocessing = access.getPreprocessing();

        if (river == null) {
            addProblem("fix.missing.river");
        }

        if (from == null) {
            addProblem("fix.missing.from");
        }

        if (to == null) {
            addProblem("fix.missing.to");
        }

        if (step == null) {
            addProblem("fix.missing.step");
        }

        if (function == null) {
            addProblem("fix.missing.function");
        }

        if (events == null || events.length < 1) {
            addProblem("fix.missing.events");
        }

        if (qSectorStart == null) {
            addProblem("fix.missing.qstart.sector");
        }

        if (qSectorEnd == null) {
            addProblem("fix.missing.qend.sector");
        }

        if (preprocessing == null) {
            addProblem("fix.missing.preprocessing");
        }

        if (!hasProblems()) {
            this.river = river;
            this.from = from;
            this.to = to;
            this.step = step;
            this.function = function;
            this.events = events;
            this.qSectorStart = qSectorStart;
            this.qSectorEnd = qSectorEnd;
            this.preprocessing = preprocessing;
        }
    }

    protected static String toString(final String[] parameterNames, final double[] values) {
        final StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parameterNames.length; ++i) {
            if (i > 0)
                sb.append(", ");
            sb.append(parameterNames[i]).append(": ").append(values[i]);
        }
        return sb.toString();
    }

    /**
     * Create filter to accept only the chosen events.
     * This factored out out to be overwritten.
     */
    protected Filter createFilter() {
        return new IdsFilter(this.events);
    }

    protected List<Column> getEventColumns(final FixingsOverview overview, final ColumnCache cc) {
        final FixingsColumnFactory fcf = FixingsColumnFactory.getInstance();

        final Filter filter = createFilter();

        final List<Fixing.Column> metas = overview.filter(null, filter);

        final List<Column> columns = new ArrayList<>(metas.size());

        for (final Fixing.Column meta : metas) {

            final Column data = cc.getColumn(meta);
            if (data == null) {
                addProblem("fix.cannot.load.data");
            } else {
                columns.add(data);
            }
        }

        return columns;
    }

    // Fit a function to the given points from fixation.
    protected FitResult doFitting(final FixingsOverview overview, final ColumnCache cc, final Function func) {
        final boolean debug = log.isDebugEnabled();

        final List<Column> eventColumns = getEventColumns(overview, cc);

        if (eventColumns.size() < 2) {
            addProblem("fix.too.less.data.columns");
            return null;
        }

        final double[] qs = new double[eventColumns.size()];
        final double[] ws = new double[qs.length];
        final boolean[] interpolated = new boolean[ws.length];

        final Fitting.QWDFactory qwdFactory = new Fitting.QWDFactory() {
            @Override
            public QWD create(final double q, final double w) {
                // Check all the event columns for close match
                // and take the description and the date from meta.
                for (int i = 0; i < qs.length; ++i) {
                    if (Math.abs(qs[i] - q) < EPSILON && Math.abs(ws[i] - w) < EPSILON) {
                        final Column column = eventColumns.get(i);
                        return new QWD(qs[i], ws[i], column.getDescription(), column.getDate(), interpolated[i], 0d, column.getId()); // Use database id here
                    }
                }
                log.warn("cannot find column for (" + q + ", " + w + ")");
                return new QWD(q, w);
            }
        };

        final Fitting fitting = new Fitting(func, qwdFactory, this.preprocessing);

        final String[] parameterNames = func.getParameterNames();

        final Parameters results = new Parameters(StringUtils.join(STANDARD_COLUMNS, parameterNames));

        boolean invalid = false;

        final double[] kms = DoubleUtil.explode(this.from, this.to, this.step / 1000.0);

        if (debug) {
            log.debug("number of kms: " + kms.length);
        }

        final KMIndex<QWI[]> outliers = new KMIndex<>();
        final KMIndex<QWD[]> referenced = new KMIndex<>(kms.length);

        final int kmIndex = results.columnIndex("km");
        final int chiSqrIndex = results.columnIndex("chi_sqr");
        final int maxQIndex = results.columnIndex("max_q");
        final int stdDevIndex = results.columnIndex("std-dev");
        final int[] parameterIndices = results.columnIndices(parameterNames);

        int numFailed = 0;

        for (final double km2 : kms) {
            final double km = km2;

            // Fill Qs and Ws from event columns.
            for (int j = 0; j < ws.length; ++j) {
                interpolated[j] = !eventColumns.get(j).getQW(km, qs, ws, j);
            }

            fitting.reset();

            if (!fitting.fit(qs, ws)) {
                log.debug("Fitting for km: " + km + " failed");
                ++numFailed;
                addProblem(km, "fix.fitting.failed");
                continue;
            }

            final QWD[] refs = fitting.referencedToArray();

            referenced.add(km, refs);

            if (fitting.hasOutliers()) {
                outliers.add(km, fitting.outliersToArray());
            }

            final int row = results.newRow();
            final double[] values = fitting.getParameters();

            results.set(row, kmIndex, km);
            results.set(row, chiSqrIndex, fitting.getChiSquare());
            results.set(row, stdDevIndex, fitting.getStandardDeviation());
            results.set(row, maxQIndex, fitting.getMaxQ());
            invalid |= results.set(row, parameterIndices, values);

            if (debug) {
                log.debug("km: " + km + " " + toString(parameterNames, values));
            }
        }

        if (debug) {
            log.debug("success: " + (kms.length - numFailed));
            log.debug("failed: " + numFailed);
        }

        if (invalid) {
            addProblem("fix.invalid.values");
            results.removeNaNs();
        }

        outliers.sort();
        referenced.sort();

        return new FitResult(results, referenced, outliers);
    }

    public CalculationResult calculate() {
        final FixingsOverview overview = FixingsOverviewFactory.getOverview(this.river);

        if (overview == null) {
            addProblem("fix.no.overview.available");
        }

        final Function func = FunctionFactory.getInstance().getFunction(this.function);

        if (func == null) {
            addProblem("fix.invalid.function.name");
        }

        if (hasProblems()) {
            return new CalculationResult(this);
        }
        final CalculationResult result = innerCalculate(overview, func);

        if (result != null) {
            // Workaraound to deal with same dates in data set
            final Object o = result.getData();
            if (o instanceof FixResult) {
                final FixResult fr = (FixResult) o;
                fr.makeReferenceEventsDatesUnique();
                fr.remapReferenceIndicesToRank();
            }
        }

        return result;
    }

    protected abstract CalculationResult innerCalculate(FixingsOverview overview, Function function);
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org