/* * Project: MoleCuilder * Description: creates and alters molecular systems * Copyright (C) 2013 Frederik Heber. All rights reserved. * Please see the LICENSE file or "Copyright notice" in builder.cpp for details. * * * This file is part of MoleCuilder. * * MoleCuilder is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * MoleCuilder is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MoleCuilder. If not, see . */ /* * PartialNucleiChargeFitter.cpp * * Created on: 12.05.2013 * Author: heber */ // include config.h #ifdef HAVE_CONFIG_H #include #endif #include "CodePatterns/MemDebug.hpp" #include "PartialNucleiChargeFitter.hpp" #include #include #include #include #include "LinearAlgebra/MatrixContent.hpp" #include "LinearAlgebra/VectorContent.hpp" #include "CodePatterns/Assert.hpp" #include "CodePatterns/Log.hpp" #include "Fragmentation/Summation/SetValues/SamplingGrid.hpp" PartialNucleiChargeFitter::dimensions_t PartialNucleiChargeFitter::getGridDimensions(const SamplingGrid &grid) const { // convert sampled potential into a vector const double round_offset = (std::numeric_limits::round_style == std::round_toward_zero) ? 0.5 : 0.; // need offset to get to round_toward_nearest behavior dimensions_t total(3,0); for(size_t index=0;index<3;++index) { const double delta = grid.getDeltaPerAxis(index); // delta is conversion factor from box length to discrete length, i.e. number of points total[index] = (grid.end[index] - grid.begin[index])/delta+round_offset; } return total; } PartialNucleiChargeFitter::PartialNucleiChargeFitter( const SamplingGrid &grid, const positions_t &_positions, const double _threshold) : total(getGridDimensions(grid)), SampledPotential(std::accumulate(total.begin(), total.end(), 1, std::multiplies())), grid_properties(static_cast(grid)), positions(_positions), PotentialFromCharges(NULL), PartialCharges(NULL), threshold(_threshold) { // we must take care of the "window", i.e. there may be less entries in sampled_grid // vector as we would expect from size of grid ((2^level)^3) as 0-entries have been // omitted. size_t pre_offset[3]; size_t post_offset[3]; size_t length[3]; size_t total[3]; grid.getDiscreteWindowIndices( grid.begin, grid.end, grid.begin_window, grid.end_window, pre_offset, post_offset, length, total ); const size_t calculated_size = length[0]*length[1]*length[2]; ASSERT( calculated_size == grid.sampled_grid.size(), "PartialNucleiChargeFitter::PartialNucleiChargeFitter() - grid does not match size indicated by its window."); const double potential_sum = std::accumulate(grid.sampled_grid.begin(), grid.sampled_grid.end(), 0.); if ( fabs(potential_sum) > std::numeric_limits::epsilon()*1e4 ) { ELOG(2, "Potential sum is not less than " << std::numeric_limits::epsilon()*1e4 << " but " << potential_sum << "."); } SamplingGrid::sampledvalues_t::const_iterator griditer = grid.sampled_grid.begin(); size_t index=0; size_t N[3]; Vector grid_position; // position of grid point in real domain size_t masked_points = 0; // store step length per axis double delta[3]; for (size_t i=0;i<3;++i) delta[i] = grid_properties.getDeltaPerAxis(i); /// convert sampled potential into a vector grid_position[0] = grid_properties.begin[0]; for(N[0]=0; N[0] < pre_offset[0]; ++N[0]) { grid_position[1] = grid_properties.begin[1]; for(N[1]=0; N[1] < total[1]; ++N[1]) { grid_position[2] = grid_properties.begin[2]; for(N[2]=0; N[2] < total[2]; ++N[2]) { const_cast(SampledPotential)[index++] = 0.; grid_position[2] += delta[2]; } grid_position[1] += delta[1]; } grid_position[0] += delta[0]; } for(N[0]=0; N[0] < length[0]; ++N[0]) { grid_position[1] = grid_properties.begin[1]; for(N[1]=0; N[1] < pre_offset[1]; ++N[1]) { grid_position[2] = grid_properties.begin[2]; for(N[2]=0; N[2] < total[2]; ++N[2]) { const_cast(SampledPotential)[index++] = 0.; grid_position[2] += delta[2]; } grid_position[1] += delta[1]; } for(N[1]=0; N[1] < length[1]; ++N[1]) { grid_position[2] = grid_properties.begin[2]; for(N[2]=0; N[2] < pre_offset[2]; ++N[2]) { const_cast(SampledPotential)[index++] = 0.; grid_position[2] += delta[2]; } for(N[2]=0; N[2] < length[2]; ++N[2]) { if (isGridPointSettable(positions, grid_position)) const_cast(SampledPotential)[index++] = *griditer++; else { // skip point ++griditer; ++masked_points; const_cast(SampledPotential)[index++] = 0.; } grid_position[2] += delta[2]; } for(N[2]=0; N[2] < post_offset[2]; ++N[2]) { const_cast(SampledPotential)[index++] = 0.; grid_position[2] += delta[2]; } grid_position[1] += delta[1]; } for(N[1]=0; N[1] < post_offset[1]; ++N[1]) { grid_position[2] = grid_properties.begin[2]; for(N[2]=0; N[2] < total[2]; ++N[2]) { const_cast(SampledPotential)[index++] = 0.; grid_position[2] += delta[2]; } grid_position[1] += delta[1]; } grid_position[0] += delta[0]; } for(N[0]=0; N[0] < post_offset[0]; ++N[0]) { grid_position[1] = grid_properties.begin[1]; for(N[1]=0; N[1] < total[1]; ++N[1]) { grid_position[2] = grid_properties.begin[2]; for(N[2]=0; N[2] < total[2]; ++N[2]) { const_cast(SampledPotential)[index++] = 0.; grid_position[2] += delta[2]; } grid_position[1] += delta[1]; } grid_position[0] += delta[0]; } // set remainder of points to zero ASSERT( index == SampledPotential.getDimension(), "PartialNucleiChargeFitter::PartialNucleiChargeFitter() - not enough or more than calculated sample points."); #ifndef NDEBUG // write vector as paraview csv file file { size_t N[3]; size_t index = 0; std::ofstream paraview_output("solution.csv"); paraview_output << "x coord,y coord,z coord,scalar" << std::endl; for(N[0]=0; N[0] < total[0]; ++N[0]) { for(N[1]=0; N[1] < total[1]; ++N[1]) { for(N[2]=0; N[2] < total[2]; ++N[2]) { paraview_output << (double)N[0]/(double)total[0] << "," << (double)N[1]/(double)total[1] << "," << (double)N[2]/(double)total[2] << "," << SampledPotential.at(index++) << std::endl; } } } paraview_output.close(); } #endif LOG(1, "INFO: I masked " << masked_points << " points in right-hand-side."); // LOG(4, "DEBUG: Right-hand side vector is " << SampledPotential << "."); } bool PartialNucleiChargeFitter::isGridPointSettable( const positions_t &_positions, const Vector &grid_position) const { bool status = true; for (positions_t::const_iterator iter = _positions.begin(); iter != _positions.end(); ++iter) { status &= grid_position.DistanceSquared(*iter) > threshold*threshold; } return status; } PartialNucleiChargeFitter::~PartialNucleiChargeFitter() { if (PartialCharges != NULL) delete PartialCharges; if (PotentialFromCharges != NULL) delete PotentialFromCharges; } void PartialNucleiChargeFitter::constructMatrix() { const size_t rows = SampledPotential.getDimension(); const size_t cols = positions.size(); // allocate memory for PotentialFromCharges if (PotentialFromCharges != NULL) { delete PotentialFromCharges; PotentialFromCharges = NULL; } PotentialFromCharges = new MatrixContent( rows, cols ); // store step length per axis double delta[3]; for (size_t i=0;i<3;++i) delta[i] = grid_properties.getDeltaPerAxis(i); // then for each charge ... size_t masked_points = 0; for (size_t nuclei_index = 0; nuclei_index < cols; ++nuclei_index) { // ... calculate potential at each grid position, // i.e. step through grid and calculate distance to charge position Vector grid_position; // position of grid point in real domain grid_position[0] = grid_properties.begin[0]; size_t N[3]; // discrete grid position size_t index = 0; // component of column vector for(N[0]=0; N[0] < total[0]; ++N[0]) { grid_position[1] = grid_properties.begin[1]; for(N[1]=0; N[1] < total[1]; ++N[1]) { grid_position[2] = grid_properties.begin[2]; for(N[2]=0; N[2] < total[2]; ++N[2]) { if (isGridPointSettable(positions, grid_position)) { const double distance = positions[nuclei_index].distance(grid_position); ASSERT( distance >= 0, "PartialNucleiChargeFitter::constructMatrix() - distance is negative?"); // Coulomb's constant is 1 in atomic units, see http://en.wikipedia.org/wiki/Atomic_units const double epsilon0_au = 1.; //4.*M_PI*0.007957747154594767; // ... with epsilon_0 in atom units from http://folk.uio.no/michalj/node72.html const double value = 1./(epsilon0_au*distance); PotentialFromCharges->at(index++, nuclei_index) = value; } else { ++masked_points; PotentialFromCharges->at(index++, nuclei_index) = 0.; } grid_position[2] += delta[2]; } grid_position[1] += delta[1]; } grid_position[0] += delta[0]; } ASSERT( index == PotentialFromCharges->getRows(), "PartialNucleiChargeFitter::operator() - number of sampled positions " +toString(index)+" unequal to set rows " +toString(PotentialFromCharges->getRows())+"."); } LOG(1, "INFO: I masked " << masked_points/cols << " points in matrix."); } double PartialNucleiChargeFitter::operator()() { // prepare PartialCharges if (PartialCharges != NULL) { delete PartialCharges; PartialCharges = NULL; } PartialCharges = new VectorContent(positions.size()); // set up over-determined system's problem matrix A for Ax=b // i.e. columns represent potential of a single charge at grid positions constructMatrix(); // solve for x *PartialCharges = PotentialFromCharges->solveOverdeterminedLinearEquation( SampledPotential); // LOG(4, "DEBUG: Solution vector is " << (*PotentialFromCharges) * (*PartialCharges) << "."); // calculate residual vector VectorContent residuum = (*PotentialFromCharges) * (*PartialCharges) - SampledPotential; #ifndef NDEBUG // write solution to file writeMatrix(); // write vector as paraview csv file file { size_t N[3]; size_t index = 0; std::ofstream paraview_output("residuum.csv"); paraview_output << "x coord,y coord,z coord,scalar" << std::endl; for(N[0]=0; N[0] < total[0]; ++N[0]) { for(N[1]=0; N[1] < total[1]; ++N[1]) { for(N[2]=0; N[2] < total[2]; ++N[2]) { paraview_output << (double)N[0]/(double)total[0] << "," << (double)N[1]/(double)total[1] << "," << (double)N[2]/(double)total[2] << "," << residuum.at(index++) << std::endl; } } } paraview_output.close(); } #endif // calculate L1 and L2 errors double residuum_l1 = 0.; for (size_t i=0; i< residuum.getDimension(); ++i) if (residuum_l1 < residuum[i]) residuum_l1 = residuum[i]; LOG(1, "INFO: L2-Norm of residuum is " << residuum.Norm() << "."); LOG(1, "INFO: L1-Norm of residuum is " << residuum_l1 << "."); return residuum.Norm(); } void PartialNucleiChargeFitter::writeMatrix() { constructMatrix(); // write matrix as paraview csv file file size_t N[3]; size_t index=0; std::string filename = std::string("potential.csv"); std::ofstream paraview_output(filename.c_str()); paraview_output << "x coord,y coord,z coord,scalar" << std::endl; for(N[0]=0; N[0] < total[0]; ++N[0]) { for(N[1]=0; N[1] < total[1]; ++N[1]) { for(N[2]=0; N[2] < total[2]; ++N[2]) { double sum = 0.; for (size_t nuclei_index = 0; nuclei_index < positions.size(); ++nuclei_index) { sum+= PotentialFromCharges->at(index, nuclei_index)*PartialCharges->at(nuclei_index); } paraview_output << (double)N[0]/(double)total[0] << "," << (double)N[1]/(double)total[1] << "," << (double)N[2]/(double)total[2] << "," << sum << std::endl; index++; } } } paraview_output.close(); } PartialNucleiChargeFitter::charges_t PartialNucleiChargeFitter::getSolutionAsCharges_t() const { ASSERT( PartialCharges != NULL, "PartialNucleiChargeFitter::getSolutionAsCharges_t() - PartialCharges requested prior to calculation."); charges_t return_charges(positions.size(), 0.); for (size_t i = 0; i < return_charges.size(); ++i) return_charges[i] = PartialCharges->at(i); return return_charges; }