view artifacts/src/main/java/org/dive4elements/river/artifacts/model/fixings/FixCalculation.java @ 9415:9744ce3c3853

Rework of fixanalysis computation and dWt and WQ facets. Got rid of strange remapping and bitshifting code by explicitely saving the column information and using it in the facets. The facets also put the valid station range into their xml-metadata
author gernotbelger
date Thu, 16 Aug 2018 16:27:53 +0200
parents 0274c7444b2d
children 2b83d3a96703
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.List;

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.Parameters;
import org.dive4elements.river.artifacts.model.fixings.FixingsOverview.IdsFilter;
import org.dive4elements.river.utils.DoubleUtil;

/** Calculation base class for fix. */
public abstract class FixCalculation extends Calculation {

    private static final long serialVersionUID = 1L;

    private static Logger log = Logger.getLogger(FixCalculation.class);

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

    protected static class FitResult {

        private final Parameters parameters;

        private final FixResultColumns resultColumns;

        public FitResult(final Parameters parameters, final FixResultColumns resultColumns) {
            this.parameters = parameters;
            this.resultColumns = resultColumns;
        }

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

        public FixResultColumns getResultColumns() {
            return this.resultColumns;
        }
    }

    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 FixingColumnFilter createFilter() {
        return new IdsFilter(this.events);
    }

    protected List<FixingColumnWithData> getEventColumns(final FixingsOverview overview, final ColumnCache cc) {
        final FixingColumnFilter filter = createFilter();

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

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

        for (final FixingColumn meta : metas) {

            final FixingColumnWithData 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 final FitResult doFitting(final FixingsOverview overview, final ColumnCache cc, final Function func) {
        final boolean debug = log.isDebugEnabled();

        final FixResultColumns resultColumns = new FixResultColumns();

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

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

        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 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 km : kms) {

            final Fitting fitting = Fitting.fit(resultColumns, km, func, this.preprocessing, eventColumns);
            if (fitting == null) {
                log.debug("Fitting for km: " + km + " failed");
                ++numFailed;
                addProblem(km, "fix.fitting.failed");
                continue;
            }

            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();
        }

        resultColumns.sortAll();

        return new FitResult(results, resultColumns);
    }

    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);

        return innerCalculate(overview, func);
    }

    protected abstract CalculationResult innerCalculate(FixingsOverview overview, Function function);
}

http://dive4elements.wald.intevation.org