SourceXtractorPlusPlus 1.0.3
SourceXtractor++, the next generation SExtractor
Loading...
Searching...
No Matches
GSLEngine.cpp
Go to the documentation of this file.
1
22
25#include <ElementsKernel/Exception.h>
26#include <array>
27#include <chrono>
28#include <gsl/gsl_blas.h>
29#include <gsl/gsl_multifit_nlinear.h>
30#include <iostream>
31
32namespace ModelFitting {
33
34
35static std::shared_ptr<LeastSquareEngine> createGslEngine(unsigned max_iterations) {
36 return std::make_shared<GSLEngine>(max_iterations);
37}
38
40
41GSLEngine::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
48class GslVectorIterator: public std::iterator<std::output_iterator_tag, double> {
49private:
50 gsl_vector *m_v;
51 size_t m_i;
52
53public:
54
55 explicit GslVectorIterator(gsl_vector *v): m_v{v}, m_i{0} {}
56
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
80class GslVectorConstIterator: public std::iterator<std::input_iterator_tag, double> {
81private:
82 const gsl_vector *m_v;
83 size_t m_i;
84
85public:
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
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
T begin(T... args)
Class responsible for managing the parameters the least square engine minimizes.
void updateEngineValues(DoubleIter new_values_iter)
Updates the managed parameters with the given engine values.
std::vector< double > convertCovarianceMatrixToWorldSpace(std::vector< double > covariance_matrix) const
std::size_t numberOfParameters()
Returns the number of parameters managed by the manager.
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
GslVectorConstIterator & operator++()
Definition GSLEngine.cpp:91
GslVectorConstIterator(const GslVectorConstIterator &)=default
GslVectorConstIterator(const gsl_vector *v)
Definition GSLEngine.cpp:87
GslVectorConstIterator operator++(int)
Definition GSLEngine.cpp:96
GslVectorIterator operator++(int)
Definition GSLEngine.cpp:64
GslVectorIterator(const GslVectorIterator &)=default
GslVectorIterator & operator++()
Definition GSLEngine.cpp:59
Provides to the LeastSquareEngine the residual values.
void populateResiduals(DoubleIter output_iter) const
T data(T... args)
T end(T... args)
T make_shared(T... args)
static LeastSquareSummary::StatusFlag getStatusFlag(int ret)
static std::shared_ptr< LeastSquareEngine > createGslEngine(unsigned max_iterations)
Definition GSLEngine.cpp:35
static LeastSquareEngineManager::StaticEngine gsl_engine
Definition GSLEngine.cpp:39
T push_back(T... args)
T size(T... args)
T sqrt(T... args)
Class containing the summary information of solving a least square minimization problem.
StatusFlag status_flag
Flag indicating if the minimization was successful.
size_t iteration_no
The number of iterations.
float duration
Runtime (in seconds)
std::vector< double > parameter_sigmas
1-sigma margin of error for all the parameters
int engine_stop_reason
Engine-specific reason for stopping the fitting.
T tie(T... args)