source: src/Actions/PotentialAction/FitPartialChargesAction.cpp@ 01120c

Action_Thermostats Add_AtomRandomPerturbation Add_FitFragmentPartialChargesAction Add_RotateAroundBondAction Add_SelectAtomByNameAction Added_ParseSaveFragmentResults Adding_Graph_to_ChangeBondActions Adding_MD_integration_tests Adding_ParticleName_to_Atom Adding_StructOpt_integration_tests Automaking_mpqc_open AutomationFragmentation_failures Candidate_v1.5.4 Candidate_v1.6.0 Candidate_v1.6.1 ChangeBugEmailaddress ChangingTestPorts ChemicalSpaceEvaluator CombiningParticlePotentialParsing Combining_Subpackages Debian_Package_split Debian_package_split_molecuildergui_only Disabling_MemDebug Docu_Python_wait EmpiricalPotential_contain_HomologyGraph EmpiricalPotential_contain_HomologyGraph_documentation Enable_parallel_make_install Enhance_userguide Enhanced_StructuralOptimization Enhanced_StructuralOptimization_continued Example_ManyWaysToTranslateAtom Exclude_Hydrogens_annealWithBondGraph FitPartialCharges_GlobalError Fix_ChargeSampling_PBC Fix_ChronosMutex Fix_FitPartialCharges Fix_FitPotential_needs_atomicnumbers Fix_ForceAnnealing Fix_IndependentFragmentGrids Fix_ParseParticles Fix_ParseParticles_split_forward_backward_Actions Fix_PopActions Fix_QtFragmentList_sorted_selection Fix_Restrictedkeyset_FragmentMolecule Fix_StatusMsg Fix_StepWorldTime_single_argument Fix_Verbose_Codepatterns Fix_fitting_potentials Fixes ForceAnnealing_goodresults ForceAnnealing_oldresults ForceAnnealing_tocheck ForceAnnealing_with_BondGraph ForceAnnealing_with_BondGraph_continued ForceAnnealing_with_BondGraph_continued_betteresults ForceAnnealing_with_BondGraph_contraction-expansion FragmentMolecule_checks_bonddegrees GeometryObjects Gui_Fixes Gui_displays_atomic_force_velocity IndependentFragmentGrids IndependentFragmentGrids_IndividualZeroInstances IndependentFragmentGrids_IntegrationTest IndependentFragmentGrids_Sole_NN_Calculation JobMarket_RobustOnKillsSegFaults JobMarket_StableWorkerPool JobMarket_unresolvable_hostname_fix ODR_violation_mpqc_open PartialCharges_OrthogonalSummation PdbParser_setsAtomName PythonUI_with_named_parameters QtGui_reactivate_TimeChanged_changes Recreated_GuiChecks Rewrite_FitPartialCharges RotateToPrincipalAxisSystem_UndoRedo SaturateAtoms_findBestMatching SaturateAtoms_singleDegree StoppableMakroAction Subpackage_CodePatterns Subpackage_JobMarket Subpackage_LinearAlgebra Subpackage_levmar Subpackage_mpqc_open Subpackage_vmg Switchable_LogView ThirdParty_MPQC_rebuilt_buildsystem TrajectoryDependenant_MaxOrder TremoloParser_IncreasedPrecision TremoloParser_MultipleTimesteps TremoloParser_setsAtomName Ubuntu_1604_changes stable
Last change on this file since 01120c was 01120c, checked in by Frederik Heber <heber@…>, 9 years ago

FitPartialChargesAction properly treats hydrogens now.

  • Property mode set to 100644
