sascha@655: package de.intevation.flys.artifacts.model;
sascha@655: 
teichmann@4821: import de.intevation.flys.artifacts.access.Calculation4Access;
teichmann@4821: 
sascha@3441: import de.intevation.flys.artifacts.math.BackJumpCorrector;
sascha@3441: import de.intevation.flys.artifacts.math.Function;
sascha@3441: import de.intevation.flys.artifacts.math.Identity;
sascha@3441: import de.intevation.flys.artifacts.math.Linear;
sascha@3441: 
teichmann@4812: import de.intevation.flys.artifacts.model.RiverFactory;
teichmann@4821: 
sascha@3441: import de.intevation.flys.artifacts.model.WstValueTable.QPosition;
sascha@3441: 
sascha@3441: import de.intevation.flys.model.River;
sascha@655: 
sascha@655: import de.intevation.flys.utils.DoubleUtil;
sascha@655: 
teichmann@4798: import java.util.ArrayList;
sascha@3441: import java.util.Arrays;
sascha@3441: import java.util.List;
sascha@655: 
sascha@655: import org.apache.log4j.Logger;
sascha@655: 
sascha@655: public class Calculation4
ingo@686: extends      Calculation
sascha@655: {
sascha@655:     private static Logger logger = Logger.getLogger(Calculation4.class);
sascha@655: 
sascha@655:     public static final double MINIMAL_STEP_WIDTH = 1e-5;
sascha@655: 
sascha@655:     protected List<Segment> segments;
sascha@655: 
teichmann@4812:     protected boolean isQ;
teichmann@4812:     protected double  from;
teichmann@4812:     protected double  to;
teichmann@4812:     protected double  step;
teichmann@4812:     protected String  river;
sascha@655: 
sascha@655:     public Calculation4() {
sascha@655:     }
sascha@655: 
teichmann@4812:     public Calculation4(Calculation4Access access) {
teichmann@4839:         logger.debug("Calculation4Access.cnst");
teichmann@4812:         String        river    = access.getRiver();
teichmann@4812:         List<Segment> segments = access.getSegments();
teichmann@4812:         double []     range    = access.getFromToStep();
teichmann@4812:         boolean       isQ      = access.isQ();
sascha@655: 
teichmann@4812:         if (river == null) {
teichmann@4812:             addProblem("no.river.selected");
teichmann@4812:         }
sascha@655: 
teichmann@4812:         if (range == null) {
teichmann@4812:             addProblem("no.range.found");
teichmann@4812:         }
teichmann@4812: 
teichmann@4812:         if (segments == null || segments.isEmpty()) {
teichmann@4812:             addProblem("cannot.create.segments");
teichmann@4812:         }
teichmann@4812: 
teichmann@4812:         if (!hasProblems()) {
teichmann@4812:             this.river    = river;
teichmann@4812:             this.segments = segments;
teichmann@4812:             this.from     = range[0];
teichmann@4812:             this.to       = range[1];
teichmann@4812:             this.step     = range[2];
teichmann@4812:             this.isQ      = isQ;
teichmann@4812:         }
sascha@655:     }
sascha@655: 
teichmann@4812:     public CalculationResult calculate() {
teichmann@4812:         if (hasProblems()) {
teichmann@4812:             return new CalculationResult(new WQKms[0], this);
teichmann@4812:         }
teichmann@4812: 
teichmann@4812:         WstValueTable table = null;
teichmann@4812:         River r = RiverFactory.getRiver(river);
teichmann@4812:         if (r == null) {
teichmann@4812:             addProblem("no.river.found");
teichmann@4812:         }
teichmann@4812:         else {
teichmann@4812:             table = WstValueTableFactory.getTable(r);
teichmann@4812:             if (table == null) {
teichmann@4812:                 addProblem("no.wst.for.river");
teichmann@4812:             }
teichmann@4812:             else {
teichmann@4812:                 Segment.setReferencePointConvertQ(segments, r, isQ, this);
teichmann@4812:             }
teichmann@4812:         }
teichmann@4812: 
teichmann@4812:         return hasProblems()
teichmann@4812:             ? new CalculationResult(new WQKms[0], this)
teichmann@4812:             : innerCalculate(table);
teichmann@4812:     }
teichmann@4812: 
teichmann@4812:     protected CalculationResult innerCalculate(WstValueTable table) {
sascha@655:         boolean debug = logger.isDebugEnabled();
sascha@655: 
sascha@655:         if (debug) {
sascha@655:             logger.debug(
sascha@655:                 "calculate from " + from + " to " + to + " step " + step);
sascha@655:             logger.debug("# segments: " + segments.size());
ingo@686:             for (Segment segment: segments) {
ingo@686:                 logger.debug("  " + segment);
ingo@686:             }
sascha@655:         }
sascha@655: 
sascha@655:         int numResults = segments.get(0).values.length;
sascha@655: 
sascha@655:         if (numResults < 1) {
sascha@655:             logger.debug("no values given");
sascha@2166:             addProblem("no.values.given");
sascha@709:             return new CalculationResult(new WQKms[0], this);
sascha@655:         }
sascha@655: 
sascha@655: 
sascha@655:         WQKms [] results = new WQKms[numResults];
sascha@655:         for (int i = 0; i < results.length; ++i) {
sascha@655:             results[i] = new WQKms();
sascha@655:         }
sascha@655: 
sascha@655:         if (Math.abs(step) < MINIMAL_STEP_WIDTH) {
sascha@655:             step = MINIMAL_STEP_WIDTH;
sascha@655:         }
sascha@655: 
sascha@655:         if (from > to) {
sascha@655:             step = -step;
sascha@655:         }
sascha@655: 
sascha@655:         QPosition [] qPositions = new QPosition[numResults];
sascha@655: 
sascha@655:         Function [] functions = new Function[numResults];
sascha@655: 
sascha@655:         double [] out = new double[2];
sascha@655: 
sascha@655:         Segment sentinel = new Segment(Double.MAX_VALUE);
sascha@655:         Segment s1 = sentinel, s2 = sentinel;
sascha@655: 
sascha@655:         for (double pos = from;
sascha@655:              from < to ? pos <= to : pos >= to;
sascha@655:              pos = DoubleUtil.round(pos + step)
sascha@655:         ) {
sascha@655:             if (pos < s1.referencePoint || pos > s2.referencePoint) {
sascha@655:                 if (debug) {
sascha@655:                     logger.debug("need to find new interval for " + pos);
sascha@655:                 }
sascha@655:                 // find new interval
sascha@655:                 if (pos <= segments.get(0).referencePoint) {
sascha@655:                     // before first segment -> "gleichwertig"
sascha@655:                     if (debug) {
sascha@655:                         logger.debug("before first segment -> gleichwertig");
sascha@655:                     }
sascha@655:                     Segment   first  = segments.get(0);
sascha@655:                     double [] values = first.values;
sascha@655:                     double    refPos = first.referencePoint;
sascha@655:                     for (int i = 0; i < qPositions.length; ++i) {
sascha@655:                         qPositions[i] = table.getQPosition(
sascha@655:                             refPos, values[i]);
sascha@655:                     }
sascha@655:                     sentinel.setReferencePoint(-Double.MAX_VALUE);
sascha@655:                     s1 = sentinel;
sascha@655:                     s2 = segments.get(0);
sascha@655:                     Arrays.fill(functions, Identity.IDENTITY);
sascha@655:                 }
sascha@655:                 else if (pos >= segments.get(segments.size()-1).referencePoint) {
sascha@655:                     // after last segment -> "gleichwertig"
sascha@655:                     if (debug) {
sascha@655:                         logger.debug("after last segment -> gleichwertig");
sascha@655:                     }
sascha@655:                     Segment   last   = segments.get(segments.size()-1);
sascha@655:                     double [] values = last.values;
sascha@655:                     double    refPos = last.referencePoint;
sascha@655:                     for (int i = 0; i < qPositions.length; ++i) {
sascha@655:                         qPositions[i] = table.getQPosition(
sascha@655:                             refPos, values[i]);
sascha@655:                     }
sascha@655:                     sentinel.setReferencePoint(Double.MAX_VALUE);
sascha@655:                     s1 = last;
sascha@655:                     s2 = sentinel;
sascha@655:                     Arrays.fill(functions, Identity.IDENTITY);
sascha@655:                 }
sascha@655:                 else { // "ungleichwertig"
sascha@655:                     // find matching interval
sascha@655:                     if (debug) {
sascha@655:                         logger.debug("in segments -> ungleichwertig");
sascha@655:                     }
sascha@655:                     s1 = s2 = null;
sascha@655:                     for (int i = 1, N = segments.size(); i < N; ++i) {
sascha@655:                         Segment si1 = segments.get(i-1);
sascha@655:                         Segment si  = segments.get(i);
sascha@655:                         if (debug) {
sascha@742:                             logger.debug("check " + pos + " in " +
sascha@655:                                 si1.referencePoint + " - " + si.referencePoint);
sascha@655:                         }
sascha@742:                         if (pos >= si1.referencePoint
sascha@655:                         &&  pos <= si. referencePoint) {
sascha@655:                             s1 = si1;
sascha@655:                             s2 = si;
sascha@655:                             break;
sascha@655:                         }
sascha@655:                     }
sascha@655: 
sascha@655:                     if (s1 == null) {
sascha@655:                         throw new IllegalStateException("no interval found");
sascha@655:                     }
sascha@655: 
sascha@655:                     Segment anchor, free;
sascha@655: 
sascha@655:                     if (from > to) { anchor = s1; free = s2; }
sascha@655:                     else           { anchor = s2; free = s1; }
sascha@655: 
sascha@655:                     // build transforms based on "gleichwertiger" phase
sascha@655:                     for (int i = 0; i < qPositions.length; ++i) {
sascha@655:                         QPosition qi = table.getQPosition(
sascha@655:                             anchor.referencePoint,
sascha@655:                             anchor.values[i]);
sascha@655: 
sascha@655:                         if ((qPositions[i] = qi) == null) {
sascha@2166:                             addProblem(pos, "cannot.find.q", anchor.values[i]);
sascha@655:                             functions[i] = Identity.IDENTITY;
sascha@655:                         }
sascha@655:                         else {
sascha@655:                             double qA = table.getQ(qi, anchor.referencePoint);
sascha@655:                             double qF = table.getQ(qi, free  .referencePoint);
sascha@655: 
sascha@655:                             functions[i] = Double.isNaN(qA) || Double.isNaN(qF)
sascha@655:                                 ? Identity.IDENTITY
sascha@655:                                 : new Linear(
sascha@655:                                     qA, qF,
sascha@655:                                     anchor.values[i], free.values[i]);
ingo@686: 
ingo@686:                             if (debug) {
ingo@686:                                 logger.debug(
sascha@742:                                     anchor.referencePoint + ": " +
ingo@686:                                     qA + " -> " + functions[i].value(qA) +
ingo@686:                                     " / " + free.referencePoint + ": " +
ingo@686:                                     qF + " -> " + functions[i].value(qF));
ingo@686:                             }
sascha@655:                         }
sascha@655:                     } // build transforms
sascha@655:                 } // "ungleichwertiges" interval
sascha@655:             } // find matching interval
sascha@655: 
sascha@655:             for (int i = 0; i < qPositions.length; ++i) {
sascha@655:                 QPosition qPosition = qPositions[i];
sascha@655: 
sascha@655:                 if (qPosition == null) {
sascha@655:                     continue;
sascha@655:                 }
sascha@655: 
sascha@655:                 if (table.interpolate(pos, out, qPosition, functions[i])) {
sascha@655:                     results[i].add(out[0], out[1], pos);
sascha@655:                 }
sascha@655:                 else {
sascha@2166:                     addProblem(pos, "cannot.interpolate.w.q");
sascha@655:                 }
sascha@655:             }
sascha@655:         }
sascha@655: 
sascha@655:         // Backjump correction
sascha@655:         for (int i = 0; i < results.length; ++i) {
sascha@655:             BackJumpCorrector bjc = new BackJumpCorrector();
sascha@655: 
sascha@655:             double [] ws  = results[i].getWs();
sascha@655:             double [] kms = results[i].getKms();
sascha@655: 
ingo@686:             if (bjc.doCorrection(kms, ws, this)) {
sascha@655:                 results[i] = new WQCKms(results[i], bjc.getCorrected());
sascha@655:             }
sascha@655:         }
sascha@655: 
felix@4967:         // Name the curves.
sascha@655:         for (int i = 0; i < results.length; ++i) {
sascha@655:             results[i].setName(createName(i));
sascha@655:         }
sascha@655: 
teichmann@4798:         // Generate the "Umhuellende".
teichmann@4835:         ConstantWQKms [] infoldings =
teichmann@4835:             generateInfolding(table, results, from, to, step);
teichmann@4821: 
teichmann@4821:         // TODO: Use qkms in a new result type.
teichmann@4835:         WQKms [] newResults = new WQKms[results.length + infoldings.length];
teichmann@4835:         System.arraycopy(
teichmann@4835:             results, 0, newResults, 0, results.length);
teichmann@4835:         System.arraycopy(
teichmann@4835:             infoldings, 0, newResults, results.length, infoldings.length);
teichmann@4798: 
teichmann@4835:         return new CalculationResult(newResults, this);
sascha@655:     }
sascha@655: 
teichmann@4835:     protected ConstantWQKms [] generateInfolding(
teichmann@4798:         WstValueTable wst,
teichmann@4798:         WQKms []      results,
teichmann@4798:         double        from,
teichmann@4798:         double        to,
teichmann@4798:         double        step
teichmann@4798:     ) {
teichmann@4798:         WstValueTable.Column [] columns = wst.getColumns();
teichmann@4798: 
teichmann@4798:         InfoldingColumns ic = new InfoldingColumns(columns);
teichmann@4798:         ic.markInfoldingColumns(results);
teichmann@4798: 
teichmann@4835:         List<ConstantWQKms> infoldings = new ArrayList<ConstantWQKms>();
teichmann@4798: 
teichmann@4798:         boolean [] infoldingColumns = ic.getInfoldingColumns();
teichmann@4821: 
teichmann@4821:         double [] kms = null;
teichmann@4835:         double [] ws  = null;
teichmann@4821: 
teichmann@4798:         for (int i = 0; i < infoldingColumns.length; ++i) {
teichmann@4799:             if (!infoldingColumns[i]) {
teichmann@4798:                 continue;
teichmann@4798:             }
teichmann@4821: 
teichmann@4835:             if (kms == null) {
teichmann@4835:                 kms = DoubleUtil.explode(from, to, step);
teichmann@4835:                 ws  = new double[kms.length];
teichmann@4835:             }
teichmann@4821: 
teichmann@4821:             QRangeTree.QuickQFinder qf =
teichmann@4821:                 columns[i].getQRangeTree().new QuickQFinder();
teichmann@4821: 
teichmann@4821:             int numProblemsBefore = numProblems();
teichmann@4821:             double [] qs = qf.findQs(kms, this);
teichmann@4821: 
rrenkert@5138:             String name = columns[i].getName();
teichmann@4835:             ConstantWQKms infolding = new ConstantWQKms(kms, qs, ws, name);
teichmann@4821: 
teichmann@4821:             if (numProblems() > numProblemsBefore) {
teichmann@4835:                 infolding.removeNaNs();
teichmann@4798:             }
teichmann@4798: 
teichmann@4835:             infoldings.add(infolding);
teichmann@4798:         }
teichmann@4798: 
rrenkert@5138:         for (int i = 0; i < infoldings.size(); i++) {
rrenkert@5138:             String name = infoldings.get(i).getName();
rrenkert@5138:             // TODO: i18n
rrenkert@5138:             if (i == 0) {
rrenkert@5138:                 infoldings.get(i).setName("untere Umh\u00fcllende " + name);
rrenkert@5138:             }
rrenkert@5138:             else if (i ==  infoldings.size() - 1) {
rrenkert@5138:                 infoldings.get(i).setName("obere Umh\u00fcllende " + name);
rrenkert@5138:             }
rrenkert@5138:             else {
rrenkert@5138:                 infoldings.get(i).setName("geschnitten " + name);
rrenkert@5138:             }
rrenkert@5138:         }
teichmann@4835:         return infoldings.toArray(new ConstantWQKms[infoldings.size()]);
teichmann@4798:     }
teichmann@4798: 
felix@4967:     // TODO: issue1109/2, merge with FixRealizingCalculation
sascha@655:     protected String createName(int index) {
sascha@3450:         // TODO: i18n
sascha@655:         StringBuilder sb = new StringBuilder(isQ ? "Q" : "W");
sascha@655:         sb.append(" benutzerdefiniert (");
sascha@655:         for (int i = 0, N = segments.size(); i < N; ++i) {
sascha@655:             if (i > 0) {
sascha@655:                 sb.append("; ");
sascha@655:             }
sascha@655:             Segment segment = segments.get(i);
sascha@655:             sb.append((segment.backup != null
sascha@655:                 ? segment.backup
sascha@655:                 : segment.values)[index]);
sascha@655:         }
sascha@655:         sb.append(')');
sascha@655:         return sb.toString();
sascha@655:     }
sascha@655: }
sascha@655: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :