source: src/FunctionApproximation/FunctionApproximation.cpp@ f5ea10

Candidate_v1.6.1 ChemicalSpaceEvaluator
Last change on this file since f5ea10 was d3e1d5, checked in by Frederik Heber <frederik.heber@…>, 7 years ago

FIX: unnecessary output iterator present in FunctionaApproximation::evaluate().

  • Property mode set to 100644
File size: 14.0 KB
Line 
1/*
2 * Project: MoleCuilder
3 * Description: creates and alters molecular systems
4 * Copyright (C) 2012 University of Bonn. All rights reserved.
5 * Please see the COPYING file or "Copyright notice" in builder.cpp for details.
6 *
7 *
8 * This file is part of MoleCuilder.
9 *
10 * MoleCuilder is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation, either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * MoleCuilder is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with MoleCuilder. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24/*
25 * FunctionApproximation.cpp
26 *
27 * Created on: 02.10.2012
28 * Author: heber
29 */
30
31// include config.h
32#ifdef HAVE_CONFIG_H
33#include <config.h>
34#endif
35
36//#include "CodePatterns/MemDebug.hpp"
37
38#include "FunctionApproximation.hpp"
39
40#include <algorithm>
41#include <boost/bind.hpp>
42#include <boost/function.hpp>
43#include <iostream>
44#include <iterator>
45#include <numeric>
46#include <sstream>
47
48#include <levmar.h>
49
50#include "CodePatterns/Assert.hpp"
51#include "CodePatterns/Log.hpp"
52
53#include "FunctionApproximation/FunctionModel.hpp"
54#include "FunctionApproximation/TrainingData.hpp"
55
56FunctionApproximation::FunctionApproximation(
57 const TrainingData &_data,
58 FunctionModel &_model,
59 const double _precision,
60 const unsigned int _maxiterations) :
61 input_dimension(_data.getTrainingInputs().size()),
62 output_dimension(_data.getTrainingOutputs().size()),
63 precision(_precision),
64 maxiterations(_maxiterations),
65 input_data(_data.getTrainingInputs()),
66 output_data(_data.getTrainingOutputs()),
67 model(_model)
68{}
69
70void FunctionApproximation::setTrainingData(const filtered_inputs_t &input, const outputs_t &output)
71{
72 ASSERT( input.size() == output.size(),
73 "FunctionApproximation::setTrainingData() - the number of input and output tuples differ: "+toString(input.size())+"!="
74 +toString(output.size())+".");
75 if (input.size() != 0) {
76 ASSERT( input[0].size() == input_dimension,
77 "FunctionApproximation::setTrainingData() - the dimension of the input tuples and input dimension differ: "+toString(input[0].size())+"!="
78 +toString(input_dimension)+".");
79 input_data = input;
80 ASSERT( output[0].size() == output_dimension,
81 "FunctionApproximation::setTrainingData() - the dimension of the output tuples and output dimension differ: "+toString(output[0].size())+"!="
82 +toString(output_dimension)+".");
83 output_data = output;
84 } else {
85 ELOG(2, "Given vectors of training data are empty, clearing internal vectors accordingly.");
86 input_data.clear();
87 output_data.clear();
88 }
89}
90
91void FunctionApproximation::setModelFunction(FunctionModel &_model)
92{
93 model= _model;
94}
95
96/** Callback to circumvent boost::bind, boost::function and function pointer problem.
97 *
98 * See here (second answer!) to the nature of the problem:
99 * http://stackoverflow.com/questions/282372/demote-boostfunction-to-a-plain-function-pointer
100 *
101 * We cannot use a boost::bind bounded boost::function as a function pointer.
102 * boost::function::target() will just return NULL because the signature does not
103 * match. We have to use a C-style callback function and our luck is that
104 * the levmar signature provides for a void* additional data pointer which we
105 * can cast back to our FunctionApproximation class, as we need access to the
106 * data contained, e.g. the FunctionModel reference FunctionApproximation::model.
107 *
108 */
109void FunctionApproximation::LevMarCallback(double *p, double *x, int m, int n, void *data)
110{
111 FunctionApproximation *approximator = static_cast<FunctionApproximation *>(data);
112 ASSERT( approximator != NULL,
113 "LevMarCallback() - received data does not represent a FunctionApproximation object.");
114 boost::function<void(double*,double*,int,int,void*)> function =
115 boost::bind(&FunctionApproximation::evaluate, approximator, _1, _2, _3, _4, _5);
116 function(p,x,m,n,data);
117}
118
119void FunctionApproximation::LevMarDerivativeCallback(double *p, double *x, int m, int n, void *data)
120{
121 FunctionApproximation *approximator = static_cast<FunctionApproximation *>(data);
122 ASSERT( approximator != NULL,
123 "LevMarDerivativeCallback() - received data does not represent a FunctionApproximation object.");
124 boost::function<void(double*,double*,int,int,void*)> function =
125 boost::bind(&FunctionApproximation::evaluateDerivative, approximator, _1, _2, _3, _4, _5);
126 function(p,x,m,n,data);
127}
128
129void FunctionApproximation::prepareParameters(double *&p, int &m) const
130{
131 m = model.getParameterDimension();
132 const FunctionModel::parameters_t params = model.getParameters();
133 {
134 p = new double[m];
135 size_t index = 0;
136 // cannot use std::copy here because of additional dereference
137 for(FunctionModel::parameters_t::const_iterator paramiter = params.begin();
138 paramiter != params.end();
139 ++paramiter, ++index) {
140 p[index] = *paramiter;
141 }
142 }
143}
144
145void FunctionApproximation::prepareOutput(double *&x, int &n) const
146{
147 n = output_data.size();
148 {
149 x = new double[n];
150 size_t index = 0;
151 // cannot use std::copy here because of additional dereference
152 for(outputs_t::const_iterator outiter = output_data.begin();
153 outiter != output_data.end();
154 ++outiter, ++index) {
155 x[index] = (*outiter)[0];
156 }
157 }
158}
159
160void FunctionApproximation::operator()(const enum JacobianMode mode)
161{
162 // let levmar optimize
163 register int i, j;
164 int ret;
165 double *p;
166 double *x;
167 int m, n;
168 double opts[LM_OPTS_SZ], info[LM_INFO_SZ];
169
170 // minim. options [\tau, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu,
171 // * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2.
172 opts[0]=LM_INIT_MU; opts[1]=1e-15; opts[2]=1e-15; opts[3]=precision;
173 opts[4]= LM_DIFF_DELTA; // relevant only if the Jacobian is approximated using finite differences; specifies forward differencing
174 //opts[4]=-LM_DIFF_DELTA; // specifies central differencing to approximate Jacobian; more accurate but more expensive to compute!
175
176 prepareParameters(p,m);
177 prepareOutput(x,n);
178
179 {
180 double *work, *covar;
181 work=(double *)malloc((LM_DIF_WORKSZ(m, n)+m*m)*sizeof(double));
182 if(!work){
183 ELOG(0, "FunctionApproximation::operator() - memory allocation request failed.");
184 return;
185 }
186 covar=work+LM_DIF_WORKSZ(m, n);
187
188 // give this pointer as additional data to construct function pointer in
189 // LevMarCallback and call
190 if (model.isBoxConstraint()) {
191 FunctionModel::parameters_t lowerbound = model.getLowerBoxConstraints();
192 FunctionModel::parameters_t upperbound = model.getUpperBoxConstraints();
193 double *lb = new double[m];
194 double *ub = new double[m];
195 for (size_t i=0;i<(size_t)m;++i) {
196 lb[i] = lowerbound[i];
197 ub[i] = upperbound[i];
198 }
199 if (mode == FiniteDifferences) {
200 ret=dlevmar_bc_dif(
201 &FunctionApproximation::LevMarCallback,
202 p, x, m, n, lb, ub, NULL, 100, opts, info, work, covar, this); // no Jacobian, caller allocates work memory, covariance estimated
203 } else if (mode == ParameterDerivative) {
204 ret=dlevmar_bc_der(
205 &FunctionApproximation::LevMarCallback,
206 &FunctionApproximation::LevMarDerivativeCallback,
207 p, x, m, n, lb, ub, NULL, 100, opts, info, work, covar, this); // no Jacobian, caller allocates work memory, covariance estimated
208 } else {
209 ASSERT(0, "FunctionApproximation::operator() - Unknown jacobian method chosen.");
210 }
211 delete[] lb;
212 delete[] ub;
213 } else {
214 ASSERT(0, "FunctionApproximation::operator() - Unknown jacobian method chosen.");
215 if (mode == FiniteDifferences) {
216 ret=dlevmar_dif(
217 &FunctionApproximation::LevMarCallback,
218 p, x, m, n, 1000, opts, info, work, covar, this); // no Jacobian, caller allocates work memory, covariance estimated
219 } else if (mode == ParameterDerivative) {
220 ret=dlevmar_der(
221 &FunctionApproximation::LevMarCallback,
222 &FunctionApproximation::LevMarDerivativeCallback,
223 p, x, m, n, 1000, opts, info, work, covar, this); // no Jacobian, caller allocates work memory, covariance estimated
224 } else {
225 ASSERT(0, "FunctionApproximation::operator() - Unknown jacobian method chosen.");
226 }
227 }
228
229 {
230 std::stringstream covar_msg;
231 covar_msg << "Covariance of the fit:\n";
232 for(i=0; i<m; ++i){
233 for(j=0; j<m; ++j)
234 covar_msg << covar[i*m+j] << " ";
235 covar_msg << std::endl;
236 }
237 covar_msg << std::endl;
238 LOG(1, "INFO: " << covar_msg.str());
239 }
240
241 free(work);
242 }
243
244 {
245 std::stringstream result_msg;
246 result_msg << "Levenberg-Marquardt returned " << ret << " in " << info[5] << " iter, reason " << info[6] << "\nSolution: ";
247 for(i=0; i<m; ++i)
248 result_msg << p[i] << " ";
249 result_msg << "\n\nMinimization info:\n";
250 std::vector<std::string> infonames(LM_INFO_SZ);
251 infonames[0] = std::string("||e||_2 at initial p");
252 infonames[1] = std::string("||e||_2");
253 infonames[2] = std::string("||J^T e||_inf");
254 infonames[3] = std::string("||Dp||_2");
255 infonames[4] = std::string("mu/max[J^T J]_ii");
256 infonames[5] = std::string("# iterations");
257 infonames[6] = std::string("reason for termination");
258 infonames[7] = std::string(" # function evaluations");
259 infonames[8] = std::string(" # Jacobian evaluations");
260 infonames[9] = std::string(" # linear systems solved");
261 for(i=0; i<LM_INFO_SZ; ++i)
262 result_msg << infonames[i] << ": " << info[i] << " ";
263 result_msg << std::endl;
264 LOG(1, "INFO: " << result_msg.str());
265 }
266
267 delete[] p;
268 delete[] x;
269}
270
271bool FunctionApproximation::checkParameterDerivatives()
272{
273 double *p;
274 int m;
275 const FunctionModel::parameters_t backupparams = model.getParameters();
276 prepareParameters(p,m);
277 int n = output_data.size();
278 double *err = new double[n];
279 dlevmar_chkjac(
280 &FunctionApproximation::LevMarCallback,
281 &FunctionApproximation::LevMarDerivativeCallback,
282 p, m, n, this, err);
283 int i;
284 for(i=0; i<n; ++i)
285 LOG(1, "INFO: gradient " << i << ", err " << err[i] << ".");
286 bool status = true;
287 for(i=0; i<n; ++i)
288 status &= err[i] > 0.5;
289
290 if (!status) {
291 int faulty;
292 ELOG(0, "At least one of the parameter derivatives are incorrect.");
293 for (faulty=1; faulty<=m; ++faulty) {
294 LOG(1, "INFO: Trying with only the first " << faulty << " parameters...");
295 model.setParameters(backupparams);
296 dlevmar_chkjac(
297 &FunctionApproximation::LevMarCallback,
298 &FunctionApproximation::LevMarDerivativeCallback,
299 p, faulty, n, this, err);
300 bool status = true;
301 for(i=0; i<n; ++i)
302 status &= err[i] > 0.5;
303 for(i=0; i<n; ++i)
304 LOG(1, "INFO: gradient(" << faulty << ") " << i << ", err " << err[i] << ".");
305 if (!status)
306 break;
307 }
308 ELOG(0, "The faulty parameter derivative is with respect to the " << faulty << " parameter.");
309 } else
310 LOG(1, "INFO: parameter derivatives are ok.");
311
312 delete[] err;
313 delete[] p;
314 model.setParameters(backupparams);
315
316 return status;
317}
318
319double SquaredDifference(const double res1, const double res2)
320{
321 return (res1-res2)*(res1-res2);
322}
323
324void FunctionApproximation::prepareModel(double *p, int m)
325{
326// ASSERT( (size_t)m == model.getParameterDimension(),
327// "FunctionApproximation::prepareModel() - LevMar expects "+toString(m)
328// +" parameters but the model function expects "+toString(model.getParameterDimension())+".");
329 FunctionModel::parameters_t params(m, 0.);
330 std::copy(p, p+m, params.begin());
331 model.setParameters(params);
332}
333
334void FunctionApproximation::evaluate(double *p, double *x, int m, int n, void *data)
335{
336 // first set parameters
337 prepareModel(p,m);
338
339 // then evaluate
340 ASSERT( (size_t)n == output_data.size(),
341 "FunctionApproximation::evaluate() - LevMar expects "+toString(n)
342 +" outputs but we provide "+toString(output_data.size())+".");
343 if (!output_data.empty()) {
344 filtered_inputs_t::const_iterator initer = input_data.begin();
345// outputs_t::const_iterator outiter = output_data.begin();
346 size_t index = 0;
347 for (; initer != input_data.end(); ++initer /* , ++outiter */) {
348 // result may be a vector, calculate L2 norm
349 const FunctionModel::results_t functionvalue =
350 model(*initer);
351 x[index++] = functionvalue[0];
352// std::vector<double> differences(functionvalue.size(), 0.);
353// std::transform(
354// functionvalue.begin(), functionvalue.end(), outiter->begin(),
355// differences.begin(),
356// &SquaredDifference);
357// x[index] = std::accumulate(differences.begin(), differences.end(), 0.);
358 }
359 }
360}
361
362void FunctionApproximation::evaluateDerivative(double *p, double *jac, int m, int n, void *data)
363{
364 // first set parameters
365 prepareModel(p,m);
366
367 // then evaluate
368 ASSERT( (size_t)n == output_data.size(),
369 "FunctionApproximation::evaluateDerivative() - LevMar expects "+toString(n)
370 +" outputs but we provide "+toString(output_data.size())+".");
371 if (!output_data.empty()) {
372 filtered_inputs_t::const_iterator initer = input_data.begin();
373// outputs_t::const_iterator outiter = output_data.begin();
374 size_t index = 0;
375 for (; initer != input_data.end(); ++initer /*, ++outiter */) {
376 // result may be a vector, calculate L2 norm
377 for (int paramindex = 0; paramindex < m; ++paramindex) {
378 const FunctionModel::results_t functionvalue =
379 model.parameter_derivative(*initer, paramindex);
380 jac[index++] = functionvalue[0];
381 }
382// std::vector<double> differences(functionvalue.size(), 0.);
383// std::transform(
384// functionvalue.begin(), functionvalue.end(), outiter->begin(),
385// differences.begin(),
386// &SquaredDifference);
387// x[index] = std::accumulate(differences.begin(), differences.end(), 0.);
388 }
389 }
390}
Note: See TracBrowser for help on using the repository browser.