reasonable stop condition

I set it up so if the iteration gets stuck in a local minimum, it will terminate and return that local minimum.  this seemd to work more or less perfectly a minute ago, but I broke it either by reducing the inverse raster resolution or by refactoring the cosine calculation slitely.
This commit is contained in:
Justin Kunimune 2023-07-23 13:45:07 -07:00
parent 1e4fe2e680
commit 6b74bfd0df

View File

@ -189,9 +189,10 @@ public class Elastik {
private double[] inverse_by_iteration(double x, double y, int i, double[] initial_guess) { private double[] inverse_by_iteration(double x, double y, int i, double[] initial_guess) {
final double finite_difference = 1e-5; // radians final double finite_difference = 1e-5; // radians
final double second_finite_difference = 0.1; // dimensionless final double second_finite_difference = 0.1; // dimensionless
final double tolerance = pow(1e-2, 2); // km^2 final double distance_tolerance = pow(1e-2, 2); // km^2
final double cosine_tolerance = 1e-3; // dimensionless
final double backstep_factor = 2.0; final double backstep_factor = 2.0;
final double backstep_relaxation_factor = 6.0; final double backstep_relaxation_factor = 12.0;
final int max_num_steps = 40; final int max_num_steps = 40;
final int max_num_step_sizes = 40; final int max_num_step_sizes = 40;
double[] target = {x, y}; double[] target = {x, y};
@ -201,7 +202,7 @@ public class Elastik {
double[] residual = new double[2]; double[] residual = new double[2];
for (int l = 0; l < 2; l ++) for (int l = 0; l < 2; l ++)
residual[l] = sections[i][l].evaluate(guess[0], guess[1]) - target[l]; residual[l] = sections[i][l].evaluate(guess[0], guess[1]) - target[l];
double distance = 1/2.*LinearAlgebra.dot(residual, residual); double distance = 1/2.*LinAlg.square(residual);
if (isNaN(distance)) // if this section doesn't cover the initial guess, it's probably pointless if (isNaN(distance)) // if this section doesn't cover the initial guess, it's probably pointless
return null; return null;
@ -211,7 +212,7 @@ public class Elastik {
while (true) { while (true) {
// check the stopping conditions // check the stopping conditions
if (distance < tolerance) if (distance < distance_tolerance)
return guess; // solution is found return guess; // solution is found
if (num_steps > max_num_steps) if (num_steps > max_num_steps)
return null; // too many iterations have elapsed return null; // too many iterations have elapsed
@ -229,10 +230,18 @@ public class Elastik {
return null; // if we're butting up against the edge, that means there's probably no solution return null; // if we're butting up against the edge, that means there's probably no solution
} }
double[][] jacobian_transpose = LinAlg.transpose(jacobian);
double[][] hessian = LinAlg.dot(jacobian_transpose, jacobian);
double[] gradient = LinAlg.dot(jacobian_transpose, residual);
// check the other stopping condition
double sum_cosines_squared = 0;
for (int l = 0; l < 2; l ++)
sum_cosines_squared += pow(gradient[l], 2)/LinAlg.square(jacobian_transpose[l])*distance;
if (sum_cosines_squared < cosine_tolerance)
return guess; // local minimum is found
// perform a backtracking line-search // perform a backtracking line-search
double[][] jacobian_transpose = LinearAlgebra.transpose(jacobian);
double[][] hessian = LinearAlgebra.dot(jacobian_transpose, jacobian);
double[] gradient = LinearAlgebra.dot(jacobian_transpose, residual);
double λ_factor = max(.01, pow(cos(guess[0]), 2)); double λ_factor = max(.01, pow(cos(guess[0]), 2));
int num_step_sizes = 0; int num_step_sizes = 0;
while (true) { while (true) {
@ -241,10 +250,10 @@ public class Elastik {
double[][] amplified_hessian = new double[][] { double[][] amplified_hessian = new double[][] {
{ hessian[0][0] + step_limiter, hessian[0][1] }, { hessian[0][0] + step_limiter, hessian[0][1] },
{ hessian[1][0], hessian[1][1] + step_limiter*λ_factor } }; { hessian[1][0], hessian[1][1] + step_limiter*λ_factor } };
double[] step = LinearAlgebra.solve_linear_equation(amplified_hessian, gradient); double[] step = LinAlg.solve_linear_equation(amplified_hessian, gradient);
// calculate the second-order correction to the step // calculate the second-order correction to the step
double[] expected_change = LinearAlgebra.dot(jacobian, step); double[] expected_change = LinAlg.dot(jacobian, step);
double[] curvature = new double[2]; double[] curvature = new double[2];
for (int l = 0; l < 2; l ++) { for (int l = 0; l < 2; l ++) {
double fore_residual = sections[i][l].evaluate( double fore_residual = sections[i][l].evaluate(
@ -254,12 +263,12 @@ public class Elastik {
(fore_residual - residual[l])/second_finite_difference (fore_residual - residual[l])/second_finite_difference
- expected_change[l]); - expected_change[l]);
} }
double[] step_correction = LinearAlgebra.solve_linear_equation( double[] step_correction = LinAlg.solve_linear_equation(
amplified_hessian, amplified_hessian,
LinearAlgebra.dot(jacobian_transpose, curvature)); LinAlg.dot(jacobian_transpose, curvature));
// check the correction magnitude condition before applying the correction // check the correction magnitude condition before applying the correction
if (LinearAlgebra.dot(step_correction, step_correction)/LinearAlgebra.dot(step, step) < .25) { if (LinAlg.square(step_correction)/LinAlg.square(step) < .25) {
// take the step // take the step
double[] new_guess = new double[2]; double[] new_guess = new double[2];
for (int l = 0; l < 2; l++) for (int l = 0; l < 2; l++)
@ -273,7 +282,7 @@ public class Elastik {
double[] new_residual = new double[2]; double[] new_residual = new double[2];
for (int l = 0; l < 2; l++) for (int l = 0; l < 2; l++)
new_residual[l] = sections[i][l].evaluate(new_guess[0], new_guess[1]) - target[l]; new_residual[l] = sections[i][l].evaluate(new_guess[0], new_guess[1]) - target[l];
double new_distance = 1/2.*LinearAlgebra.dot(new_residual, new_residual); double new_distance = 1/2.*LinAlg.square(new_residual);
// check the line-search stopping conditions // check the line-search stopping conditions
if (!isNaN(new_distance) && new_distance < distance) { if (!isNaN(new_distance) && new_distance < distance) {
@ -291,7 +300,7 @@ public class Elastik {
} }
if (step_limiter == 0) if (step_limiter == 0)
step_limiter += (sqrt(1.3*(1 - pow(hessian[0][1], 2)/(hessian[0][0]*hessian[1][1])) + 1) - 1) step_limiter += (sqrt(1.4*(1 - pow(hessian[0][1], 2)/(hessian[0][0]*hessian[1][1])) + 1) - 1)
*min(hessian[0][0], hessian[1][1]/λ_factor); *min(hessian[0][0], hessian[1][1]/λ_factor);
else else
step_limiter *= backstep_factor; step_limiter *= backstep_factor;
@ -688,7 +697,7 @@ public class Elastik {
/** /**
* some static matrix operations for the Levenberg-Marquardt implementation * some static matrix operations for the Levenberg-Marquardt implementation
*/ */
private static class LinearAlgebra { private static class LinAlg {
/** /**
* evaluate the inner product of two vectors * evaluate the inner product of two vectors
* @return the 2-vector resulting from the dot-product of A and b * @return the 2-vector resulting from the dot-product of A and b
@ -731,6 +740,14 @@ public class Elastik {
return product; return product;
} }
/**
* evaluate the square of the L-2 norm of a vector
* @return the dot-product of a with itself
*/
private static double square(double[] a) {
return dot(a, a);
}
/** /**
* flip the rows of A with its columns * flip the rows of A with its columns
* @return the transpose of A * @return the transpose of A