sascha@326: package de.intevation.flys.artifacts.model; sascha@326: sascha@326: import java.io.Serializable; sascha@326: sascha@326: sascha@427: import de.intevation.flys.artifacts.math.LinearRemap; sascha@427: sascha@335: import java.util.Arrays; sascha@326: import java.util.ArrayList; sascha@326: import java.util.List; sascha@326: import java.util.Collections; sascha@326: sascha@339: import org.apache.log4j.Logger; sascha@339: sascha@339: import org.apache.commons.math.analysis.interpolation.SplineInterpolator; sascha@339: sascha@339: import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction; sascha@339: sascha@339: import org.apache.commons.math.ArgumentOutsideDomainException; sascha@339: sascha@326: public class WstValueTable sascha@326: implements Serializable sascha@326: { sascha@339: private static Logger log = Logger.getLogger(WstValueTable.class); sascha@326: sascha@395: public static final int DEFAULT_Q_STEPS = 500; sascha@339: sascha@326: public static class Column sascha@326: implements Serializable sascha@326: { sascha@326: protected String name; sascha@326: sascha@326: public Column() { sascha@326: } sascha@326: sascha@326: public Column(String name) { sascha@326: this.name = name; sascha@326: } sascha@326: sascha@326: public String getName() { sascha@326: return name; sascha@326: } sascha@326: sascha@326: public void setName(String name) { sascha@326: this.name = name; sascha@326: } sascha@326: } sascha@326: // class Column sascha@326: sascha@426: public static class QPosition { sascha@426: sascha@426: protected double q; sascha@426: protected int index; sascha@426: protected double weight; sascha@426: sascha@426: public QPosition() { sascha@426: } sascha@426: sascha@426: public QPosition(double q, int index, double weight) { sascha@426: this.index = index; sascha@426: this.weight = weight; sascha@426: } sascha@426: sascha@426: } // class Position sascha@426: sascha@326: public static class Row sascha@335: implements Serializable, Comparable sascha@326: { sascha@326: double km; sascha@335: double [] ws; sascha@335: double [] qs; sascha@335: boolean qSorted; sascha@326: sascha@326: public Row() { sascha@326: } sascha@326: sascha@335: public Row(double km) { sascha@326: this.km = km; sascha@335: } sascha@335: sascha@335: public Row(double km, double [] ws, double [] qs) { sascha@335: this(km); sascha@335: this.ws = ws; sascha@335: this.qs = qs; sascha@335: } sascha@335: sascha@335: public static final double linear( sascha@335: double x, sascha@335: double x1, double x2, sascha@335: double y1, double y2 sascha@335: ) { sascha@335: // y1 = m*x1 + b sascha@335: // y2 = m*x2 + b sascha@335: // y2 - y1 = m*(x2 - x1) sascha@335: // m = (y2 - y1)/(x2 - x1) # x2 != x1 sascha@335: // b = y1 - m*x1 sascha@335: sascha@335: if (x1 == x2) { sascha@335: return 0.5*(y1 + y2); sascha@335: } sascha@335: double m = (y2 - y1)/(x2 - x1); sascha@335: double b = y1 - m*x1; sascha@335: return x*m + b; sascha@335: } sascha@335: sascha@335: public static final double factor(double x, double p1, double p2) { sascha@335: // 0 = m*p1 + b <=> b = -m*p1 sascha@335: // 1 = m*p2 + b sascha@335: // 1 = m*(p2 - p1) sascha@335: // m = 1/(p2 - p1) # p1 != p2 sascha@335: // f(x) = x/(p2-p1) - p1/(p2-p1) <=> (x-p1)/(p2-p1) sascha@335: sascha@335: return p1 == p2 ? 0.0 : (x-p1)/(p2-p1); sascha@335: } sascha@335: sascha@335: public static final double weight(double factor, double a, double b) { sascha@335: return (1.0-factor)*a + factor*b; sascha@335: } sascha@335: sascha@335: public void interpolateW( sascha@335: Row other, sascha@335: double km, sascha@335: double [] input, sascha@335: double [] output sascha@335: ) { sascha@335: double factor = factor(km, this.km, other.km); sascha@335: sascha@335: for (int i = 0; i < input.length; ++i) { sascha@335: double q = input[i]; sascha@335: int idx1 = getQIndex(q); sascha@335: int idx2 = other.getQIndex(q); sascha@335: sascha@335: double w1 = idx1 >= 0 sascha@335: ? ws[idx1] sascha@335: : interpolateW(-idx1-1, q); sascha@335: sascha@335: double w2 = idx2 >= 0 sascha@335: ? other.ws[idx2] sascha@335: : other.interpolateW(-idx2-1, q); sascha@335: sascha@335: output[i] = weight(factor, w1, w2); sascha@335: } sascha@335: } sascha@335: sascha@335: public double interpolateW(int idx, double q) { sascha@335: return idx < 1 || idx >= qs.length sascha@335: ? Double.NaN // do not extrapolate sascha@335: : linear(q, qs[idx-1], qs[idx], ws[idx-1], ws[idx]); sascha@335: } sascha@335: sascha@335: public double interpolateW(double q) { sascha@335: if (Double.isNaN(q)) return Double.NaN; sascha@335: int index = getQIndex(q); ingo@360: return index >= 0 ? ws[index] : interpolateW(-index -1, q); sascha@335: } sascha@335: sascha@335: public int getQIndex(double q) { sascha@335: return qSorted ? binaryQIndex(q) : linearQIndex(q); sascha@335: } sascha@335: sascha@335: public int binaryQIndex(double q) { sascha@335: return Arrays.binarySearch(qs, q); sascha@335: } sascha@335: sascha@335: public int linearQIndex(double q) { sascha@335: switch (qs.length) { sascha@335: case 0: break; sascha@335: case 1: sascha@335: if (qs[0] == q) return 0; sascha@335: if (qs[0] < q) return -(1+1); sascha@335: return -(0+1); sascha@335: default: sascha@335: for (int i = 1; i < qs.length; ++i) { sascha@335: double qa = qs[i-1]; sascha@335: double qb = qs[i]; sascha@335: if (qa == q) return i-1; sascha@335: if (qb == q) return i; sascha@335: if (qa > qb) { double t = qa; qa = qb; qb = t; } sascha@335: if (q > qa && q < qb) return -(i+1); sascha@335: } sascha@335: return -qs.length; sascha@335: } sascha@335: sascha@335: return -1; sascha@335: } sascha@335: sascha@335: public int compareTo(Row other) { sascha@335: double d = km - other.km; sascha@458: if (d < -0.0001) return -1; sascha@458: if (d > 0.0001) return +1; sascha@335: return 0; sascha@326: } sascha@339: sascha@395: public double [][] interpolateWQ(Row other, double km, int steps) { sascha@339: sascha@395: int W = Math.min(ws.length, other.ws.length); sascha@339: sascha@339: if (W < 1) { sascha@339: return new double[2][0]; sascha@339: } sascha@339: sascha@339: double factor = factor(km, this.km, other.km); sascha@339: sascha@395: double [] splineQ = new double[W]; sascha@395: double [] splineW = new double[W]; sascha@339: sascha@395: double minQ = Double.MAX_VALUE; sascha@395: double maxQ = -Double.MAX_VALUE; sascha@339: sascha@339: for (int i = 0; i < W; ++i) { sascha@395: double wws = weight(factor, ws[i], other.ws[i]); sascha@395: double wqs = weight(factor, qs[i], other.qs[i]); sascha@395: splineW[i] = wws; sascha@395: splineQ[i] = wqs; sascha@395: if (wqs < minQ) minQ = wqs; sascha@395: if (wqs > maxQ) maxQ = wqs; sascha@339: } sascha@339: sascha@395: double stepWidth = (maxQ - minQ)/steps; sascha@395: sascha@339: SplineInterpolator interpolator = new SplineInterpolator(); sascha@395: PolynomialSplineFunction spline = sascha@395: interpolator.interpolate(splineQ, splineW); sascha@339: sascha@395: double [] outWs = new double[steps]; sascha@395: double [] outQs = new double[steps]; sascha@339: sascha@339: try { sascha@395: double q = minQ; sascha@395: for (int i = 0; i < outWs.length; ++i, q += stepWidth) { sascha@395: outWs[i] = spline.value(outQs[i] = q); sascha@339: } sascha@339: } sascha@339: catch (ArgumentOutsideDomainException aode) { sascha@339: log.error("Spline interpolation failed.", aode); sascha@339: } sascha@339: sascha@339: return new double [][] { outWs, outQs }; sascha@339: } sascha@339: sascha@395: public double [][] interpolateWQ(int steps) { sascha@395: sascha@395: int W = ws.length; sascha@339: sascha@339: if (W < 1) { sascha@339: return new double[2][0]; sascha@339: } sascha@339: sascha@395: double [] splineW = new double[W]; sascha@395: double [] splineQ = new double[W]; sascha@395: sascha@395: double minQ = Double.MAX_VALUE; sascha@395: double maxQ = -Double.MAX_VALUE; sascha@339: sascha@339: for (int i = 0; i < W; ++i) { sascha@395: splineW[i] = ws[i]; sascha@395: splineQ[i] = qs[i]; sascha@395: if (qs[i] < minQ) minQ = qs[i]; sascha@395: if (qs[i] > maxQ) maxQ = qs[i]; sascha@339: } sascha@339: sascha@395: double stepWidth = (maxQ - minQ)/steps; sascha@395: sascha@339: SplineInterpolator interpolator = new SplineInterpolator(); sascha@339: sascha@395: PolynomialSplineFunction spline = sascha@395: interpolator.interpolate(splineQ, splineW); sascha@339: sascha@395: double [] outWs = new double[steps]; sascha@395: double [] outQs = new double[steps]; sascha@339: sascha@339: try { sascha@395: double q = minQ; sascha@395: for (int i = 0; i < outWs.length; ++i, q += stepWidth) { sascha@395: outWs[i] = spline.value(outQs[i] = q); sascha@339: } sascha@339: } sascha@339: catch (ArgumentOutsideDomainException aode) { sascha@339: log.error("Spline interpolation failed.", aode); sascha@339: } sascha@339: sascha@339: return new double [][] { outWs, outQs }; sascha@339: } sascha@339: sascha@426: public QPosition getQPosition(double q) { sascha@426: int qIndex = getQIndex(q); sascha@426: sascha@426: if (qIndex >= 0) { sascha@426: // direct column match sascha@426: return new QPosition(q, qIndex, 1.0); sascha@426: } sascha@426: sascha@426: qIndex = -qIndex -1; sascha@426: sascha@426: if (qIndex < 0 || qIndex >= qs.length-1) { sascha@426: // do not extrapolate sascha@426: return null; sascha@426: } sascha@426: sascha@426: return new QPosition( sascha@426: q, qIndex, factor(q, qs[qIndex-1], qs[qIndex])); sascha@426: } sascha@426: sascha@426: public QPosition getQPosition(Row other, double km, double q) { sascha@426: sascha@426: QPosition qpt = getQPosition(q); sascha@426: QPosition qpo = other.getQPosition(q); sascha@426: sascha@426: if (qpt == null || qpo == null) { sascha@426: return null; sascha@426: } sascha@426: sascha@426: double kmWeight = factor(km, this.km, other.km); sascha@426: sascha@426: // XXX: Index interpolation is a bit sticky here! sascha@426: sascha@426: int index = (int)Math.round( sascha@426: weight(kmWeight, qpt.index, qpo.index)); sascha@426: sascha@426: double weight = weight(kmWeight, qpt.weight, qpo.weight); sascha@426: sascha@426: return new QPosition(q, index, weight); sascha@426: } sascha@426: sascha@426: public void storeWQ( sascha@426: QPosition qPosition, sascha@426: double [] ows, sascha@426: double [] oqs, sascha@426: int index sascha@426: ) { sascha@426: int qIdx = qPosition.index; sascha@426: double qWeight = qPosition.weight; sascha@426: sascha@426: if (qWeight == 1.0) { sascha@426: oqs[index] = qs[qIdx]; sascha@426: ows[index] = ws[qIdx]; sascha@426: } sascha@426: else { sascha@426: oqs[index] = weight(qWeight, qs[qIdx-1], qs[qIdx]); sascha@426: ows[index] = weight(qWeight, ws[qIdx-1], ws[qIdx]); sascha@426: } sascha@426: } sascha@426: sascha@426: public void storeWQ( sascha@426: Row other, sascha@426: double km, sascha@426: QPosition qPosition, sascha@426: double [] ows, sascha@426: double [] oqs, sascha@426: int index sascha@426: ) { sascha@426: double kmWeight = factor(km, this.km, other.km); sascha@426: sascha@426: double tq, tw; sascha@426: double oq, ow; sascha@426: sascha@426: int qIdx = qPosition.index; sascha@426: double qWeight = qPosition.weight; sascha@426: sascha@426: if (qWeight == 1.0) { sascha@426: tq = qs[qIdx]; sascha@426: tw = ws[qIdx]; sascha@426: oq = other.qs[qIdx]; sascha@426: ow = other.ws[qIdx]; sascha@426: } sascha@426: else { sascha@426: tq = weight(qWeight, qs[qIdx-1], qs[qIdx]); sascha@426: tw = weight(qWeight, ws[qIdx-1], ws[qIdx]); sascha@426: oq = weight(qWeight, other.qs[qIdx-1], other.qs[qIdx]); sascha@426: ow = weight(qWeight, other.ws[qIdx-1], other.ws[qIdx]); sascha@426: } sascha@426: sascha@621: oqs[index] = weight(kmWeight, tq, oq); sascha@621: ows[index] = weight(kmWeight, tw, ow); sascha@426: } sascha@427: sascha@427: public void storeWQ( sascha@427: QPosition qPosition, sascha@427: LinearRemap remap, sascha@427: double [] ows, sascha@427: double [] oqs, sascha@427: int index sascha@427: ) { sascha@427: int qIdx = qPosition.index; sascha@427: double qWeight = qPosition.weight; sascha@427: sascha@427: oqs[index] = remap.eval(km, qWeight == 1.0 sascha@427: ? qs[qIdx] sascha@427: : weight(qWeight, qs[qIdx-1], qs[qIdx])); sascha@427: sascha@427: ows[index] = interpolateW(oqs[index]); sascha@427: } sascha@427: sascha@427: public void storeWQ( sascha@427: Row other, sascha@427: double km, sascha@427: QPosition qPosition, sascha@427: LinearRemap remap, sascha@427: double [] ows, sascha@427: double [] oqs, sascha@427: int index sascha@427: ) { sascha@427: double kmWeight = factor(km, this.km, other.km); sascha@427: sascha@427: double tq, tw; sascha@427: double oq, ow; sascha@427: sascha@427: int qIdx = qPosition.index; sascha@427: double qWeight = qPosition.weight; sascha@427: sascha@427: if (qWeight == 1.0) { sascha@427: tq = remap.eval(this.km, qs[qIdx]); sascha@427: oq = remap.eval(other.km, other.qs[qIdx]); sascha@427: } sascha@427: else { sascha@427: tq = remap.eval( sascha@427: this.km, sascha@427: weight(qWeight, qs[qIdx-1], qs[qIdx])); sascha@427: oq = remap.eval( sascha@427: other.km, sascha@427: weight(qWeight, other.qs[qIdx-1], other.qs[qIdx])); sascha@427: } sascha@427: sascha@427: tw = interpolateW(tq); sascha@427: ow = other.interpolateW(oq); sascha@427: sascha@621: oqs[index] = weight(kmWeight, tq, oq); sascha@621: ows[index] = weight(kmWeight, tw, ow); sascha@427: } sascha@326: } sascha@326: // class Row sascha@326: sascha@326: protected List rows; sascha@326: sascha@326: protected Column [] columns; sascha@326: sascha@326: public WstValueTable() { sascha@326: rows = new ArrayList(); sascha@326: } sascha@326: sascha@326: public WstValueTable(Column [] columns) { sascha@326: this(); sascha@326: this.columns = columns; sascha@326: } sascha@326: sascha@458: public void sortRows() { sascha@458: Collections.sort(rows); sascha@458: } sascha@458: sascha@458: sascha@335: public double [] interpolateW(double km, double [] qs) { sascha@380: return interpolateW(km, qs, new double[qs.length]); sascha@380: } sascha@335: sascha@380: public double [] interpolateW(double km, double [] qs, double [] ws) { sascha@335: sascha@335: int rowIndex = Collections.binarySearch(rows, new Row(km)); sascha@335: sascha@335: if (rowIndex >= 0) { // direct row match sascha@335: Row row = rows.get(rowIndex); sascha@335: for (int i = 0; i < qs.length; ++i) { sascha@335: ws[i] = row.interpolateW(qs[i]); sascha@335: } sascha@335: } sascha@335: else { // needs bilinear interpolation sascha@335: rowIndex = -rowIndex -1; sascha@335: sascha@335: if (rowIndex < 1 || rowIndex >= rows.size()) { sascha@335: // do not extrapolate sascha@335: Arrays.fill(ws, Double.NaN); sascha@335: } sascha@335: else { sascha@335: rows.get(rowIndex-1).interpolateW( sascha@335: rows.get(rowIndex), sascha@335: km, qs, ws); sascha@335: } sascha@335: } sascha@335: sascha@335: return ws; sascha@335: } sascha@335: sascha@339: sascha@339: public double [][] interpolateWQ(double km) { sascha@395: return interpolateWQ(km, DEFAULT_Q_STEPS); sascha@339: } sascha@339: sascha@395: public double [][] interpolateWQ(double km, int steps) { sascha@395: sascha@339: int rowIndex = Collections.binarySearch(rows, new Row(km)); sascha@339: sascha@339: if (rowIndex >= 0) { // direct row match sascha@339: Row row = rows.get(rowIndex); sascha@395: return row.interpolateWQ(steps); sascha@339: } sascha@339: sascha@339: rowIndex = -rowIndex -1; sascha@339: sascha@339: if (rowIndex < 1 || rowIndex >= rows.size()) { sascha@339: // do not extrapolate sascha@339: return new double[2][0]; sascha@339: } sascha@339: sascha@339: Row r1 = rows.get(rowIndex-1); sascha@339: Row r2 = rows.get(rowIndex); sascha@339: sascha@395: return r1.interpolateWQ(r2, km, steps); sascha@339: } sascha@339: sascha@427: public void interpolate( sascha@427: double [] kms, sascha@427: double [] ws, sascha@427: double [] qs, sascha@427: QPosition qPosition, sascha@427: LinearRemap remap sascha@427: ) { sascha@427: int R1 = rows.size()-1; sascha@427: sascha@427: Row kmKey = new Row(); sascha@427: for (int i = 0; i < kms.length; ++i) { sascha@427: kmKey.km = kms[i]; sascha@427: int rowIndex = Collections.binarySearch(rows, kmKey); sascha@427: sascha@427: if (rowIndex >= 0) { sascha@427: // direct row match sascha@427: rows.get(rowIndex).storeWQ(qPosition, remap, ws, qs, i); sascha@427: continue; sascha@427: } sascha@427: sascha@427: rowIndex = -rowIndex -1; sascha@427: sascha@427: if (rowIndex < 1 || rowIndex >= R1) { sascha@427: // do not extrapolate sascha@427: ws[i] = qs[i] = Double.NaN; sascha@427: continue; sascha@427: } sascha@427: Row r1 = rows.get(rowIndex-1); sascha@427: Row r2 = rows.get(rowIndex); sascha@427: r1.storeWQ(r2, kms[i], qPosition, remap, ws, qs, i); sascha@427: } sascha@427: } sascha@427: sascha@427: public QPosition interpolate( sascha@426: double q, sascha@426: int referenceIndex, sascha@426: double [] kms, sascha@426: double [] ws, sascha@426: double [] qs sascha@426: ) { sascha@426: Row kmKey = new Row(kms[referenceIndex]); sascha@426: sascha@426: int rowIndex = Collections.binarySearch(rows, kmKey); sascha@426: sascha@426: int R1 = rows.size()-1; sascha@426: sascha@426: QPosition qPosition = null; sascha@426: sascha@426: if (rowIndex >= 0) { // direct row match sascha@426: qPosition = rows.get(rowIndex).getQPosition(q); sascha@426: } sascha@426: else { sascha@426: rowIndex = -rowIndex -1; sascha@426: sascha@426: if (rowIndex < 1 || rowIndex >= R1) { sascha@426: // do not extrapolate sascha@427: return null; sascha@426: } sascha@426: sascha@426: // interpolation case sascha@426: Row r1 = rows.get(rowIndex-1); sascha@426: Row r2 = rows.get(rowIndex); sascha@426: sascha@426: qPosition = r1.getQPosition(r2, kms[referenceIndex], q); sascha@426: } sascha@426: sascha@426: if (qPosition == null) { sascha@426: // we cannot locate q in reference row sascha@427: return null; sascha@426: } sascha@426: sascha@426: for (int i = 0; i < kms.length; ++i) { sascha@426: kmKey.km = kms[i]; sascha@458: sascha@426: rowIndex = Collections.binarySearch(rows, kmKey); sascha@426: sascha@426: if (rowIndex >= 0) { sascha@426: // direct row match sascha@426: rows.get(rowIndex).storeWQ(qPosition, ws, qs, i); sascha@426: continue; sascha@426: } sascha@426: sascha@426: rowIndex = -rowIndex -1; sascha@426: sascha@426: if (rowIndex < 1 || rowIndex >= R1) { sascha@426: // do not extrapolate sascha@426: ws[i] = qs[i] = Double.NaN; sascha@426: continue; sascha@426: } sascha@426: Row r1 = rows.get(rowIndex-1); sascha@426: Row r2 = rows.get(rowIndex); sascha@426: r1.storeWQ(r2, kms[i], qPosition, ws, qs, i); sascha@426: } sascha@426: sascha@427: return qPosition; sascha@426: } sascha@326: } sascha@326: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :