Unverified Commit 7b4ed71b authored by Jesse Mapel's avatar Jesse Mapel Committed by GitHub
Browse files

Replaced secant root with Newton's method in SAR (#302)

* Added Newton's method root finder

* Replaced secant root with Newton's method

* cleaned up slantRangeToGroundRange

* Review comments

* Better error message
parent 342b2168
Loading
Loading
Loading
Loading
+19 −6
Original line number Original line Diff line number Diff line
@@ -78,11 +78,24 @@ double brentRoot(
  std::function<double(double)> func,
  std::function<double(double)> func,
  double epsilon = 1e-10);
  double epsilon = 1e-10);


double secantRoot(
// Evaluate a polynomial function.
    double lowerBound,
// Coefficients should be ordered least order to greatest I.E. {1, 2, 3} is 1 + 2x + 3x^2
    double upperBound,
double evaluatePolynomial(
    std::function<double(double)> func,
  const std::vector<double> &coeffs,
    double epsilon = 1e-10,
  double x);

// Evaluate the derivative of a polynomial function.
// Coefficients should be ordered least order to greatest I.E. {1, 2, 3} is 1 + 2x + 3x^2
double evaluatePolynomialDerivative(
  const std::vector<double> &coeffs,
  double x);

// Find a root of a polynomial using Newton's method.
// Coefficients should be ordered least order to greatest I.E. {1, 2, 3} is 1 + 2x + 3x^2
double polynomialRoot(
  const std::vector<double> &coeffs,
  double guess,
  double threshold = 1e-10,
  int maxIterations = 30);
  int maxIterations = 30);


double computeEllipsoidElevation(
double computeEllipsoidElevation(
+14 −26
Original line number Original line Diff line number Diff line
@@ -426,29 +426,17 @@ double UsgsAstroSarSensorModel::slantRangeToGroundRange(


  std::vector<double> coeffs = getRangeCoefficients(time);
  std::vector<double> coeffs = getRangeCoefficients(time);


  // Calculates the ground range from the slant range.
  std::function<double(double)> slantRangeToGroundRangeFunction =
    [this, coeffs, slantRange](double groundRange){
   return slantRange - groundRangeToSlantRange(groundRange, coeffs);
  };

  // Need to come up with an initial guess when solving for ground
  // Need to come up with an initial guess when solving for ground
  // range given slant range. Compute the ground range at the
  // range given slant range. Naively use the middle of the image.
  // near and far edges of the image by evaluating the sample-to-
  double guess = 0.5 * m_nSamples * m_scaledPixelWidth;
  // ground-range equation: groundRange=(sample-1)*scaled_pixel_width
  // at the edges of the image. We also need to add some padding to
  // allow for solving for coordinates that are slightly outside of
  // the actual image area. Use sample=-0.25*image_samples and
  // sample=1.25*image_samples.
  double minGroundRangeGuess = (-0.25 * m_nSamples - 1.0) * m_scaledPixelWidth;
  double maxGroundRangeGuess = (1.25 * m_nSamples - 1.0) * m_scaledPixelWidth;


  // Tolerance to 1/20th of a pixel for now.
  // Tolerance to 1/20th of a pixel for now.
  return secantRoot(minGroundRangeGuess, maxGroundRangeGuess, slantRangeToGroundRangeFunction, tolerance);
  coeffs[0] -= slantRange;
  return polynomialRoot(coeffs, guess, tolerance);
}
}


double UsgsAstroSarSensorModel::groundRangeToSlantRange(double groundRange, const std::vector<double> &coeffs) const {
double UsgsAstroSarSensorModel::groundRangeToSlantRange(double groundRange, const std::vector<double> &coeffs) const {
  return coeffs[0] + groundRange * (coeffs[1] + groundRange * (coeffs[2] + groundRange * coeffs[3]));
  return evaluatePolynomial(coeffs, groundRange);
}
}




+108 −113
Original line number Original line Diff line number Diff line
@@ -279,8 +279,7 @@ void lagrangeInterp(
  }
  }
}
}


