changeset 6793:bdbe704dd433 longitudinal-symmetry

merged changes from default into longitudinal-symmetry branch
author Tom Gottfried <tom@intevation.de>
date Thu, 08 Aug 2013 17:36:44 +0200
parents 962f6b805b48 (current diff) 70b440dc5317 (diff)
children 23ab795f2f0e
files artifacts/doc/conf/meta-data.xml artifacts/src/main/java/org/dive4elements/river/exports/minfo/BedDifferenceEpochGenerator.java artifacts/src/main/java/org/dive4elements/river/exports/minfo/BedQualityGenerator.java
diffstat 18 files changed, 161 insertions(+), 105 deletions(-) [+]
line wrap: on
line diff
--- a/artifacts/doc/conf/meta-data.xml	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/doc/conf/meta-data.xml	Thu Aug 08 17:36:44 2013 +0200
@@ -347,6 +347,7 @@
                     <dc:when test="$out = 'longitudinal_section'">
                       <dc:call-macro name="longitudinal"/>
                       <dc:call-macro name="differences"/>
+                      <dc:call-macro name="bedheight_differences"/>
                     </dc:when>
                     <dc:when test="$out = 'discharge_longitudinal_section'">
                       <dc:call-macro name="longitudinal"/>
@@ -395,6 +396,7 @@
                     <dc:when test="$out = 'bedheight_middle_longitudinal_section'">
                       <dc:call-macro name="waterlevels-discharge"/>
                       <dc:call-macro name="differenceable-fix"/>
+                      <dc:call-macro name="differences"/>
                     </dc:when>
                     <dc:when test="$out = 'floodmap-hws'">
                       <dc:call-macro name="floodmap-hws-user"/>
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/QualityMeasurementFactory.java	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/QualityMeasurementFactory.java	Thu Aug 08 17:36:44 2013 +0200
@@ -97,6 +97,7 @@
         private QualityMeasurementResultTransformer() {
         }
 
+        /** tuples is a row. */
         @Override
         public Object transformTuple(Object[] tuple, String[] aliases) {
             Map<String, Double> map = new HashMap<String, Double>();
@@ -175,6 +176,7 @@
         return new QualityMeasurements(query.list());
     }
 
+    /** Get all measurements. */
     public static QualityMeasurements getBedMeasurements(
         String river,
         double from,
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadFactory.java	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadFactory.java	Thu Aug 08 17:36:44 2013 +0200
@@ -395,19 +395,24 @@
             sqlQuery.setString("grain", "total");
             List<Object []> results = sqlQuery.list();
             SedimentLoad load = new SedimentLoad();
-            Object[] row = results.get(0);
-            load = new SedimentLoad(
-                    (String) row[0],
-                    (Date) row[1],
-                    null,
-                    false,
-                    (String) row[4]);
-            getValues("coarse", sqlQuery, load, floatStations);
-            getValues("fine_middle", sqlQuery, load, floatStations);
-            getValues("sand", sqlQuery, load, floatStations);
-            getValues("suspended_sediment", sqlQuery, load, suspStations);
-            getValues("susp_sand_bed", sqlQuery, load, floatStations);
-            getValues("susp_sand", sqlQuery, load, floatStations);
+            if (results.isEmpty()) {
+                log.warn("Empty result for year calculation.");
+            }
+            else {
+                Object[] row = results.get(0);
+                load = new SedimentLoad(
+                        (String) row[0],
+                        (Date) row[1],
+                        null,
+                        false,
+                        (String) row[4]);
+            }
+            load = getValues("coarse", sqlQuery, load, floatStations);
+            load = getValues("fine_middle", sqlQuery, load, floatStations);
+            load = getValues("sand", sqlQuery, load, floatStations);
+            load = getValues("suspended_sediment", sqlQuery, load, suspStations);
+            load = getValues("susp_sand_bed", sqlQuery, load, floatStations);
+            load = getValues("susp_sand", sqlQuery, load, floatStations);
 
             return load;
         }
@@ -628,6 +633,7 @@
 
     /**
      * Return sediment loads with 'unknown' fraction type.
+     * @param river Name of the river
      * @param unit Restrict result set to those of given unit.
      * @param type Type like year, epoch, off_epoch
      */
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/sq/Fitting.java	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/sq/Fitting.java	Thu Aug 08 17:36:44 2013 +0200
@@ -9,7 +9,6 @@
 package org.dive4elements.river.artifacts.model.sq;
 
 import org.dive4elements.river.artifacts.math.fitting.Function;
-import org.dive4elements.river.artifacts.math.fitting.Linear;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -18,9 +17,8 @@
 
 import org.apache.commons.math.optimization.fitting.CurveFitter;
 
