From 86a0ac05048a606c8fcb285cb22b6496d6305405 Mon Sep 17 00:00:00 2001
From: Luc Maisonobe <luc@orekit.org>
Date: Wed, 25 Feb 2015 16:44:32 +0100
Subject: [PATCH] Better protection against search loops.

---
 .../linesensor/SensorMeanPlaneCrossing.java   | 71 +++++++++++++++----
 1 file changed, 58 insertions(+), 13 deletions(-)

diff --git a/src/main/java/org/orekit/rugged/linesensor/SensorMeanPlaneCrossing.java b/src/main/java/org/orekit/rugged/linesensor/SensorMeanPlaneCrossing.java
index 7357b189..4c79e9a5 100644
--- a/src/main/java/org/orekit/rugged/linesensor/SensorMeanPlaneCrossing.java
+++ b/src/main/java/org/orekit/rugged/linesensor/SensorMeanPlaneCrossing.java
@@ -16,7 +16,10 @@
  */
 package org.orekit.rugged.linesensor;
 
+import org.apache.commons.math3.analysis.UnivariateFunction;
 import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.solvers.BracketingNthOrderBrentSolver;
+import org.apache.commons.math3.analysis.solvers.UnivariateSolver;
 import org.apache.commons.math3.geometry.euclidean.threed.FieldVector3D;
 import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
 import org.apache.commons.math3.linear.MatrixUtils;
@@ -25,6 +28,7 @@ import org.apache.commons.math3.linear.SingularValueDecomposition;
 import org.apache.commons.math3.util.FastMath;
 import org.orekit.frames.Transform;
 import org.orekit.rugged.errors.RuggedException;
+import org.orekit.rugged.errors.RuggedExceptionWrapper;
 import org.orekit.rugged.utils.SpacecraftToObservedBody;
 import org.orekit.time.AbsoluteDate;
 import org.orekit.utils.Constants;
@@ -303,30 +307,32 @@ public class SensorMeanPlaneCrossing {
         // as we know the solution is improved in the second stage of inverse location.
         // We expect two or three evaluations only. Each new evaluation shows up quickly in
         // the performances as it involves frames conversions
-        double  crossingLine  = midLine;
-        Transform bodyToInert = midBodyToInert;
-        Transform scToInert   = midScToInert;
-        boolean atMin         = false;
-        boolean atMax         = false;
-        double deltaL         = Double.NaN;
+        double[]  searchHistory = new double[maxEval];
+        double crossingLine     = midLine;
+        Transform bodyToInert   = midBodyToInert;
+        Transform scToInert     = midScToInert;
+        boolean atMin           = false;
+        boolean atMax           = false;
         for (int i = 0; i < maxEval; ++i) {
 
+            searchHistory[i] = crossingLine;
             final FieldVector3D<DerivativeStructure> targetDirection =
                     evaluateLine(crossingLine, targetPV, bodyToInert, scToInert);
             final DerivativeStructure beta = FieldVector3D.angle(targetDirection, meanPlaneNormal);
 
-            final double previousDeltaL = deltaL;
-            deltaL = (0.5 * FastMath.PI - beta.getValue()) / beta.getPartialDerivative(1);
+            final double deltaL = (0.5 * FastMath.PI - beta.getValue()) / beta.getPartialDerivative(1);
             if (FastMath.abs(deltaL) <= accuracy) {
                 // return immediately, without doing any additional evaluation!
                 return new CrossingResult(sensor.getDate(crossingLine), crossingLine, targetDirection);
             }
-            if (FastMath.abs(deltaL + previousDeltaL) <= 0.01 * FastMath.abs(deltaL)) {
-                // we are stuck in a loop between two values!
-                // try to escape from the loop by targeting the middle point
-                deltaL = 0.5 * deltaL;
-            }
             crossingLine += deltaL;
+            for (int j = 0; j < i; ++j) {
+                if (FastMath.abs(crossingLine - searchHistory[j]) <= 0.01 * FastMath.abs(deltaL)) {
+                    // rare case: we are stuck in a loop!
+                    // switch to a more robust (but slower) algorithm in this case
+                    return slowFind(targetPV, crossingLine);
+                }
+            }
 
             if (crossingLine < minLine) {
                 if (atMin) {
@@ -359,6 +365,45 @@ public class SensorMeanPlaneCrossing {
 
     }
 
+    /** Find mean plane crossing using a slow but robust method.
+     * @param targetPV target ground point
+     * @param initialGuess initial guess for the crossing line
+     * @return line number and target direction at mean plane crossing,
+     * or null if search interval does not bracket a solution
+     * @exception RuggedException if geometry cannot be computed for some line or
+     * if the maximum number of evaluations is exceeded
+     */
+    public CrossingResult slowFind(final PVCoordinates targetPV, final double initialGuess)
+        throws RuggedException {
+        try {
+
+            final UnivariateSolver solver = new BracketingNthOrderBrentSolver(accuracy, 5);
+            double crossingLine = solver.solve(maxEval, new UnivariateFunction() {
+                /** {@inheritDoc} */
+                @Override
+                public double value(final double x) throws RuggedExceptionWrapper {
+                    try {
+                        final AbsoluteDate date = sensor.getDate(x);
+                        final FieldVector3D<DerivativeStructure> targetDirection =
+                                evaluateLine(x, targetPV, scToBody.getBodyToInertial(date), scToBody.getScToInertial(date));
+                        final DerivativeStructure beta = FieldVector3D.angle(targetDirection, meanPlaneNormal);
+                        return 0.5 * FastMath.PI - beta.getValue();
+                    } catch (RuggedException re) {
+                        throw new RuggedExceptionWrapper(re);
+                    }
+                }
+            }, minLine, maxLine, initialGuess);
+
+            final AbsoluteDate date = sensor.getDate(crossingLine);
+            final FieldVector3D<DerivativeStructure> targetDirection =
+                    evaluateLine(crossingLine, targetPV, scToBody.getBodyToInertial(date), scToBody.getScToInertial(date));
+            return new CrossingResult(sensor.getDate(crossingLine), crossingLine, targetDirection);
+
+        } catch (RuggedExceptionWrapper rew) {
+            throw rew.getException();
+        }
+    }
+
     /** Evaluate geometry for a given line number.
      * @param lineNumber current line number
      * @param targetPV target ground point
-- 
GitLab