File size: 14.8 KB
Line 
1/*
2 * Project: MoleCuilder
3 * Description: creates and alters molecular systems
4 * Copyright (C) 2013 Frederik Heber. All rights reserved.
5 *
6 *
7 * This file is part of MoleCuilder.
8 *
9 * MoleCuilder is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * MoleCuilder is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with MoleCuilder. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23/*
24 * FitPartialChargesAction.cpp
25 *
26 * Created on: Jul 03, 2013
27 * Author: heber
28 */
29
30// include config.h
31#ifdef HAVE_CONFIG_H
32#include <config.h>
33#endif
34
35// needs to come before MemDebug due to placement new
36#include <boost/archive/text_iarchive.hpp>
37
38#include "CodePatterns/MemDebug.hpp"
39
40#include "Atom/atom.hpp"
41#include "CodePatterns/Log.hpp"
42#include "Fragmentation/Exporters/ExportGraph_ToFiles.hpp"
43#include "Fragmentation/Graph.hpp"
44#include "World.hpp"
45
46#include <boost/bimap.hpp>
47#include <boost/bind.hpp>
48#include <boost/filesystem.hpp>
49#include <boost/foreach.hpp>
50#include <algorithm>
51#include <functional>
52#include <iostream>
53#include <string>
54
55#include "Actions/PotentialAction/FitPartialChargesAction.hpp"
56
57#include "Potentials/PartialNucleiChargeFitter.hpp"
58
59#include "AtomIdSet.hpp"
60#include "Descriptors/AtomIdDescriptor.hpp"
61#include "Element/element.hpp"
62#include "Element/periodentafel.hpp"
63#include "Fragmentation/Homology/AtomFragmentsMap.hpp"
64#include "Fragmentation/Homology/HomologyContainer.hpp"
65#include "Fragmentation/Homology/HomologyGraph.hpp"
66#include "Fragmentation/Summation/SetValues/SamplingGrid.hpp"
67#include "FunctionApproximation/Extractors.hpp"
68#include "Potentials/PartialNucleiChargeFitter.hpp"
69#include "Potentials/Particles/ParticleFactory.hpp"
70#include "Potentials/Particles/ParticleRegistry.hpp"
71#include "Potentials/SerializablePotential.hpp"
72#include "World.hpp"
73
74using namespace MoleCuilder;
75
76// and construct the stuff
77#include "FitPartialChargesAction.def"
78#include "Action_impl_pre.hpp"
79/** =========== define the function ====================== */
80
81static void enforceZeroTotalCharge(
82 PartialNucleiChargeFitter::charges_t &_averaged_charges)
83{
84 double charge_sum = 0.;
85 charge_sum = std::accumulate(_averaged_charges.begin(), _averaged_charges.end(), charge_sum);
86 if (fabs(charge_sum) > MYEPSILON) {
87 std::transform(
88 _averaged_charges.begin(), _averaged_charges.end(),
89 _averaged_charges.begin(),
90 boost::bind(std::minus<double>(), _1, charge_sum/_averaged_charges.size()));
91 }
92 charge_sum = 0.;
93 charge_sum = std::accumulate(_averaged_charges.begin(), _averaged_charges.end(), charge_sum);
94 ASSERT( fabs(charge_sum) < MYEPSILON,
95 "enforceZeroTotalCharge() - enforcing neutral net charge failed, "
96 +toString(charge_sum)+" is the remaining net charge.");
97
98 LOG(2, "DEBUG: final charges with net zero charge are " << _averaged_charges);
99}
100
101static size_t obtainAverageChargesOverFragments(
102 PartialNucleiChargeFitter::charges_t &_average_charges,
103 const std::pair<
104 HomologyContainer::const_iterator,
105 HomologyContainer::const_iterator> &_range,
106 const double _radius
107 )
108{
109 HomologyContainer::const_iterator iter = _range.first;
110 if (!iter->second.containsGrids) {
111 ELOG(1, "This HomologyGraph does not contain sampled grids.");
112 return 0;
113 }
114 _average_charges.resize(iter->second.fragment.getCharges().size(), 0.);
115 size_t NoFragments = 0;
116 for (;
117 iter != _range.second; ++iter, ++NoFragments) {
118 if (!iter->second.containsGrids) {
119 ELOG(2, "This HomologyGraph does not contain sampled grids,\ndid you forget to add '--store-grids 1' to AnalyseFragmentResults.");
120 return 0;
121 }
122 const Fragment &fragment = iter->second.fragment;
123 // const double &energy = iter->second.energy;
124 // const SamplingGrid &charge = iter->second.charge_distribution;
125 const SamplingGrid &potential = iter->second.potential_distribution;
126 if ((potential.level == 0)
127 || ((potential.begin[0] == potential.end[0])
128 && (potential.begin[1] == potential.end[1])
129 && (potential.begin[2] == potential.end[2]))) {
130 ELOG(1, "Sampled grid contains grid made of zero points.");
131 return 0;
132 }
133
134 // then we extract positions from fragment
135 PartialNucleiChargeFitter::positions_t positions;
136 Fragment::positions_t fragmentpositions = fragment.getPositions();
137 positions.reserve(fragmentpositions.size());
138 BOOST_FOREACH( Fragment::position_t pos, fragmentpositions) {
139 positions.push_back( Vector(pos[0], pos[1], pos[2]) );
140 }
141 PartialNucleiChargeFitter fitter(potential, positions, _radius);
142 fitter();
143 PartialNucleiChargeFitter::charges_t return_charges = fitter.getSolutionAsCharges_t();
144 LOG(2, "DEBUG: fitted charges are " << return_charges);
145 std::transform(
146 return_charges.begin(), return_charges.end(),
147 _average_charges.begin(),
148 _average_charges.begin(),
149 std::plus<PartialNucleiChargeFitter::charge_t>());
150 }
151 if (NoFragments != 0)
152 std::transform(_average_charges.begin(), _average_charges.end(),
153 _average_charges.begin(),
154 std::bind1st(std::multiplies<PartialNucleiChargeFitter::charge_t>(),1./(double)NoFragments)
155 );
156 LOG(2, "DEBUG: final averaged charges are " << _average_charges);
157
158 return NoFragments;
159}
160
161inline SerializablePotential::ParticleTypes_t
162getParticleTypesForAtomIdSet(const AtomIdSet &_atoms)
163{
164 SerializablePotential::ParticleTypes_t particletypes;
165 particletypes.resize(_atoms.size());
166 std::transform(
167 _atoms.begin(), _atoms.end(),
168 particletypes.begin(),
169 boost::bind(&atom::getElementNo, _1));
170 return particletypes;
171}
172
173ActionState::ptr PotentialFitPartialChargesAction::performCall()
174{
175 // check for selected atoms
176 const World &world = const_cast<const World &>(World::getInstance());
177 if (world.beginAtomSelection() == world.endAtomSelection()) {
178 STATUS("There are no atoms selected for fitting partial charges to.");
179 return Action::failure;
180 }
181
182 /// obtain possible fragments to each selected atom
183 const AtomFragmentsMap &atomfragments = AtomFragmentsMap::getConstInstance();
184 if (!atomfragments.checkCompleteness()) {
185 STATUS("AtomFragmentsMap failed internal consistency check, missing forcekeysets?");
186 return Action::failure;
187 }
188 std::set<KeySet> fragments;
189 const AtomFragmentsMap::AtomFragmentsMap_t &atommap = atomfragments.getMap();
190 for (World::AtomSelectionConstIterator iter = world.beginAtomSelection();
191 iter != world.endAtomSelection(); ++iter) {
192 const AtomFragmentsMap::AtomFragmentsMap_t::const_iterator atomiter =
193 atommap.find(iter->first);
194 if (iter->second->getElementNo() != 1) {
195 if (atomiter == atommap.end()) {
196 ELOG(2, "There are no fragments associated to " << iter->first << ".");
197 continue;
198 }
199 const AtomFragmentsMap::keysets_t &keysets = atomiter->second;
200 LOG(2, "DEBUG: atom " << iter->first << " has " << keysets.size() << " fragments.");
201 fragments.insert( keysets.begin(), keysets.end() );
202 } else {
203 LOG(3, "DEBUG: Skipping atom " << iter->first << " as it's hydrogen.");
204 }
205 }
206
207 // reduce given fragments to homologous graphs to avoid multiple fittings
208 typedef std::map<KeySet, HomologyGraph> KeysetsToGraph_t;
209 KeysetsToGraph_t keyset_graphs;
210 typedef std::map<HomologyGraph, PartialNucleiChargeFitter::charges_t> GraphFittedChargeMap_t;
211 GraphFittedChargeMap_t fittedcharges_per_fragment;
212 HomologyContainer &homologies = World::getInstance().getHomologies();
213 for (std::set<KeySet>::const_iterator fragmentiter = fragments.begin();
214 fragmentiter != fragments.end(); ++fragmentiter) {
215 const KeySet &keyset = *fragmentiter;
216 const AtomFragmentsMap::indices_t &forceindices = atomfragments.getFullKeyset(keyset);
217 ASSERT( !forceindices.empty(),
218 "PotentialFitPartialChargesAction::performCall() - force keyset to "
219 +toString(keyset)+" is empty.");
220 KeySet forcekeyset;
221 forcekeyset.insert(forceindices.begin(), forceindices.end());
222 forcekeyset.erase(-1);
223 const HomologyGraph graph(forcekeyset);
224 LOG(2, "DEBUG: Associating keyset " << forcekeyset << " with graph " << graph);
225 keyset_graphs.insert( std::make_pair(keyset, graph) );
226 fittedcharges_per_fragment.insert( std::make_pair(graph, PartialNucleiChargeFitter::charges_t()) );
227 }
228
229 /// then go through all fragments and get partial charges for each
230 for (GraphFittedChargeMap_t::iterator graphiter = fittedcharges_per_fragment.begin();
231 graphiter != fittedcharges_per_fragment.end(); ++graphiter) {
232 const HomologyGraph &graph = graphiter->first;
233 LOG(2, "DEBUG: Now fitting charges to graph " << graph);
234 const HomologyContainer::range_t range = homologies.getHomologousGraphs(graph);
235 if (range.first == range.second) {
236 STATUS("HomologyContainer does not contain specified fragment.");
237 return Action::failure;
238 }
239
240 // fit and average partial charges over all homologous fragments
241 PartialNucleiChargeFitter::charges_t &averaged_charges = graphiter->second;
242 const size_t NoFragments =
243 obtainAverageChargesOverFragments(averaged_charges, range, params.radius.get());
244 if ((NoFragments == 0) && (range.first != range.second)) {
245 STATUS("Fitting for fragment "+toString(*graphiter)+" failed.");
246 return Action::failure;
247 }
248
249 // make sum of charges zero if desired
250 if (params.enforceZeroCharge.get())
251 enforceZeroTotalCharge(averaged_charges);
252
253 // output status info fitted charges
254 LOG(2, "DEBUG: For fragment " << *graphiter << " we have fitted the following charges "
255 << averaged_charges << ", averaged over " << NoFragments << " fragments.");
256 }
257
258 /// obtain average charge for each atom the fitted charges over all its fragments
259 typedef std::map<atomId_t, double> fitted_charges_t;
260 fitted_charges_t fitted_charges;
261 for (World::AtomSelectionConstIterator atomiter = world.beginAtomSelection();
262 atomiter != world.endAtomSelection(); ++atomiter) {
263 const atom * walker = atomiter->second;
264 if (walker->getElementNo() == 1) {
265 // it's hydrogen, check its bonding and use its bond partner instead to request
266 // keysets
267 const BondList &ListOfBonds = walker->getListOfBonds();
268 if ( ListOfBonds.size() != 1) {
269 ELOG(1, "Solitary hydrogen in atom " << atomiter->first << " detected, skipping.");
270 continue;
271 }
272 walker = (*ListOfBonds.begin())->GetOtherAtom(walker);
273 }
274 const atomId_t walkerid = walker->getId();
275 const AtomFragmentsMap::AtomFragmentsMap_t::const_iterator keysetsiter =
276 atommap.find(walkerid);
277 ASSERT(keysetsiter != atommap.end(),
278 "PotentialFitPartialChargesAction::performCall() - we checked already that "
279 +toString(walkerid)+" should be present!");
280 const AtomFragmentsMap::keysets_t & keysets = keysetsiter->second;
281
282 double average_charge = 0.;
283 size_t NoFragments = 0;
284 // go over all fragments associated to this atom
285 for (AtomFragmentsMap::keysets_t::const_iterator keysetsiter = keysets.begin();
286 keysetsiter != keysets.end(); ++keysetsiter) {
287 const KeySet &keyset = *keysetsiter;
288
289 const AtomFragmentsMap::indices_t &forcekeyset = atomfragments.getFullKeyset(keyset);
290 ASSERT( !forcekeyset.empty(),
291 "PotentialFitPartialChargesAction::performCall() - force keyset to "
292 +toString(keyset)+" is empty.");
293
294 // find the associated charge in the charge vector
295 const HomologyGraph &graph = keyset_graphs[keyset];
296 const GraphFittedChargeMap_t::const_iterator chargesiter =
297 fittedcharges_per_fragment.find(graph);
298 ASSERT(chargesiter != fittedcharges_per_fragment.end(),
299 "PotentialFitPartialChargesAction::performCall() - no charge to "+toString(keyset)
300 +" any longer present in fittedcharges_per_fragment?");
301 const PartialNucleiChargeFitter::charges_t &charges = chargesiter->second;
302 ASSERT( charges.size() == forcekeyset.size(),
303 "PotentialFitPartialChargesAction::performCall() - charges "
304 +toString(charges.size())+" and keyset "+toString(forcekeyset.size())
305 +" do not have the same length?");
306 PartialNucleiChargeFitter::charges_t::const_iterator chargeiter =
307 charges.begin();
308 const AtomFragmentsMap::indices_t::const_iterator forcekeysetiter =
309 std::find(forcekeyset.begin(), forcekeyset.end(), atomiter->first);
310 ASSERT( forcekeysetiter != forcekeyset.end(),
311 "PotentialFitPartialChargesAction::performCall() - atom "
312 +toString(atomiter->first)+" not contained in force keyset "+toString(forcekeyset));
313 std::advance(chargeiter, std::distance(forcekeyset.begin(), forcekeysetiter));
314
315 // and add onto charge sum
316 const double & charge_in_fragment = *chargeiter;
317 average_charge += charge_in_fragment;
318 ++NoFragments;
319 }
320 // average to obtain final partial charge for this atom
321 average_charge *= 1./(double)NoFragments;
322 fitted_charges.insert( std::make_pair(atomiter->first, average_charge) );
323
324 LOG(2, "DEBUG: For atom " << atomiter->first << " we have an average charge of "
325 << average_charge);
326 }
327
328 /// place all fitted charges into ParticleRegistry
329 const ParticleFactory &factory =
330 const_cast<const ParticleFactory&>(ParticleFactory::getInstance());
331 const periodentafel &periode = *World::getInstance().getPeriode();
332 for (fitted_charges_t::const_iterator chargeiter = fitted_charges.begin();
333 chargeiter != fitted_charges.end(); ++chargeiter) {
334 const atom * const walker = World::getInstance().getAtom(AtomById(chargeiter->first));
335 ASSERT( walker != NULL,
336 "PotentialFitPartialChargesAction::performCall() - atom "
337 +toString(chargeiter->first)+" not present in the World?");
338 const double &charge = chargeiter->second;
339 const atomicNumber_t elementno = walker->getElementNo();
340 const std::string name = Particle::findFreeName(periode, elementno);
341 LOG(1, "INFO: Adding particle " << name << " for atom "
342 << *walker << " with element " << elementno << ", charge " << charge);
343 factory.createInstance(name, elementno, charge);
344 }
345
346 return Action::success;
347}
348
349ActionState::ptr PotentialFitPartialChargesAction::performUndo(ActionState::ptr _state) {
350 return Action::success;
351}
352
353ActionState::ptr PotentialFitPartialChargesAction::performRedo(ActionState::ptr _state){
354 return Action::success;
355}
356
357bool PotentialFitPartialChargesAction::canUndo() {
358 return false;
359}
360
361bool PotentialFitPartialChargesAction::shouldUndo() {
362 return false;
363}
364/** =========== end of function ====================== */
Note: See TracBrowser for help on using the repository browser.