SourceXtractorPlusPlus  0.19
SourceXtractor++, the next generation SExtractor
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
GSLEngine.cpp
Go to the documentation of this file.
1 
26 #include <array>
27 #include <chrono>
28 #include <gsl/gsl_blas.h>
29 #include <gsl/gsl_multifit_nlinear.h>
30 #include <iostream>
31 
32 namespace ModelFitting {
33 
34 
35 static std::shared_ptr<LeastSquareEngine> createGslEngine(unsigned max_iterations) {
36  return std::make_shared<GSLEngine>(max_iterations);
37 }
38 
40 
41 GSLEngine::GSLEngine(int itmax, double xtol, double gtol, double ftol, double delta):
42  m_itmax{itmax}, m_xtol{xtol}, m_gtol{gtol}, m_ftol{ftol}, m_delta{delta} {
43  // Prevent an abort() call from GSL, we handle the return code ourselves
44  gsl_set_error_handler_off();
45 }
46 
47 // Provide an iterator for gsl_vector
48 class GslVectorIterator: public std::iterator<std::output_iterator_tag, double> {
49 private:
50  gsl_vector *m_v;
51  size_t m_i;
52 
53 public:
54 
55  explicit GslVectorIterator(gsl_vector *v): m_v{v}, m_i{0} {}
56 
57  GslVectorIterator(const GslVectorIterator&) = default;
58 
60  ++m_i;
61  return *this;
62  }
63 
65  auto c = GslVectorIterator(*this);
66  ++m_i;
67  return c;
68  }
69 
70  double operator*() const {
71  return gsl_vector_get(m_v, m_i);
72  }
73 
74  double &operator*() {
75  return *gsl_vector_ptr(m_v, m_i);
76  }
77 };
78 
79 // Provide a const iterator for gsl_vector
80 class GslVectorConstIterator: public std::iterator<std::input_iterator_tag, double> {
81 private:
82  const gsl_vector *m_v;
83  size_t m_i;
84 
85 public:
86 
87  explicit GslVectorConstIterator(const gsl_vector *v): m_v{v}, m_i{0} {}
88 
90 
92  ++m_i;
93  return *this;
94  }
95 
97  auto c = GslVectorConstIterator(*this);
98  ++m_i;
99  return c;
100  }
101 
102  double operator*() const {
103  return gsl_vector_get(m_v, m_i);
104  }
105 };
106 
108  switch (ret) {
109  case GSL_SUCCESS:
111  case GSL_EMAXITER:
113  default:
115  }
116 }
117 
119  ModelFitting::ResidualEstimator& residual_estimator) {
120  // Create a tuple which keeps the references to the given manager and estimator
121  // If we capture, we can not use the lambda for the function pointer
122  auto adata = std::tie(parameter_manager, residual_estimator);
123 
124  // Only type supported by GSL
125  const gsl_multifit_nlinear_type *type = gsl_multifit_nlinear_trust;
126 
127  // Initialize parameters
128  // NOTE: These settings are set from experimenting with the examples/runs, and are those
129  // that match closer Levmar residuals, without increasing runtime too much
130  gsl_multifit_nlinear_parameters params = gsl_multifit_nlinear_default_parameters();
131  // gsl_multifit_nlinear_trs_lm is the only one that converges reasonably fast
132  // gsl_multifit_nlinear_trs_lmaccel *seems* to be faster when fitting a single source, but turns out to be slower
133  // when fitting a whole image
134  params.trs = gsl_multifit_nlinear_trs_lm;
135  // This is the only scale method that converges reasonably in SExtractor++
136  // When using gsl_multifit_nlinear_scale_more or gsl_multifit_nlinear_scale_marquardt the results are *very* bad
137  params.scale = gsl_multifit_nlinear_scale_levenberg;
138  // Others work too, but this one is faster
139  params.solver = gsl_multifit_nlinear_solver_cholesky;
140  // This is the default, and requires half the operations than GSL_MULTIFIT_NLINEAR_CTRDIFF
141  params.fdtype = GSL_MULTIFIT_NLINEAR_FWDIFF;
142  // Passed via constructor
143  params.h_df = m_delta;
144 
145  // Allocate the workspace for the resolver. It may return null if there is no memory.
146  gsl_multifit_nlinear_workspace *workspace = gsl_multifit_nlinear_alloc(
147  type, &params,
148  residual_estimator.numberOfResiduals(), parameter_manager.numberOfParameters()
149  );
150  if (workspace == nullptr) {
151  throw Elements::Exception() << "Insufficient memory for initializing the GSL solver";
152  }
153 
154  // Allocate space for the parameters and initialize with the guesses
155  std::vector<double> param_values(parameter_manager.numberOfParameters());
156  parameter_manager.getEngineValues(param_values.begin());
157  gsl_vector_view gsl_param_view = gsl_vector_view_array(param_values.data(), param_values.size());
158 
159  // Function to be minimized
160  auto function = [](const gsl_vector *x, void *extra, gsl_vector *f) -> int {
161  auto *extra_ptr = (decltype(adata) *) extra;
162  EngineParameterManager& pm = std::get<0>(*extra_ptr);
164  ResidualEstimator& re = std::get<1>(*extra_ptr);
166  return GSL_SUCCESS;
167  };
168  gsl_multifit_nlinear_fdf fdf;
169  fdf.f = function;
170  fdf.df = nullptr;
171  fdf.fvv = nullptr;
172  fdf.n = residual_estimator.numberOfResiduals();
173  fdf.p = parameter_manager.numberOfParameters();
174  fdf.params = &adata;
175 
176  // Setup the solver
177  gsl_multifit_nlinear_init(&gsl_param_view.vector, &fdf, workspace);
178 
179  // Initial cost
180  double chisq0;
181  gsl_vector *residual = gsl_multifit_nlinear_residual(workspace);
182  gsl_blas_ddot(residual, residual, &chisq0);
183 
184  // Solve
185  auto start = std::chrono::steady_clock::now();
186  int info = 0;
187  int ret = gsl_multifit_nlinear_driver(
188  m_itmax, // Maximum number of iterations
189  m_xtol, // Tolerance for the step
190  m_gtol, // Tolerance for the gradient
191  m_ftol, // Tolerance for chi^2 (may be unused)
192  nullptr, // Callback
193  nullptr, // Callback parameters
194  &info, // Convergence information if GSL_SUCCESS
195  workspace // The solver workspace
196  );
198  std::chrono::duration<float> elapsed = end - start;
199 
200  // Final cost
201  double chisq;
202  gsl_blas_ddot(residual, residual, &chisq);
203 
204  // Build run summary
205  std::vector<double> covariance_matrix(
206  parameter_manager.numberOfParameters() * parameter_manager.numberOfParameters());
207 
208  LeastSquareSummary summary;
209  summary.status_flag = getStatusFlag(ret);
210  summary.engine_stop_reason = ret;
211  summary.iteration_no = gsl_multifit_nlinear_niter(workspace);
212  summary.parameter_sigmas = {};
213  summary.duration = elapsed.count();
214 
215  // Covariance matrix. Note: Do not free J! It is owned by the workspace.
216  gsl_matrix *J = gsl_multifit_nlinear_jac(workspace);
217  gsl_matrix_view covar = gsl_matrix_view_array(covariance_matrix.data(), parameter_manager.numberOfParameters(),
218  parameter_manager.numberOfParameters());
219  gsl_multifit_nlinear_covar(J, 0.0, &covar.matrix);
220 
221  // We have done an unweighted minimization, so, from the documentation, we need
222  // to multiply the covariance matrix by the variance of the residuals
223  // See: https://www.gnu.org/software/gsl/doc/html/nls.html#covariance-matrix-of-best-fit-parameters
224  double sigma2 = 0;
225  for (size_t i = 0; i < residual->size; ++i) {
226  auto v = gsl_vector_get(residual, i);
227  sigma2 += v * v;
228  }
229  sigma2 /= (fdf.n - fdf.p);
230 
231  for (auto ci = covariance_matrix.begin(); ci != covariance_matrix.end(); ++ci) {
232  *ci *= sigma2;
233  }
234 
235  auto converted_covariance_matrix = parameter_manager.convertCovarianceMatrixToWorldSpace(covariance_matrix);
236  for (unsigned int i=0; i<parameter_manager.numberOfParameters(); i++) {
237  summary.parameter_sigmas.push_back(sqrt(converted_covariance_matrix[i*(parameter_manager.numberOfParameters()+1)]));
238  }
239 
240  // Levmar-compatible engine info
241  int levmar_reason = 0;
242  if (ret == GSL_SUCCESS) {
243  levmar_reason = (info == 1) ? 2 : 1;
244  }
245  else if (ret == GSL_EMAXITER) {
246  levmar_reason = 3;
247  }
248 
249  summary.underlying_framework_info = std::array<double, 10>{
250  chisq0, // ||e||_2 at initial p
251  chisq, // ||e||_2
252  gsl_blas_dnrm2(workspace->g), // ||J^T e||_inf
253  gsl_blas_dnrm2(workspace->dx), // ||Dp||_2
254  0., // mu/max[J^T J]_ii
255  static_cast<double>(summary.iteration_no), // Number of iterations
256  static_cast<double>(levmar_reason), // Reason for finishing
257  static_cast<double>(fdf.nevalf), // Function evaluations
258  static_cast<double>(fdf.nevaldf), // Jacobian evaluations
259  0. // Linear systems solved
260  };
261 
262  // Free resources and return
263  gsl_multifit_nlinear_free(workspace);
264  return summary;
265 }
266 
267 } // end namespace ModelFitting
GslVectorConstIterator(const gsl_vector *v)
Definition: GSLEngine.cpp:87
T tie(T...args)
GslVectorIterator & operator++()
Definition: GSLEngine.cpp:59
Class containing the summary information of solving a least square minimization problem.
void populateResiduals(DoubleIter output_iter) const
void getEngineValues(DoubleIter output_iter) const
Returns the engine values of the managed parameters.
GSLEngine(int itmax=1000, double xtol=1e-8, double gtol=1e-8, double ftol=1e-8, double delta=1e-4)
Constructs a new instance of the engine.
Definition: GSLEngine.cpp:41
LeastSquareSummary solveProblem(EngineParameterManager &parameter_manager, ResidualEstimator &residual_estimator) override
Definition: GSLEngine.cpp:118
T end(T...args)
GslVectorConstIterator & operator++()
Definition: GSLEngine.cpp:91
void updateEngineValues(DoubleIter new_values_iter)
Updates the managed parameters with the given engine values.
T push_back(T...args)
std::size_t numberOfResiduals() const
static LeastSquareEngineManager::StaticEngine gsl_engine
Definition: GSLEngine.cpp:39
GslVectorIterator(gsl_vector *v)
Definition: GSLEngine.cpp:55
GslVectorIterator operator++(int)
Definition: GSLEngine.cpp:64
STL class.
Class responsible for managing the parameters the least square engine minimizes.
std::vector< double > convertCovarianceMatrixToWorldSpace(std::vector< double > covariance_matrix) const
static std::shared_ptr< LeastSquareEngine > createGslEngine(unsigned max_iterations)
Definition: GSLEngine.cpp:35
T sqrt(T...args)
GslVectorConstIterator operator++(int)
Definition: GSLEngine.cpp:96
Provides to the LeastSquareEngine the residual values.
static LeastSquareSummary::StatusFlag getStatusFlag(int ret)
Definition: GSLEngine.cpp:107
std::size_t numberOfParameters()
Returns the number of parameters managed by the manager.