source: src/FunctionApproximation/FunctionApproximation.cpp@ 4464ef

Action_Thermostats Add_AtomRandomPerturbation Add_RotateAroundBondAction Add_SelectAtomByNameAction Adding_Graph_to_ChangeBondActions Adding_MD_integration_tests Adding_StructOpt_integration_tests Automaking_mpqc_open AutomationFragmentation_failures Candidate_v1.6.0 Candidate_v1.6.1 ChangeBugEmailaddress ChangingTestPorts ChemicalSpaceEvaluator Combining_Subpackages Debian_Package_split Debian_package_split_molecuildergui_only Disabling_MemDebug Docu_Python_wait EmpiricalPotential_contain_HomologyGraph_documentation Enable_parallel_make_install Enhance_userguide Enhanced_StructuralOptimization Enhanced_StructuralOptimization_continued Example_ManyWaysToTranslateAtom Exclude_Hydrogens_annealWithBondGraph FitPartialCharges_GlobalError Fix_ChronosMutex Fix_StatusMsg Fix_StepWorldTime_single_argument Fix_Verbose_Codepatterns ForceAnnealing_goodresults ForceAnnealing_oldresults ForceAnnealing_tocheck ForceAnnealing_with_BondGraph ForceAnnealing_with_BondGraph_continued ForceAnnealing_with_BondGraph_continued_betteresults ForceAnnealing_with_BondGraph_contraction-expansion GeometryObjects Gui_displays_atomic_force_velocity IndependentFragmentGrids_IntegrationTest JobMarket_RobustOnKillsSegFaults JobMarket_StableWorkerPool JobMarket_unresolvable_hostname_fix ODR_violation_mpqc_open PartialCharges_OrthogonalSummation PythonUI_with_named_parameters QtGui_reactivate_TimeChanged_changes Recreated_GuiChecks RotateToPrincipalAxisSystem_UndoRedo StoppableMakroAction Subpackage_levmar Subpackage_vmg ThirdParty_MPQC_rebuilt_buildsystem TremoloParser_IncreasedPrecision TremoloParser_MultipleTimesteps Ubuntu_1604_changes stable
Last change on this file since 4464ef was b40690, checked in by Frederik Heber <heber@…>, 9 years ago

Fit..PotentialAction now allow setting maximum number of optimization iteration.

  • also reduced default value to 100 instead of 1000 for speeding up tests.
  • Property mode set to 100644
