7 #ifdef MANGO_EIGEN_AVAILABLE
11 #ifndef MANGO_EIGEN_AVAILABLE
15 throw std::runtime_error(
"ERROR: The algorithm mango_levenberg_marquardt was selected. This algorithm requires Eigen, but MANGO was built without Eigen.");
26 : state_vector(solver_in->state_vector, solver_in->N_parameters),
27 targets(solver_in->targets, solver_in->N_terms),
28 sigmas(solver_in->sigmas, solver_in->N_terms)
34 check_least_squares_solution =
false;
37 central_lambda = 0.01;
38 lambda_reduction_on_success = 10.0;
40 max_line_search_iterations = 4;
41 max_outer_iterations = 100000;
42 save_lambda_history =
true;
46 N_terms = solver->N_terms;
47 verbose = solver->verbose;
48 N_line_search = solver->N_line_search;
49 proc0_world = solver->mpi_partition->get_proc0_world();
50 comm_group_leaders = solver->mpi_partition->get_comm_group_leaders();
52 if (solver->verbose > 0) std::cout <<
"Hello from levenberg_marquardt. N_line_search=" << N_line_search << std::endl;
54 if (N_line_search < 1)
throw std::runtime_error(
"N_line_search must be >= 1.");
57 state_vector_tentative.resize(N_parameters);
58 residuals.resize(N_terms);
59 shifted_residuals.resize(N_terms);
60 Jacobian.resize(N_terms,N_parameters);
61 residuals_extended.resize(N_terms + N_parameters);
62 Jacobian_extended.resize(N_terms + N_parameters, N_parameters);
63 delta_x.resize(N_parameters);
64 lambda_scan_residuals.resize(N_terms, N_line_search);
65 lambda_scan_state_vectors.resize(N_parameters, N_line_search);
66 lambdas.resize(N_line_search);
67 lambda_scan_objective_functions.resize(N_line_search);
68 if (check_least_squares_solution) {
69 delta_x_direct.resize(N_parameters);
70 alpha.resize(N_parameters, N_parameters);
71 alpha_prime.resize(N_parameters, N_parameters);
72 beta.resize(N_parameters);
75 residuals_extended.bottomRows(N_parameters) = Eigen::VectorXd::Zero(N_parameters);
76 Jacobian_extended.bottomRows(N_parameters) = Eigen::MatrixXd::Zero(N_parameters,N_parameters);
79 lambda_increase_factor = compute_lambda_increase_factor(N_line_search);
80 normalized_lambda_grid =
new double[N_line_search];
81 compute_lambda_grid(N_line_search, lambda_increase_factor, normalized_lambda_grid);
82 if (verbose>0 && proc0_world) {
83 std::cout <<
"lambda_increase_factor: " << lambda_increase_factor << std::endl;
84 std::cout <<
"normalized_lambda_grid:";
85 for (j_lambda_grid=0; j_lambda_grid<N_line_search; j_lambda_grid++) std::cout <<
" " << normalized_lambda_grid[j_lambda_grid];
86 std::cout << std::endl;
97 if (save_lambda_history && proc0_world) {
98 std::string filename = solver->output_filename +
"_levenberg_marquardt";
99 lambda_file.open(filename.c_str());
100 if (!lambda_file.is_open()) {
101 std::cerr <<
"Levenberg-Marquardt output file: " << filename << std::endl;
102 throw std::runtime_error(
"Error! Unable to open Levenberg-Marquardt output file.");
104 lambda_file <<
"outer_iteration,j_line_search,lambdas(1:N_line_search),objective_functions(1:N_line_search),min_objective_function_index,line_search_succeeded" << std::endl;
107 keep_going_outer =
true;
110 while (keep_going_outer && (outer_iteration < max_outer_iterations)) {
113 if (! proc0_world) MPI_Bcast(&data,1,MPI_INT,0,comm_group_leaders);
115 solver->finite_difference_Jacobian(state_vector.data(), residuals.data(), Jacobian.data());
120 shifted_residuals = (residuals - targets).cwiseQuotient(sigmas);
121 for (j=0; j<N_parameters; j++) {
123 Jacobian.col(j) = Jacobian.col(j).cwiseQuotient(sigmas);
127 MPI_Bcast(Jacobian.data(), N_terms*N_parameters, MPI_DOUBLE, 0, comm_group_leaders);
128 MPI_Bcast(shifted_residuals.data(), N_terms, MPI_DOUBLE, 0, comm_group_leaders);
131 objective_function = shifted_residuals.dot(shifted_residuals);
133 residuals_extended.topRows(N_terms) = shifted_residuals;
134 if (verbose>0 && proc0_world) {
135 std::cout <<
"Here comes state_vector from Eigen" << std::endl;
136 std::cout << std::setprecision(16) << std::scientific << state_vector << std::endl;
137 std::cout <<
"Here comes shifted_residuals from Eigen" << std::endl;
138 std::cout << shifted_residuals << std::endl;
139 std::cout <<
"Here comes residuals_extended from Eigen" << std::endl;
140 std::cout << residuals_extended << std::endl;
141 std::cout <<
"Here comes Jacobian from Eigen" << std::endl;
142 std::cout << Jacobian << std::endl;
145 if (check_least_squares_solution) {
146 alpha = Jacobian.transpose() * Jacobian;
148 beta = -Jacobian.transpose() * shifted_residuals;
152 if (!line_search_succeeded) {
153 keep_going_outer =
false;
154 if (verbose>0) std::cout <<
"Line search failed, so exiting outer loop on proc" << solver->mpi_partition->get_rank_world() << std::endl;
159 if (save_lambda_history && proc0_world) lambda_file.close();
161 delete[] normalized_lambda_grid;
169 void mango::Levenberg_marquardt::line_search() {
170 line_search_succeeded =
false;
172 for (j_line_search = 0; j_line_search < max_line_search_iterations; j_line_search++) {
173 evaluate_on_lambda_grid();
174 process_lambda_grid_results();
176 MPI_Bcast(&line_search_succeeded, 1, MPI_C_BOOL, 0, comm_group_leaders);
177 MPI_Bcast(&keep_going_outer, 1, MPI_C_BOOL, 0, comm_group_leaders);
178 MPI_Bcast(state_vector.data(), N_parameters, MPI_DOUBLE, 0, comm_group_leaders);
186 void mango::Levenberg_marquardt::evaluate_on_lambda_grid() {
187 lambda_scan_residuals = Eigen::MatrixXd::Zero(N_terms, N_line_search);
188 lambda_scan_state_vectors = Eigen::MatrixXd::Zero(N_parameters, N_line_search);
190 for (j_lambda_grid = 0; j_lambda_grid < N_line_search; j_lambda_grid++) {
191 lambda = central_lambda * normalized_lambda_grid[j_lambda_grid];
192 lambdas(j_lambda_grid) = lambda;
194 if ((j_lambda_grid % solver->mpi_partition->get_N_worker_groups()) == solver->mpi_partition->get_rank_group_leaders()) {
195 if (verbose>0) std::cout <<
"Proc " << solver->mpi_partition->get_rank_world() <<
" is handling j_lambda_grid=" << j_lambda_grid
196 <<
", lambda=" << lambda << std::endl;
197 Jacobian_extended.topRows(N_terms) = Jacobian;
198 for (j=0; j<N_parameters; j++) {
199 Jacobian_extended(N_terms+j,j) = sqrt(lambda * Jacobian.col(j).dot(Jacobian.col(j)));
201 if (verbose>0 && proc0_world) std::cout <<
"Here comes Jacobian_extended from Eigen" << std::endl << Jacobian_extended << std::endl;
204 delta_x = -Jacobian_extended.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(residuals_extended);
205 if (verbose>0 && proc0_world) std::cout <<
"Here comes delta_x from Eigen" << std::endl << delta_x << std::endl;
207 if (check_least_squares_solution) {
209 for (j=0; j<N_parameters; j++) {
210 alpha_prime(j,j) = alpha(j,j) * (1 + lambda);
212 delta_x_direct = alpha_prime.colPivHouseholderQr().solve(beta);
213 if (verbose>0 && proc0_world) std::cout <<
"Here comes delta_x_direct from Eigen" << std::endl << delta_x_direct << std::endl;
214 difference = (delta_x - delta_x_direct).norm();
215 if (verbose>0 && proc0_world) std::cout <<
"(delta_x - delta_x_direct).norm() = " << difference << std::endl;
216 if (difference > 1e-10) {
217 std::cerr <<
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
218 std::cerr <<
"WARNING!!! delta_x vs delta_x_norm disagree!!" << std::endl;
219 std::cerr <<
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
223 state_vector_tentative = state_vector + delta_x;
224 lambda_scan_state_vectors.col(j_lambda_grid) = state_vector_tentative;
227 solver->residual_function(&N_parameters, state_vector_tentative.data(), &N_terms, residuals.data(), &failed_int, solver->problem, solver->user_data);
228 lambda_scan_residuals.col(j_lambda_grid) = residuals;
236 MPI_Reduce(MPI_IN_PLACE, lambda_scan_state_vectors.data(), N_line_search * N_parameters, MPI_DOUBLE, MPI_SUM, 0, comm_group_leaders);
237 MPI_Reduce(MPI_IN_PLACE, lambda_scan_residuals.data(), N_line_search * N_terms, MPI_DOUBLE, MPI_SUM, 0, comm_group_leaders);
239 MPI_Reduce(lambda_scan_state_vectors.data(), lambda_scan_state_vectors.data(), N_line_search * N_parameters, MPI_DOUBLE, MPI_SUM, 0, comm_group_leaders);
240 MPI_Reduce(lambda_scan_residuals.data(), lambda_scan_residuals.data(), N_line_search * N_terms, MPI_DOUBLE, MPI_SUM, 0, comm_group_leaders);
248 void mango::Levenberg_marquardt::process_lambda_grid_results() {
249 int original_j_line_search = j_line_search;
254 for (j_lambda_grid = 0; j_lambda_grid < N_line_search; j_lambda_grid++) {
256 solver->record_function_evaluation_pointer(lambda_scan_state_vectors.col(j_lambda_grid).data(), lambda_scan_residuals.col(j_lambda_grid).data(), failed);
258 shifted_residuals = (lambda_scan_residuals.col(j_lambda_grid) - targets).cwiseQuotient(sigmas);
259 tentative_objective_function = shifted_residuals.dot(shifted_residuals);
260 if (verbose>0) std::cout<<
"For j_lambda_grid=" << j_lambda_grid <<
", objective function=" << tentative_objective_function << std::endl;
261 lambda_scan_objective_functions(j_lambda_grid) = tentative_objective_function;
263 if (j_lambda_grid==0) {
264 min_objective_function = tentative_objective_function;
265 min_objective_function_index = 0;
266 }
else if (tentative_objective_function < min_objective_function) {
267 min_objective_function = tentative_objective_function;
268 min_objective_function_index = j_lambda_grid;
271 if (verbose>0 && proc0_world) std::cout <<
"Best j_lambda_grid=" << min_objective_function_index <<
272 ", lambda=" << central_lambda * normalized_lambda_grid[min_objective_function_index] << std::endl;
273 if (min_objective_function < objective_function) {
275 state_vector = lambda_scan_state_vectors.col(min_objective_function_index);
276 objective_function = min_objective_function;
278 central_lambda = central_lambda * normalized_lambda_grid[min_objective_function_index] / lambda_reduction_on_success;
279 if (verbose>0) std::cout <<
"Line search succeeded. New central lambda = " << central_lambda << std::endl;
280 line_search_succeeded =
true;
281 j_line_search = max_line_search_iterations;
284 central_lambda = central_lambda * lambda_increase_factor;
285 if (verbose>0) std::cout <<
"Increasing central lambda to " << central_lambda << std::endl;
288 if (solver->function_evaluations >= solver->max_function_evaluations) {
290 j_line_search = max_line_search_iterations;
291 keep_going_outer =
false;
292 if (verbose>0) std::cout <<
"Maximum number of function evaluations reached." << std::endl;
296 if (save_lambda_history) {
297 lambda_file << std::setw(6) << outer_iteration <<
"," << std::setw(3) << original_j_line_search <<
",";
298 for (
int j=0; j<N_line_search; j++) lambda_file << std::setw(24) << std::setprecision(16) << std::scientific << lambdas(j) <<
",";
299 for (
int j=0; j<N_line_search; j++) lambda_file << std::setw(24) << std::setprecision(16) << std::scientific << lambda_scan_objective_functions(j) <<
",";
300 lambda_file << std::setw(3) << min_objective_function_index <<
", " << std::setw(1) << line_search_succeeded << std::endl << std::flush;
305 MPI_Bcast(¢ral_lambda, 1, MPI_DOUBLE, 0, comm_group_leaders);
306 MPI_Bcast(&j_line_search, 1, MPI_INT, 0, comm_group_leaders);
314 double mango::Levenberg_marquardt::compute_lambda_increase_factor(
const int N_line_search) {
316 double max_lambda_step = 1.0e6;
319 double min_lambda_step = 10.0;
325 return exp(log(max_lambda_step) - (log(max_lambda_step) - log(min_lambda_step)) * (1+factor)/(N_line_search+factor));
334 void mango::Levenberg_marquardt::compute_lambda_grid(
const int N_line_search,
const double lambda_step,
double* lambdas) {
335 double log_lambda_step = log(lambda_step);
336 for (
int j = 0; j < N_line_search; j++) {
337 lambdas[j] = exp(((j+0.5)/N_line_search - 0.5) * log_lambda_step);
341 #endif // MANGO_EIGEN_AVAILABLE