optimize_least_squares_gsl.cpp
Go to the documentation of this file.
1 // Copyright 2019, University of Maryland and the MANGO development team.
2 //
3 // This file is part of MANGO.
4 //
5 // MANGO is free software: you can redistribute it and/or modify it
6 // under the terms of the GNU Lesser General Public License as
7 // published by the Free Software Foundation, either version 3 of the
8 // License, or (at your option) any later version.
9 //
10 // MANGO is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with MANGO. If not, see
17 // <https://www.gnu.org/licenses/>.
18 
19 #include <iostream>
20 #include <cassert>
21 #include <stdexcept>
22 #include "mango.hpp"
23 #include "Least_squares_solver.hpp"
24 #include "Package_gsl.hpp"
25 
26 #ifdef MANGO_GSL_AVAILABLE
27 #include <gsl/gsl_vector.h>
28 #include <gsl/gsl_matrix.h>
29 #include <gsl/gsl_multifit_nlinear.h>
30 #endif
31 
33 #ifdef MANGO_GSL_AVAILABLE
34  if (solver->verbose>0) std::cout << "Hello from optimize_least_squares_gsl" << std::endl;
35 
36  gsl_vector *gsl_residual = gsl_vector_alloc(solver->N_terms);
37  gsl_vector *gsl_state_vector = gsl_vector_alloc(solver->N_parameters);
38  gsl_multifit_nlinear_fdf gsl_optimizer;
39  gsl_multifit_nlinear_parameters gsl_optimizer_params = gsl_multifit_nlinear_default_parameters();
40 
41  gsl_optimizer.f = gsl_residual_function;
42  gsl_optimizer.df = gsl_residual_function_and_Jacobian;
43  //gsl_optimizer.fvv = func_fvv;
44  gsl_optimizer.n = solver->N_terms;
45  gsl_optimizer.p = solver->N_parameters;
46  gsl_optimizer.params = (void*)solver;
47 
48  // Set initial condition
49  for (int j=0; j<solver->N_parameters; j++) gsl_vector_set(gsl_state_vector, j, solver->state_vector[j]);
50 
51  switch (solver->algorithm) {
52  case GSL_LM:
53  gsl_optimizer_params.trs = gsl_multifit_nlinear_trs_lm;
54  break;
55  case GSL_DOGLEG:
56  gsl_optimizer_params.trs = gsl_multifit_nlinear_trs_dogleg;
57  break;
58  case GSL_DDOGLEG:
59  gsl_optimizer_params.trs = gsl_multifit_nlinear_trs_ddogleg;
60  break;
61  case GSL_SUBSPACE2D:
62  gsl_optimizer_params.trs = gsl_multifit_nlinear_trs_subspace2D;
63  break;
64  default:
65  throw std::runtime_error("Error! in optimize_least_squares_gsl.cpp switch! Should not get here!");
66  }
67 
68  // Set other optimizer parameters
69  gsl_optimizer_params.solver = gsl_multifit_nlinear_solver_svd; // This option is described in the documentation as the slowest but most robust for ill-conditioned problems. Since speed is not a major concern outside of the objective function, I'll opt for the extra robustness.
70  const gsl_multifit_nlinear_type *T = gsl_multifit_nlinear_trust;
71  const size_t max_iter = (solver->max_function_evaluations + solver->max_function_and_gradient_evaluations) / 2;
72  const double xtol = 1.0e-8;
73  const double gtol = 1.0e-8;
74  const double ftol = 1.0e-8;
75  gsl_multifit_nlinear_workspace *work = gsl_multifit_nlinear_alloc(T, &gsl_optimizer_params, solver->N_terms, solver->N_parameters);
76  gsl_vector * f = gsl_multifit_nlinear_residual(work);
77  gsl_vector * x = gsl_multifit_nlinear_position(work);
78  int info;
79 
80  // Run the optimization
81  gsl_multifit_nlinear_init(gsl_state_vector, &gsl_optimizer, work);
82  gsl_multifit_nlinear_driver(max_iter, xtol, gtol, ftol, NULL, NULL, &info, work); // NULLs correspond to optional callback function
83 
84  gsl_multifit_nlinear_free(work);
85  gsl_vector_free(gsl_residual);
86  gsl_vector_free(gsl_state_vector);
87  if (solver->verbose>0) std::cout << "Goodbye from optimize_least_squares_gsl" << std::endl;
88 #else
89  throw std::runtime_error("Error! A GSL algorithm was requested, but Mango was compiled without GSL support.");
90 #endif
91 }
92 
93 //////////////////////////////////////////////////////////////////////////////
94 //////////////////////////////////////////////////////////////////////////////
95 #ifdef MANGO_GSL_AVAILABLE
96 
97 int mango::Package_gsl::gsl_residual_function(const gsl_vector * x, void *params, gsl_vector * f) {
98  Least_squares_solver* solver = (Least_squares_solver*)params;
99 
100  if (solver->verbose > 0) std::cout << "Hello from gsl_residual_function. f stride = " << f->stride << std::endl << std::flush;
101 
102  assert(solver->mpi_partition->get_proc0_world()); // This subroutine should only ever be called by proc 0.
103 
104  // gsl vectors have a 'stride'. Only if the stride is 1 does the layout of a gsl vector correspond to a standard double array.
105  // Curran pointed out that the stride for f may not be 1!
106  // See https://github.com/PrincetonUniversity/STELLOPT/commit/5820c453283785ffd97e40aec261ca97f76e9071
107  assert(x->stride == 1);
108 
109  bool failed;
110  solver->residual_function_wrapper(x->data, solver->residuals, &failed);
111 
112  // GSL's definition of the residual function does not include sigmas or targets, so shift and scale the mango residuals appropriately:
113  for (int j=0; j<solver->N_terms; j++) {
114  gsl_vector_set(f, j, (solver->residuals[j] - solver->targets[j]) / solver->sigmas[j]);
115  // Should handle the "failed" case
116  }
117 
118  if (solver->verbose > 0) std::cout << "Goodbye from gsl_residual_function" << std::endl << std::flush;
119  return GSL_SUCCESS;
120 }
121 
122 //////////////////////////////////////////////////////////////////////////////
123 //////////////////////////////////////////////////////////////////////////////
124 
125 int mango::Package_gsl::gsl_residual_function_and_Jacobian (const gsl_vector * x, void *params, gsl_matrix * J) {
126  Least_squares_solver* solver = (Least_squares_solver*)params;
127 
128  if (solver->verbose > 0) std::cout << "Hello from gsl_residual_function_and_Jacobian" << std::endl << std::flush;
129 
130  assert(solver->mpi_partition->get_proc0_world()); // This subroutine should only ever be called by proc 0.
131 
132  // gsl vectors have a 'stride'. Only if the stride is 1 does the layout of a gsl vector correspond to a standard double array.
133  // Curran pointed out that the stride for f may not be 1!
134  // See https://github.com/PrincetonUniversity/STELLOPT/commit/5820c453283785ffd97e40aec261ca97f76e9071
135  assert(x->stride == 1);
136 
137  // For now, I'll allocate and de-allocate memory for the Jacobian on every call to this subroutine. It might be worth modifying things so this allocation is done only once.
138  double* mango_Jacobian = new double[solver->N_parameters * solver->N_terms];
139  solver->finite_difference_Jacobian(x->data, solver->residuals, mango_Jacobian);
140 
141  // It does not seem possible in GSL to get both the residual vector and Jacobian in a single subroutine call!
142  // This is a major inefficiency!!!
143 
144  // Introduce shorthand:
145  int N_parameters = solver->N_parameters;
146  int N_terms = solver->N_terms;
147 
148  // GSL's definition of the residual function does not include sigmas or targets, so scale mango's Jacobian appropriately.
149  // There is probably a faster approach than these explicit loops, but I'll worry about optimizing this later.
150  for (int j_parameter = 0; j_parameter < N_parameters; j_parameter++) {
151  for (int j_term = 0; j_term < N_terms; j_term++) {
152  // row index is before column index in gsl_matrix_set
153  // row index = term, column index = parameter
154  gsl_matrix_set(J, j_term, j_parameter, mango_Jacobian[j_parameter*N_terms+j_term] / solver->sigmas[j_term]);
155  }
156  }
157 
158  delete[] mango_Jacobian;
159 
160  if (solver->verbose > 0) std::cout << "Goodbye from gsl_residual_function_and_Jacobian" << std::endl << std::flush;
161  return GSL_SUCCESS;
162 }
163 
164 #endif
Least_squares_solver.hpp
mango::GSL_DDOGLEG
@ GSL_DDOGLEG
Definition: mango.hpp:110
mango::Least_squares_solver::N_terms
int N_terms
Definition: Least_squares_solver.hpp:38
mango::Least_squares_solver::sigmas
double * sigmas
Definition: Least_squares_solver.hpp:40
mango::Solver::verbose
int verbose
Definition: Solver.hpp:63
Package_gsl.hpp
mango::Least_squares_solver
Definition: Least_squares_solver.hpp:28
mango::GSL_DOGLEG
@ GSL_DOGLEG
Definition: mango.hpp:109
mango::Solver::state_vector
double * state_vector
Definition: Solver.hpp:58
mango::Least_squares_solver::residuals
double * residuals
Definition: Least_squares_solver.hpp:43
mango::Package_gsl::optimize_least_squares
void optimize_least_squares(Least_squares_solver *)
Definition: optimize_least_squares_gsl.cpp:32
mango::Solver::mpi_partition
MPI_Partition * mpi_partition
Definition: Solver.hpp:65
mango::GSL_LM
@ GSL_LM
Definition: mango.hpp:108
mango.hpp
mango::Solver::max_function_evaluations
int max_function_evaluations
Definition: Solver.hpp:62
mango::Solver::N_parameters
int N_parameters
Definition: Solver.hpp:43
mango::Solver::algorithm
algorithm_type algorithm
Definition: Solver.hpp:42
mango::GSL_SUBSPACE2D
@ GSL_SUBSPACE2D
Definition: mango.hpp:111
mango::MPI_Partition::get_proc0_world
bool get_proc0_world()
Determine whether this MPI processor has rank 0 in MANGO's world communicator.
Definition: mpi_partition.cpp:56
mango::Solver::max_function_and_gradient_evaluations
int max_function_and_gradient_evaluations
Definition: Solver.hpp:48
mango::Least_squares_solver::residual_function_wrapper
void residual_function_wrapper(const double *, double *, bool *)
Definition: residual_function_wrapper.cpp:26
mango::Least_squares_solver::targets
double * targets
Definition: Least_squares_solver.hpp:39