File size: 13.9 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 for(FunctionModel::parameters_t::const_iterator paramiter = params.begin();
137 paramiter != params.end();
138 ++paramiter, ++index) {
139 p[index] = *paramiter;
140 }
141 }
142}
143
144void FunctionApproximation::prepareOutput(double *&x, int &n) const
145{
146 n = output_data.size();
147 {
148 x = new double[n];
149 size_t index = 0;
150 for(outputs_t::const_iterator outiter = output_data.begin();
151 outiter != output_data.end();
152 ++outiter, ++index) {
153 x[index] = (*outiter)[0];
154 }
155 }
156}
157
158void FunctionApproximation::operator()(const enum JacobianMode mode)
159{
160 // let levmar optimize
161 register int i, j;
162 int ret;
163 double *p;
164 double *x;
165 int m, n;
166 double opts[LM_OPTS_SZ], info[LM_INFO_SZ];
167
168 // minim. options [\tau, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu,
169 // * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2.
170 opts[0]=LM_INIT_MU; opts[1]=1e-15; opts[2]=1e-15; opts[3]=precision;
171 opts[4]= LM_DIFF_DELTA; // relevant only if the Jacobian is approximated using finite differences; specifies forward differencing
172 //opts[4]=-LM_DIFF_DELTA; // specifies central differencing to approximate Jacobian; more accurate but more expensive to compute!
173
174 prepareParameters(p,m);
175 prepareOutput(x,n);
176
177 {
178 double *work, *covar;
179 work=(double *)malloc((LM_DIF_WORKSZ(m, n)+m*m)*sizeof(double));
180 if(!work){
181 ELOG(0, "FunctionApproximation::operator() - memory allocation request failed.");
182 return;
183 }
184 covar=work+LM_DIF_WORKSZ(m, n);
185
186 // give this pointer as additional data to construct function pointer in
187 // LevMarCallback and call
188 if (model.isBoxConstraint()) {
189 FunctionModel::parameters_t lowerbound = model.getLowerBoxConstraints();
190 FunctionModel::parameters_t upperbound = model.getUpperBoxConstraints();
191 double *lb = new double[m];
192 double *ub = new double[m];
193 for (size_t i=0;i<(size_t)m;++i) {
194 lb[i] = lowerbound[i];
195 ub[i] = upperbound[i];
196 }
197 if (mode == FiniteDifferences) {
198 ret=dlevmar_bc_dif(
199 &FunctionApproximation::LevMarCallback,
200 p, x, m, n, lb, ub, NULL, 100, opts, info, work, covar, this); // no Jacobian, caller allocates work memory, covariance estimated
201 } else if (mode == ParameterDerivative) {
202 ret=dlevmar_bc_der(
203 &FunctionApproximation::LevMarCallback,
204 &FunctionApproximation::LevMarDerivativeCallback,
205 p, x, m, n, lb, ub, NULL, 100, opts, info, work, covar, this); // no Jacobian, caller allocates work memory, covariance estimated
206 } else {
207 ASSERT(0, "FunctionApproximation::operator() - Unknown jacobian method chosen.");
208 }
209 delete[] lb;
210 delete[] ub;
211 } else {
212 ASSERT(0, "FunctionApproximation::operator() - Unknown jacobian method chosen.");
213 if (mode == FiniteDifferences) {
214 ret=dlevmar_dif(
215 &FunctionApproximation::LevMarCallback,
216 p, x, m, n, 1000, opts, info, work, covar, this); // no Jacobian, caller allocates work memory, covariance estimated
217 } else if (mode == ParameterDerivative) {
218 ret=dlevmar_der(
219 &FunctionApproximation::LevMarCallback,
220 &FunctionApproximation::LevMarDerivativeCallback,
221 p, x, m, n, 1000, opts, info, work, covar, this); // no Jacobian, caller allocates work memory, covariance estimated
222 } else {
223 ASSERT(0, "FunctionApproximation::operator() - Unknown jacobian method chosen.");
224 }
225 }
226
227 {
228 std::stringstream covar_msg;
229 covar_msg << "Covariance of the fit:\n";
230 for(i=0; i<m; ++i){
231 for(j=0; j<m; ++j)
232 covar_msg << covar[i*m+j] << " ";
233 covar_msg << std::endl;
234 }
235 covar_msg << std::endl;
236 LOG(1, "INFO: " << covar_msg.str());
237 }
238
239 free(work);
240 }
241
242 {
243 std::stringstream result_msg;
244 result_msg << "Levenberg-Marquardt returned " << ret << " in " << info[5] << " iter, reason " << info[6] << "\nSolution: ";
245 for(i=0; i<m; ++i)
246 result_msg << p[i] << " ";
247 result_msg << "\n\nMinimization info:\n";
248 std::vector<std::string> infonames(LM_INFO_SZ);
249 infonames[0] = std::string("||e||_2 at initial p");
250 infonames[1] = std::string("||e||_2");
251 infonames[2] = std::string("||J^T e||_inf");
252 infonames[3] = std::string("||Dp||_2");
253 infonames[4] = std::string("mu/max[J^T J]_ii");
254 infonames[5] = std::string("# iterations");
255 infonames[6] = std::string("reason for termination");
256 infonames[7] = std::string(" # function evaluations");
257 infonames[8] = std::string(" # Jacobian evaluations");
258 infonames[9] = std::string(" # linear systems solved");
259 for(i=0; i<LM_INFO_SZ; ++i)
260 result_msg << infonames[i] << ": " << info[i] << " ";
261 result_msg << std::endl;
262 LOG(1, "INFO: " << result_msg.str());
263 }
264
265 delete[] p;
266 delete[] x;
267}
268
269bool FunctionApproximation::checkParameterDerivatives()
270{
271 double *p;
272 int m;
273 const FunctionModel::parameters_t backupparams = model.getParameters();
274 prepareParameters(p,m);
275 int n = output_data.size();
276 double *err = new double[n];
277 dlevmar_chkjac(
278 &FunctionApproximation::LevMarCallback,
279 &FunctionApproximation::LevMarDerivativeCallback,
280 p, m, n, this, err);
281 int i;
282 for(i=0; i<n; ++i)
283 LOG(1, "INFO: gradient " << i << ", err " << err[i] << ".");
284 bool status = true;
285 for(i=0; i<n; ++i)
286 status &= err[i] > 0.5;
287
288 if (!status) {
289 int faulty;
290 ELOG(0, "At least one of the parameter derivatives are incorrect.");
291 for (faulty=1; faulty<=m; ++faulty) {
292 LOG(1, "INFO: Trying with only the first " << faulty << " parameters...");
293 model.setParameters(backupparams);
294 dlevmar_chkjac(
295 &FunctionApproximation::LevMarCallback,
296 &FunctionApproximation::LevMarDerivativeCallback,
297 p, faulty, n, this, err);
298 bool status = true;
299 for(i=0; i<n; ++i)
300 status &= err[i] > 0.5;
301 for(i=0; i<n; ++i)
302 LOG(1, "INFO: gradient(" << faulty << ") " << i << ", err " << err[i] << ".");
303 if (!status)
304 break;
305 }
306 ELOG(0, "The faulty parameter derivative is with respect to the " << faulty << " parameter.");
307 } else
308 LOG(1, "INFO: parameter derivatives are ok.");
309
310 delete[] err;
311 delete[] p;
312 model.setParameters(backupparams);
313
314 return status;
315}
316
317double SquaredDifference(const double res1, const double res2)
318{
319 return (res1-res2)*(res1-res2);
320}
321
322void FunctionApproximation::prepareModel(double *p, int m)
323{
324// ASSERT( (size_t)m == model.getParameterDimension(),
325// "FunctionApproximation::prepareModel() - LevMar expects "+toString(m)
326// +" parameters but the model function expects "+toString(model.getParameterDimension())+".");
327 FunctionModel::parameters_t params(m, 0.);
328 std::copy(p, p+m, params.begin());
329 model.setParameters(params);
330}
331
332void FunctionApproximation::evaluate(double *p, double *x, int m, int n, void *data)
333{
334 // first set parameters
335 prepareModel(p,m);
336
337 // then evaluate
338 ASSERT( (size_t)n == output_data.size(),
339 "FunctionApproximation::evaluate() - LevMar expects "+toString(n)
340 +" outputs but we provide "+toString(output_data.size())+".");
341 if (!output_data.empty()) {
342 filtered_inputs_t::const_iterator initer = input_data.begin();
343 outputs_t::const_iterator outiter = output_data.begin();
344 size_t index = 0;
345 for (; initer != input_data.end(); ++initer, ++outiter) {
346 // result may be a vector, calculate L2 norm
347 const FunctionModel::results_t functionvalue =
348 model(*initer);
349 x[index++] = functionvalue[0];
350// std::vector<double> differences(functionvalue.size(), 0.);
351// std::transform(
352// functionvalue.begin(), functionvalue.end(), outiter->begin(),
353// differences.begin(),
354// &SquaredDifference);
355// x[index] = std::accumulate(differences.begin(), differences.end(), 0.);
356 }
357 }
358}
359
360void FunctionApproximation::evaluateDerivative(double *p, double *jac, int m, int n, void *data)
361{
362 // first set parameters
363 prepareModel(p,m);
364
365 // then evaluate
366 ASSERT( (size_t)n == output_data.size(),
367 "FunctionApproximation::evaluateDerivative() - LevMar expects "+toString(n)
368 +" outputs but we provide "+toString(output_data.size())+".");
369 if (!output_data.empty()) {
370 filtered_inputs_t::const_iterator initer = input_data.begin();
371 outputs_t::const_iterator outiter = output_data.begin();
372 size_t index = 0;
373 for (; initer != input_data.end(); ++initer, ++outiter) {
374 // result may be a vector, calculate L2 norm
375 for (int paramindex = 0; paramindex < m; ++paramindex) {
376 const FunctionModel::results_t functionvalue =
377 model.parameter_derivative(*initer, paramindex);
378 jac[index++] = functionvalue[0];
379 }
380// std::vector<double> differences(functionvalue.size(), 0.);
381// std::transform(
382// functionvalue.begin(), functionvalue.end(), outiter->begin(),
383// differences.begin(),
384// &SquaredDifference);
385// x[index] = std::accumulate(differences.begin(), differences.end(), 0.);
386 }
387 }
388}
Note: See TracBrowser for help on using the repository browser.