source: src/Potentials/PartialNucleiChargeFitter.cpp@ 407d2c

FitPartialCharges_GlobalError PartialCharges_OrthogonalSummation
Last change on this file since 407d2c was 407d2c, checked in by Frederik Heber <heber@…>, 8 years ago

FIX: PartialNucleiChargeFitter needs to reconstruct matrix after SVD.

  • SVD overwrites original matrix and this causes the residuum to be calculated wrong.
  • Property mode set to 100644
File size: 14.2 KB
Line 
1/*
2 * Project: MoleCuilder
3 * Description: creates and alters molecular systems
4 * Copyright (C) 2013 Frederik Heber. All rights reserved.
5 * Please see the LICENSE 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 * PartialNucleiChargeFitter.cpp
26 *
27 * Created on: 12.05.2013
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 "PartialNucleiChargeFitter.hpp"
39
40#include <cmath>
41#include <fstream>
42#include <limits>
43#include <numeric>
44
45#include "LinearAlgebra/MatrixContent.hpp"
46#include "LinearAlgebra/VectorContent.hpp"
47
48#include "CodePatterns/Assert.hpp"
49#include "CodePatterns/Log.hpp"
50
51#include "Fragmentation/Summation/SetValues/SamplingGrid.hpp"
52
53PartialNucleiChargeFitter::dimensions_t
54PartialNucleiChargeFitter::getGridDimensions(const SamplingGrid &grid) const
55{
56 // convert sampled potential into a vector
57 const double round_offset =
58 (std::numeric_limits<size_t>::round_style == std::round_toward_zero) ?
59 0.5 : 0.; // need offset to get to round_toward_nearest behavior
60 dimensions_t total(3,0);
61 for(size_t index=0;index<3;++index) {
62 const double delta = grid.getDeltaPerAxis(index);
63 // delta is conversion factor from box length to discrete length, i.e. number of points
64 total[index] = (grid.end[index] - grid.begin[index])/delta+round_offset;
65 }
66 return total;
67}
68
69PartialNucleiChargeFitter::PartialNucleiChargeFitter(
70 const SamplingGrid &grid,
71 const positions_t &_positions,
72 const double _threshold) :
73 total(getGridDimensions(grid)),
74 SampledPotential(std::accumulate(total.begin(), total.end(), 1, std::multiplies<double>())),
75 grid_properties(static_cast<const SamplingGridProperties &>(grid)),
76 positions(_positions),
77 PotentialFromCharges(NULL),
78 PartialCharges(NULL),
79 threshold(_threshold)
80{
81 // we must take care of the "window", i.e. there may be less entries in sampled_grid
82 // vector as we would expect from size of grid ((2^level)^3) as 0-entries have been
83 // omitted.
84 size_t pre_offset[3];
85 size_t post_offset[3];
86 size_t length[3];
87 size_t total[3];
88 grid.getDiscreteWindowCopyIndices(
89 grid.begin, grid.end,
90 grid.begin_window, grid.end_window,
91 pre_offset,
92 post_offset,
93 length,
94 total
95 );
96 const size_t calculated_size = length[0]*length[1]*length[2];
97 ASSERT( calculated_size == grid.sampled_grid.size(),
98 "PartialNucleiChargeFitter::PartialNucleiChargeFitter() - grid does not match size indicated by its window.");
99
100 double minimum = std::numeric_limits<double>::max();
101 double maximum = std::numeric_limits<double>::min();
102 double average = 0.;
103 for (SamplingGrid::sampledvalues_t::const_iterator iter = grid.sampled_grid.begin();
104 iter != grid.sampled_grid.end(); ++iter) {
105 minimum = std::min(minimum, *iter);
106 maximum = std::max(maximum, *iter);
107 average += *iter;
108 }
109 LOG(2, "DEBUG: Max over grid is " << maximum
110 << ", minimum is " << minimum
111 << ", and average is " << average/(double)grid.sampled_grid.size());
112
113 const double potential_sum = std::accumulate(grid.sampled_grid.begin(), grid.sampled_grid.end(), 0.);
114 if ( fabs(potential_sum) > std::numeric_limits<double>::epsilon()*1e4 ) {
115 ELOG(2, "Potential sum is not less than "
116 << std::numeric_limits<double>::epsilon()*1e4 << " but "
117 << potential_sum << ".");
118 }
119
120 SamplingGrid::sampledvalues_t::const_iterator griditer = grid.sampled_grid.begin();
121 size_t index=0;
122 size_t N[3];
123 Vector grid_position; // position of grid point in real domain
124 size_t masked_points = 0;
125 // store step length per axis
126 double delta[3];
127 for (size_t i=0;i<3;++i)
128 delta[i] = grid_properties.getDeltaPerAxis(i);
129 /// convert sampled potential into a vector
130 grid_position[0] = grid_properties.begin[0];
131 for(N[0]=0; N[0] < pre_offset[0]; ++N[0]) {
132 grid_position[1] = grid_properties.begin[1];
133 for(N[1]=0; N[1] < total[1]; ++N[1]) {
134 grid_position[2] = grid_properties.begin[2];
135 for(N[2]=0; N[2] < total[2]; ++N[2]) {
136 const_cast<VectorContent &>(SampledPotential)[index++] = 0.;
137 grid_position[2] += delta[2];
138 }
139 grid_position[1] += delta[1];
140 }
141 grid_position[0] += delta[0];
142 }
143 for(N[0]=0; N[0] < length[0]; ++N[0]) {
144 grid_position[1] = grid_properties.begin[1];
145 for(N[1]=0; N[1] < pre_offset[1]; ++N[1]) {
146 grid_position[2] = grid_properties.begin[2];
147 for(N[2]=0; N[2] < total[2]; ++N[2]) {
148 const_cast<VectorContent &>(SampledPotential)[index++] = 0.;
149 grid_position[2] += delta[2];
150 }
151 grid_position[1] += delta[1];
152 }
153 for(N[1]=0; N[1] < length[1]; ++N[1]) {
154 grid_position[2] = grid_properties.begin[2];
155 for(N[2]=0; N[2] < pre_offset[2]; ++N[2]) {
156 const_cast<VectorContent &>(SampledPotential)[index++] = 0.;
157 grid_position[2] += delta[2];
158 }
159 for(N[2]=0; N[2] < length[2]; ++N[2]) {
160 if (isGridPointSettable(positions, grid_position))
161 const_cast<VectorContent &>(SampledPotential)[index++] = *griditer++;
162 else {
163 // skip point
164 ++griditer;
165 ++masked_points;
166 const_cast<VectorContent &>(SampledPotential)[index++] = 0.;
167 }
168 grid_position[2] += delta[2];
169 }
170 for(N[2]=0; N[2] < post_offset[2]; ++N[2]) {
171 const_cast<VectorContent &>(SampledPotential)[index++] = 0.;
172 grid_position[2] += delta[2];
173 }
174 grid_position[1] += delta[1];
175 }
176 for(N[1]=0; N[1] < post_offset[1]; ++N[1]) {
177 grid_position[2] = grid_properties.begin[2];
178 for(N[2]=0; N[2] < total[2]; ++N[2]) {
179 const_cast<VectorContent &>(SampledPotential)[index++] = 0.;
180 grid_position[2] += delta[2];
181 }
182 grid_position[1] += delta[1];
183 }
184 grid_position[0] += delta[0];
185 }
186 for(N[0]=0; N[0] < post_offset[0]; ++N[0]) {
187 grid_position[1] = grid_properties.begin[1];
188 for(N[1]=0; N[1] < total[1]; ++N[1]) {
189 grid_position[2] = grid_properties.begin[2];
190 for(N[2]=0; N[2] < total[2]; ++N[2]) {
191 const_cast<VectorContent &>(SampledPotential)[index++] = 0.;
192 grid_position[2] += delta[2];
193 }
194 grid_position[1] += delta[1];
195 }
196 grid_position[0] += delta[0];
197 }
198 // set remainder of points to zero
199 ASSERT( index == SampledPotential.getDimension(),
200 "PartialNucleiChargeFitter::PartialNucleiChargeFitter() - not enough or more than calculated sample points.");
201
202#ifndef NDEBUG
203 // write vector as paraview csv file file
204 {
205 size_t N[3];
206 size_t index = 0;
207 std::ofstream paraview_output("solution.csv");
208 paraview_output << "x coord,y coord,z coord,scalar" << std::endl;
209 for(N[0]=0; N[0] < total[0]; ++N[0]) {
210 for(N[1]=0; N[1] < total[1]; ++N[1]) {
211 for(N[2]=0; N[2] < total[2]; ++N[2]) {
212 paraview_output
213 << (double)N[0]/(double)total[0] << ","
214 << (double)N[1]/(double)total[1] << ","
215 << (double)N[2]/(double)total[2] << ","
216 << SampledPotential.at(index++) << std::endl;
217 }
218 }
219 }
220 paraview_output.close();
221 }
222#endif
223
224 LOG(1, "INFO: I masked " << masked_points << " points in right-hand-side.");
225// LOG(4, "DEBUG: Right-hand side vector is " << SampledPotential << ".");
226}
227
228bool PartialNucleiChargeFitter::isGridPointSettable(
229 const positions_t &_positions,
230 const Vector &grid_position) const
231{
232 bool status = true;
233 for (positions_t::const_iterator iter = _positions.begin();
234 iter != _positions.end(); ++iter) {
235 status &= grid_position.DistanceSquared(*iter) > threshold*threshold;
236 }
237 return status;
238}
239
240PartialNucleiChargeFitter::~PartialNucleiChargeFitter()
241{
242 if (PartialCharges != NULL)
243 delete PartialCharges;
244
245 if (PotentialFromCharges != NULL)
246 delete PotentialFromCharges;
247}
248
249
250void PartialNucleiChargeFitter::constructMatrix()
251{
252 const size_t rows = SampledPotential.getDimension();
253 const size_t cols = positions.size();
254
255 // allocate memory for PotentialFromCharges
256 if (PotentialFromCharges != NULL) {
257 delete PotentialFromCharges;
258 PotentialFromCharges = NULL;
259 }
260 PotentialFromCharges = new MatrixContent( rows, cols );
261 // store step length per axis
262 double delta[3];
263 for (size_t i=0;i<3;++i)
264 delta[i] = grid_properties.getDeltaPerAxis(i);
265 // then for each charge ...
266 size_t masked_points = 0;
267 for (size_t nuclei_index = 0; nuclei_index < cols; ++nuclei_index) {
268 // ... calculate potential at each grid position,
269 // i.e. step through grid and calculate distance to charge position
270 Vector grid_position; // position of grid point in real domain
271 grid_position[0] = grid_properties.begin[0];
272 size_t N[3]; // discrete grid position
273 size_t index = 0; // component of column vector
274 for(N[0]=0; N[0] < total[0]; ++N[0]) {
275 grid_position[1] = grid_properties.begin[1];
276 for(N[1]=0; N[1] < total[1]; ++N[1]) {
277 grid_position[2] = grid_properties.begin[2];
278 for(N[2]=0; N[2] < total[2]; ++N[2]) {
279 if (isGridPointSettable(positions, grid_position)) {
280 const double distance = positions[nuclei_index].distance(grid_position);
281 ASSERT( distance >= 0,
282 "PartialNucleiChargeFitter::constructMatrix() - distance is negative?");
283 // Coulomb's constant is 1 in atomic units, see http://en.wikipedia.org/wiki/Atomic_units
284 const double epsilon0_au = 1.; //4.*M_PI*0.007957747154594767;
285 // ... with epsilon_0 in atom units from http://folk.uio.no/michalj/node72.html
286 const double value = 1./(epsilon0_au*distance);
287 PotentialFromCharges->at(index++, nuclei_index) = value;
288 } else {
289 ++masked_points;
290 PotentialFromCharges->at(index++, nuclei_index) = 0.;
291 }
292 grid_position[2] += delta[2];
293 }
294 grid_position[1] += delta[1];
295 }
296 grid_position[0] += delta[0];
297 }
298 ASSERT( index == PotentialFromCharges->getRows(),
299 "PartialNucleiChargeFitter::operator() - number of sampled positions "
300 +toString(index)+" unequal to set rows "
301 +toString(PotentialFromCharges->getRows())+".");
302 }
303
304 LOG(1, "INFO: I masked " << masked_points/cols << " points in matrix.");
305}
306
307double PartialNucleiChargeFitter::operator()()
308{
309 // prepare PartialCharges
310 if (PartialCharges != NULL) {
311 delete PartialCharges;
312 PartialCharges = NULL;
313 }
314 PartialCharges = new VectorContent(positions.size());
315
316 // set up over-determined system's problem matrix A for Ax=b
317 // i.e. columns represent potential of a single charge at grid positions
318 constructMatrix();
319
320 // solve for x
321 *PartialCharges =
322 PotentialFromCharges->solveOverdeterminedLinearEquation(
323 SampledPotential);
324
325// LOG(4, "DEBUG: Solution vector is " << (*PotentialFromCharges) * (*PartialCharges) << ".");
326
327 LOG(2, "DEBUG: Norm of right-hand side is " << SampledPotential.Norm());
328
329 // need to reconstruct matrix as it was overwritten by SVD
330 constructMatrix();
331
332 // calculate residual vector
333 VectorContent residuum = (*PotentialFromCharges) * (*PartialCharges) - SampledPotential;
334
335#ifndef NDEBUG
336 // write solution to file
337 writeMatrix();
338
339 // write vector as paraview csv file file
340 {
341 size_t N[3];
342 size_t index = 0;
343 std::ofstream paraview_output("residuum.csv");
344 paraview_output << "x coord,y coord,z coord,scalar" << std::endl;
345 for(N[0]=0; N[0] < total[0]; ++N[0]) {
346 for(N[1]=0; N[1] < total[1]; ++N[1]) {
347 for(N[2]=0; N[2] < total[2]; ++N[2]) {
348 paraview_output
349 << (double)N[0]/(double)total[0] << ","
350 << (double)N[1]/(double)total[1] << ","
351 << (double)N[2]/(double)total[2] << ","
352 << residuum.at(index++) << std::endl;
353 }
354 }
355 }
356 paraview_output.close();
357 }
358#endif
359
360 // calculate L1 and L2 errors
361 double residuum_l1 = 0.;
362 for (size_t i=0; i< residuum.getDimension(); ++i)
363 if (residuum_l1 < residuum[i])
364 residuum_l1 = residuum[i];
365 LOG(1, "INFO: L2-Norm of residuum is " << residuum.Norm() << ".");
366 LOG(1, "INFO: L1-Norm of residuum is " << residuum_l1 << ".");
367
368 return residuum.Norm();
369}
370
371void PartialNucleiChargeFitter::writeMatrix()
372{
373 // only construct if not yet present
374 if (PotentialFromCharges == NULL)
375 constructMatrix();
376
377 // write matrix as paraview csv file file
378 size_t N[3];
379 size_t index=0;
380 std::string filename = std::string("potential.csv");
381 std::ofstream paraview_output(filename.c_str());
382 paraview_output << "x coord,y coord,z coord,scalar" << std::endl;
383 for(N[0]=0; N[0] < total[0]; ++N[0]) {
384 for(N[1]=0; N[1] < total[1]; ++N[1]) {
385 for(N[2]=0; N[2] < total[2]; ++N[2]) {
386 double sum = 0.;
387 for (size_t nuclei_index = 0; nuclei_index < positions.size(); ++nuclei_index) {
388 sum+= PotentialFromCharges->at(index, nuclei_index)*PartialCharges->at(nuclei_index);
389 }
390 paraview_output
391 << (double)N[0]/(double)total[0] << ","
392 << (double)N[1]/(double)total[1] << ","
393 << (double)N[2]/(double)total[2] << ","
394 << sum << std::endl;
395 index++;
396 }
397 }
398 }
399 paraview_output.close();
400}
401
402PartialNucleiChargeFitter::charges_t
403PartialNucleiChargeFitter::getSolutionAsCharges_t() const
404{
405 ASSERT( PartialCharges != NULL,
406 "PartialNucleiChargeFitter::getSolutionAsCharges_t() - PartialCharges requested prior to calculation.");
407 charges_t return_charges(positions.size(), 0.);
408 for (size_t i = 0; i < return_charges.size(); ++i)
409 return_charges[i] = PartialCharges->at(i);
410 return return_charges;
411}
Note: See TracBrowser for help on using the repository browser.