-import org.apache.commons.math.optimization.general.AbstractLeastSquaresOptimizer;
-import org.apache.commons.math.optimization.general.GaussNewtonOptimizer;
 import org.apache.commons.math.optimization.general.LevenbergMarquardtOptimizer;
+import org.apache.commons.math.stat.regression.SimpleRegression;
 
 import org.apache.log4j.Logger;
 
@@ -54,13 +52,15 @@
 
     protected Callback callback;
 
+    protected SQ.View sqView;
+
     public Fitting() {
     }
 
-    public Fitting(Function function, double stdDevFactor) {
-        this();
+    public Fitting(Function function, double stdDevFactor, SQ.View sqView) {
         this.function     = function;
         this.stdDevFactor = stdDevFactor;
+        this.sqView       = sqView;
     }
 
     public Function getFunction() {
@@ -82,57 +82,79 @@
     @Override
     public void initialize(List<SQ> sqs) throws MathException {
 
-        AbstractLeastSquaresOptimizer optimizer = getOptimizer();
+        if (USE_NON_LINEAR_FITTING
+        || function.getInitialGuess().length != 2) {
+            nonLinearFitting(sqs);
+        }
+        else {
+            linearFitting(sqs);
+        }
+    }
+
+    protected void linearFitting(List<SQ> sqs) {
+
+        coeffs = linearRegression(sqs);
+
+        instance = function.instantiate(coeffs);
+    }
+
+    protected double [] linearRegression(List<SQ> sqs) {
+
+        SimpleRegression reg = new SimpleRegression();
+
+        int invalidPoints = 0;
+        for (SQ sq: sqs) {
+            double s = sq.getS();
+            double q = sq.getQ();
+            if (s <= 0d || q <= 0d) {
+                ++invalidPoints;
+                continue;
+            }
+            reg.addData(Math.log(q), Math.log(s));
+        }
+
+        if (sqs.size() - invalidPoints < 2) {
+            log.debug("not enough points");
+            return new double [] { 0, 0 };
+        }
+
+        double a = Math.exp(reg.getIntercept());
+        double b = reg.getSlope();
+
+        if (log.isDebugEnabled()) {
+            log.debug("invalid points: " +
+                invalidPoints + " (" + sqs.size() + ")");
+            log.debug("a: " + a + " (" + Math.log(a) + ")");
+            log.debug("b: " + b);
+        }
+
+        return new double [] { a, b };
+    }
+
+
+    protected void nonLinearFitting(List<SQ> sqs) throws MathException {
+
+        LevenbergMarquardtOptimizer optimizer =
+            new LevenbergMarquardtOptimizer();
 
         CurveFitter cf = new CurveFitter(optimizer);
-        double [] values = new double[2];
+
         for (SQ sq: sqs) {
-            values[0] = sq.getQ();
-            values[1] = sq.getS();
-            transformInputValues(values);
-            cf.addObservedPoint(values[0], values[1]);
+            cf.addObservedPoint(sq.getS(), sq.getQ());
         }
 
         coeffs = cf.fit(
             function, function.getInitialGuess());
 
-        transformCoeffsBack(coeffs);
-
         instance = function.instantiate(coeffs);
 
         chiSqr = optimizer.getChiSquare();
     }
 
-    protected Function getFunction(Function function) {
-        return USE_NON_LINEAR_FITTING
-            ? function
-            : Linear.INSTANCE;
-    }
-
-    protected void transformInputValues(double [] values) {
-        if (!USE_NON_LINEAR_FITTING) {
-            for (int i = 0; i < values.length; ++i) {
-                values[i] = Math.log(values[i]);
-            }
-        }
-    }
-
-    protected AbstractLeastSquaresOptimizer getOptimizer() {
-        return USE_NON_LINEAR_FITTING
-            ? new LevenbergMarquardtOptimizer()
-            : new GaussNewtonOptimizer(false);
-    }
-
-    protected void transformCoeffsBack(double [] coeffs) {
-        if (!USE_NON_LINEAR_FITTING && coeffs.length > 0) {
-            coeffs[0] = Math.exp(coeffs[0]);
-        }
-    }
-
     @Override
     public double eval(SQ sq) {
-        double s = instance.value(sq.q);
-        return sq.s - s;
+        double s = instance.value(sqView.getQ(sq));
+        return sqView.getS(sq) - s;
     }
 
     @Override
@@ -157,28 +179,15 @@
             chiSqr);
     }
 