double brentRoot(
double brentRoot(double lowerBound,
  double lowerBound,
                 double upperBound,
                 double upperBound,
                 std::function<double(double)> func,
                 std::function<double(double)> func,
                 double epsilon) {
                 double epsilon) {
@@ -347,61 +346,57 @@ double brentRoot(
  return nextPoint;
  return nextPoint;
}
}


double secantRoot(double lowerBound, double upperBound, std::function<double(double)> func,
double evaluatePolynomial(const std::vector<double> &coeffs,
                  double epsilon, int maxIters) {
                          double x) {
  bool found = false;
  if (coeffs.empty()) {

    throw std::invalid_argument("Polynomial coeffs must be non-empty.");
  double x0 = lowerBound;
  double x1 = upperBound;
  double f0 = func(x0);
  double f1 = func(x1);
  double diff = 0;
  double x2 = 0;
  double f2 = 0;

  // Make sure we bound the root (f = 0.0)
  if (f0 * f1 > 0.0) {
    throw std::invalid_argument("Function values at the boundaries have the same sign [secantRoot].");
  }

  // Order the bounds
  if (f1 < f0) {
    std::swap(x0, x1);
    std::swap(f0, f1);
  }
  }

  auto revIt = coeffs.crbegin();
  for (int iteration=0; iteration < maxIters; iteration++) {
  double value = *revIt;
    x2 = x1 - f1 * (x1 - x0)/(f1 - f0);
  ++revIt;
    f2 = func(x2);
  for (; revIt != coeffs.crend(); ++revIt) {

    value *= x;
    // Update the bounds for the next iteration
    value += *revIt;
    if (f2 < 0.0) {
      diff = x1 - x2;
      x1 = x2;
      f1 = f2;
  }
  }
    else {
  return value;
      diff = x0 - x2;
      x0 = x2;
      f0 = f2;
}
}


    // Check to see if we're done
double evaluatePolynomialDerivative(const std::vector<double> &coeffs,
    if ((fabs(diff) <= epsilon) || (f2 == 0.0) ) {
                                    double x) {
      found = true;
  if (coeffs.empty()) {
      break;
    throw std::invalid_argument("Polynomial coeffs must be non-empty.");
  }
  int i = coeffs.size() - 1;
  double value = i * coeffs[i];
  --i;
  for (; i > 0; --i) {
    value *= x;
    value += i * coeffs[i];
  }
  }
  return value;
}
}


  if (found) {
double polynomialRoot(const std::vector<double> &coeffs,
   return x2;
                      double guess,
                      double threshold,
                      int maxIterations) {
  double root = guess;
  double polyValue = evaluatePolynomial(coeffs, root);
  double polyDeriv = 0.0;
  for (int iteration = 0; iteration < maxIterations+1; iteration++) {
    if (fabs(polyValue) < threshold) {
      return root;
    }
    }
  else {
    polyDeriv = evaluatePolynomialDerivative(coeffs, root);
    throw csm::Error(
    if (fabs(polyDeriv) < 1e-15) {
    csm::Error::UNKNOWN_ERROR,
      throw std::invalid_argument("Derivative at guess (" +
    "Could not find a root of the function using the secant method",
                                  std::to_string(guess) + ") is too close to 0.");
    "secantRoot");
    }
    root -= polyValue / polyDeriv;
    polyValue = evaluatePolynomial(coeffs, root);
  }
  }
  throw std::invalid_argument("Root finder did not converge after " +
                              std::to_string(maxIterations) + " iterations");
}
}


double computeEllipsoidElevation(
double computeEllipsoidElevation(
+19 −7
Original line number Original line Diff line number Diff line
@@ -350,13 +350,25 @@ TEST(UtilitiesTests, brentRoot) {
  EXPECT_THROW(brentRoot(-3.0, 3.0, testPoly), std::invalid_argument);
  EXPECT_THROW(brentRoot(-3.0, 3.0, testPoly), std::invalid_argument);
}
}


TEST(UtilitiesTests, secantRoot) {
TEST(UtilitiesTests, polynomialEval) {
  std::function<double(double)> testPoly = [](double x) {
  std::vector<double> coeffs = {-12.0, 4.0, -3.0, 1.0};
    return (x - 2) * (x + 1) * (x + 7);
  EXPECT_DOUBLE_EQ(evaluatePolynomial(coeffs, -1.0), -20.0);
  };
  EXPECT_DOUBLE_EQ(evaluatePolynomialDerivative(coeffs, -1.0), 13.0);
  EXPECT_NEAR(secantRoot(1.0, 3.0, testPoly, 1e-10), 2.0, 1e-10);
  EXPECT_DOUBLE_EQ(evaluatePolynomial(coeffs, 0.0), -12.0);
  EXPECT_NEAR(secantRoot(0.0, -3.0, testPoly, 1e-10), -1.0, 1e-10);
  EXPECT_DOUBLE_EQ(evaluatePolynomialDerivative(coeffs,  0.0), 4.0);
  EXPECT_THROW(secantRoot(-1000.0, 1000.0, testPoly), csm::Error);
  EXPECT_DOUBLE_EQ(evaluatePolynomial(coeffs, 2.0), -8.0);
  EXPECT_DOUBLE_EQ(evaluatePolynomialDerivative(coeffs,  2.0), 4.0);
  EXPECT_DOUBLE_EQ(evaluatePolynomial(coeffs, 3.5), 8.125);
  EXPECT_DOUBLE_EQ(evaluatePolynomialDerivative(coeffs, 3.5), 19.75);
  EXPECT_THROW(evaluatePolynomial(std::vector<double>(), 0.0), std::invalid_argument);
  EXPECT_THROW(evaluatePolynomialDerivative(std::vector<double>(), 0.0), std::invalid_argument);
}

TEST(UtilitiesTests, polynomialRoot) {
  std::vector<double> oneRootCoeffs = {-12.0, 4.0, -3.0, 1.0}; // roots are 3, +-2i
  std::vector<double> noRootCoeffs = {4.0, 0.0, 1.0}; // roots are +-2i
  EXPECT_NEAR(polynomialRoot(oneRootCoeffs, 0.0), 3.0, 1e-10);
  EXPECT_THROW(polynomialRoot(noRootCoeffs, 0.0), std::invalid_argument);
}
}


TEST(UtilitiesTests, vectorProduct) {
TEST(UtilitiesTests, vectorProduct) {