-    protected static final List<SQ> onlyValid(List<SQ> sqs) {
-
-        List<SQ> good = new ArrayList<SQ>(sqs.size());
-
-        for (SQ sq: sqs) {
-            if (sq.isValid()) {
-                good.add(sq);
-            }
-        }
-
-        return good;
-    }
-
-    public boolean fit(List<SQ> sqs, String method, Callback callback) {
-
-        sqs = onlyValid(sqs);
+    public boolean fit(List<SQ> sqs, String   method, Callback callback) {
 
         if (sqs.size() < 2) {
             log.warn("Too less points for fitting.");
             return false;
         }
 
+        sqs = new ArrayList<SQ>(sqs);
+
         this.callback = callback;
 
         try {
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/sq/MeasurementFactory.java	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/sq/MeasurementFactory.java	Thu Aug 08 17:36:44 2013 +0200
@@ -190,9 +190,10 @@
     }
 
     public static Measurements getMeasurements(
-        String    river,
-        double    location,
-        DateRange dateRange
+        String     river,
+        double     location,
+        DateRange  dateRange,
+        SQ.Factory sqFactory
     ) {
         Session session = SedDBSessionHolder.HOLDER.get();
         try {
@@ -202,7 +203,7 @@
             List<Measurement> accumulated = loadFractions(
                 session, river, location, dateRange);
 
-            return new Measurements(totals, accumulated);
+            return new Measurements(totals, accumulated, sqFactory);
         }
         finally {
             session.close();
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/sq/Measurements.java	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/sq/Measurements.java	Thu Aug 08 17:36:44 2013 +0200
@@ -66,13 +66,17 @@
     protected List<Measurement> measuments;
     protected List<Measurement> accumulated;
 
+    protected SQ.Factory sqFactory;
+
     public Measurements() {
     }
 
     public Measurements(
         List<Measurement> measuments,
-        List<Measurement> accumulated
+        List<Measurement> accumulated,
+        SQ.Factory        sqFactory
     ) {
+        this.sqFactory = sqFactory;
         if (log.isDebugEnabled()) {
             log.debug("number of measuments: " + measuments.size());
             log.debug("number of accumulated: " + accumulated.size());
@@ -81,14 +85,14 @@
         this.accumulated = accumulated;
     }
 
-    public static List<SQ> extractSQ(
+    public List<SQ> extractSQ(
         List<Measurement> measuments,
         SExtractor extractor
     ) {
         List<SQ> result = new ArrayList<SQ>(measuments.size());
         int invalid = 0;
         for (Measurement measument: measuments) {
-            SQ sq = new SQ(extractor.getS(measument), measument.Q());
+            SQ sq = sqFactory.createSQ(extractor.getS(measument), measument.Q());
             if (sq.isValid()) {
                 result.add(sq);
             }
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/sq/SQ.java	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/sq/SQ.java	Thu Aug 08 17:36:44 2013 +0200
@@ -11,8 +11,37 @@
 import java.io.Serializable;
 
 
+/** Represents S/Q pairs. They are immutable! */
 public class SQ implements Serializable {
 
+    public interface Factory {
+        SQ createSQ(double s, double q);
+    }
+
+    public static final Factory SQ_FACTORY = new Factory() {
+        @Override
+        public SQ createSQ(double s, double q) {
+            return new SQ(s, q);
+        }
+    };
+
+    public interface View {
+        double getS(SQ sq);
+        double getQ(SQ sq);
+    }
+
+    public static final View SQ_VIEW = new View() {
+        @Override
+        public double getS(SQ sq) {
+            return sq.getS();
+        }
+
+        @Override
+        public double getQ(SQ sq) {
+            return sq.getQ();
+        }
+    };
+
     protected double s;
     protected double q;
 
@@ -29,19 +58,10 @@
         return s;
     }
 
-    public void setS(double s) {
-        this.s = s;
-    }
-
-
     public double getQ() {
         return q;
     }
 
-    public void setQ(double q) {
-        this.q = q;
-    }
-
     public boolean isValid() {
         return !Double.isNaN(s) && !Double.isNaN(q);
     }
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/model/sq/SQRelationCalculation.java	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/model/sq/SQRelationCalculation.java	Thu Aug 08 17:36:44 2013 +0200
@@ -114,8 +114,12 @@
             addProblem("sq.missing.sq.function");
         }
 
+        SQ.View    sqView    = SQ.SQ_VIEW;
+        SQ.Factory sqFactory = SQ.SQ_FACTORY;
+
         Measurements measurements =
-            MeasurementFactory.getMeasurements(river, location, period);
+            MeasurementFactory.getMeasurements(
+                river, location, period, sqFactory);
 
         SQFractionResult [] fractionResults =
             new SQFractionResult[SQResult.NUMBER_FRACTIONS];
@@ -126,7 +130,7 @@
             SQFractionResult fractionResult;
 
             List<SQFractionResult.Iteration> iterations =
-                doFitting(function, sqs);
+                doFitting(function, sqs, sqView);
 
             if (iterations == null) {
                 // TODO: i18n
@@ -149,12 +153,13 @@
 
     protected List<SQFractionResult.Iteration> doFitting(
         final Function function,
-        List<SQ> sqs
+        List<SQ>       sqs,
+        SQ.View        sqView
     ) {
         final List<SQFractionResult.Iteration> iterations =
             new ArrayList<SQFractionResult.Iteration>();
 
-        boolean success = new Fitting(function, outliers).fit(
+        boolean success = new Fitting(function, outliers, sqView).fit(
             sqs,
             method,
             new Fitting.Callback() {
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/states/minfo/DifferencesState.java	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/states/minfo/DifferencesState.java	Thu Aug 08 17:36:44 2013 +0200
@@ -127,9 +127,7 @@
                 newFacets.add(new BedDiffYearFacet(
                     idx,
                     BED_DIFFERENCE_MORPH_WIDTH,
-                    createBedDiffMorphDescription(
-                        meta,
-                        (BedDiffYearResult)results[idx]),
+                    createBedDiffMorphDescription(meta),
                     ComputeType.ADVANCE,
                     stateId,
                     hash));
@@ -342,12 +340,9 @@
     }
 
     protected String createBedDiffMorphDescription(
-        CallMeta meta,
-        BedDiffYearResult result) {
-        String range = result.getStart() + " - " + result.getEnd();
-
+        CallMeta meta) {
         return Resources.getMsg(meta, I18N_FACET_BED_DIFF_MORPH,
-            I18N_FACET_BED_DIFF_MORPH, new Object[] { range });
+            I18N_FACET_BED_DIFF_MORPH);
     }
 
     protected String createBedDiffAbsoluteDescription(
--- a/artifacts/src/main/java/org/dive4elements/river/exports/StyledSeriesBuilder.java	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/StyledSeriesBuilder.java	Thu Aug 08 17:36:44 2013 +0200
@@ -49,6 +49,7 @@
      *                 the NaNs lead to gaps in graph.
      * @param distance if two consecutive entries in points[0] are more
      *                 than distance apart, create a NaN value to skip in display.
+     *                 Still, create a line segment.
      */
     public static void addPoints(XYSeries series, double[][] points, boolean skipNANs, double distance) {
         if (points == null || points.length <= 1) {
@@ -64,6 +65,11 @@
             }
             // Create gap if distance >= distance.
             if (i != 0 && Math.abs(xPoints[i-1] - xPoints[i]) >= distance) {
+                // Create at least a small segment for last point.
+                if (!Double.isNaN(yPoints[i-1])) {
+                    series.add(xPoints[i-1]+0.99d*(distance)/2.d, yPoints[i-1], false);
+                }
+
                 if (!Double.isNaN(yPoints[i-1]) && !Double.isNaN(yPoints[i])) {
                     series.add((xPoints[i-1]+xPoints[i])/2.d, Double.NaN, false);
                 }
--- a/artifacts/src/main/java/org/dive4elements/river/exports/minfo/BedDifferenceEpochGenerator.java	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/minfo/BedDifferenceEpochGenerator.java	Thu Aug 08 17:36:44 2013 +0200
@@ -260,3 +260,4 @@
         }
     }
 }
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifacts/src/main/java/org/dive4elements/river/exports/minfo/BedQualityGenerator.java	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/minfo/BedQualityGenerator.java	Thu Aug 08 17:36:44 2013 +0200
@@ -138,6 +138,7 @@
      * @param attr
      *            theme for facet
      */
+    @Override
     public void doOut(ArtifactAndFacet artifactAndFacet, Document attr,
         boolean visible) {
         String name = artifactAndFacet.getFacetName();
--- a/artifacts/src/main/java/org/dive4elements/river/exports/process/BedDiffHeightYearProcessor.java	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/process/BedDiffHeightYearProcessor.java	Thu Aug 08 17:36:44 2013 +0200
@@ -26,6 +26,8 @@
     private final static Logger logger =
             Logger.getLogger(BedDiffHeightYearProcessor.class);
 
+    protected static double GAP_TOLERANCE = 0.101d;
+
     @Override
     public void doOut(
             XYChartGenerator generator,
@@ -62,7 +64,7 @@
         int axidx) {
 
         XYSeries series = new StyledXYSeries(aandf.getFacetDescription(), theme);
-        StyledSeriesBuilder.addPoints(series, data.getHeightPerYearData(), false, 0.101d);
+        StyledSeriesBuilder.addPoints(series, data.getHeightPerYearData(), false, GAP_TOLERANCE);
 
         generator.addAxisSeries(series, axidx, visible);
     }
--- a/artifacts/src/main/java/org/dive4elements/river/exports/process/BedDiffYearProcessor.java	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/process/BedDiffYearProcessor.java	Thu Aug 08 17:36:44 2013 +0200
@@ -26,6 +26,8 @@
     private final static Logger logger =
             Logger.getLogger(BedDiffYearProcessor.class);
 
+    protected static double GAP_TOLERANCE = 0.101d;
+
     @Override
     public void doOut(
             XYChartGenerator generator,
@@ -71,10 +73,10 @@
 
         XYSeries series = new StyledXYSeries(bundle.getFacetDescription(), attr);
         if (idx == 0) {
-            StyledSeriesBuilder.addPoints(series, data.getHeights1Data(), false, 0.101d);
+            StyledSeriesBuilder.addPoints(series, data.getHeights1Data(), false, GAP_TOLERANCE);
         }
         else {
-            StyledSeriesBuilder.addPoints(series, data.getHeights2Data(), false, 0.101d);
+            StyledSeriesBuilder.addPoints(series, data.getHeights2Data(), false, GAP_TOLERANCE);
         }
 
         generator.addAxisSeries(series, axidx, visible);
--- a/artifacts/src/main/resources/messages.properties	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/resources/messages.properties	Thu Aug 08 17:36:44 2013 +0200
@@ -287,7 +287,7 @@
 bedquality.toplayer = 0.0m - 0.3m
 bedquality.sublayer = 0.1m - 0.5m
 facet.bedheight.diff.year = Bedheight Difference {0}
-facet.bedheight.diff.morph = sounding Width {0}
+facet.bedheight.diff.morph = sounding Width
 facet.bedheight.diff.height1 = Original Height Minuend {0}
 facet.bedheight.diff.height2 = Original Height Subtrahend {0}
 facet.bedheight.diff.absolute = Bedheight Difference/Year {0}
--- a/artifacts/src/main/resources/messages_de.properties	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/resources/messages_de.properties	Thu Aug 08 17:36:44 2013 +0200
@@ -287,7 +287,7 @@
 bedquality.toplayer = 0,0m - 0,3m
 bedquality.sublayer = 0,1m - 0,5m
 facet.bedheight.diff.year = Sohlh\u00f6hendifferenz {0}
-facet.bedheight.diff.morph = gepeilte Breite {0}
+facet.bedheight.diff.morph = gepeilte Breite
 facet.bedheight.diff.height1 = H\u00f6he Minuend {0}
 facet.bedheight.diff.height2 = H\u00f6he Subtrahend {0}
 facet.bedheight.diff.absolute = Sohlh\u00f6hendifferenz/Jahr {0}
--- a/artifacts/src/main/resources/messages_de_DE.properties	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/resources/messages_de_DE.properties	Thu Aug 08 17:36:44 2013 +0200
@@ -286,7 +286,7 @@
 bedquality.sublayer = 0,1m - 0,5m
 facet.bedheight.diff.year = Sohlh\u00f6hendifferenz {0}
 facet.bedheight.diff.year.raw = Sohlh\u00f6hendifferenz {0} (Rohdaten)
-facet.bedheight.diff.morph = gepeilte Breite {0}
+facet.bedheight.diff.morph = gepeilte Breite
 facet.bedheight.diff.height1 = H\u00f6he Minuend {0}
 facet.bedheight.diff.height2 = H\u00f6he Subtrahend {0}
 facet.bedheight.diff.absolute = Sohlh\u00f6hendifferenz/Jahr {0}
--- a/artifacts/src/main/resources/messages_en.properties	Wed Aug 07 18:57:29 2013 +0200
+++ b/artifacts/src/main/resources/messages_en.properties	Thu Aug 08 17:36:44 2013 +0200
@@ -290,7 +290,7 @@
 bedquality.toplayer = 0.0m - 0.3m
 bedquality.sublayer = 0.1m - 0.5m
 facet.bedheight.diff.year = Bedheight Difference {0}
-facet.bedheight.diff.morph = sounding Width {0}
+facet.bedheight.diff.morph = sounding Width
 facet.bedheight.diff.height1 = Original Height Minuend {0}
 facet.bedheight.diff.height2 = Original Height Subtrahend {0}
 facet.bedheight.diff.absolute = Bedheight Difference/Year {0}

http://dive4elements.wald.intevation.org