source: src/Actions/PotentialAction/FitPartialChargesAction.cpp@ c1ec8e

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 c1ec8e was c1ec8e, checked in by Frederik Heber <heber@…>, 9 years ago

Split FitPartialChargesAction's extensive performCall() into subfunctions.

  • Property mode set to 100644
File size: 16.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
173static
174std::set<KeySet> accumulateKeySetsForAtoms(
175 const AtomFragmentsMap::AtomFragmentsMap_t &_atommap,
176 const std::vector<const atom *> &_selected_atoms)
177{
178 std::set<KeySet> fragments;
179 for (std::vector<const atom *>::const_iterator iter = _selected_atoms.begin();
180 iter != _selected_atoms.end(); ++iter) {
181 const atomId_t atomid = (*iter)->getId();
182 const AtomFragmentsMap::AtomFragmentsMap_t::const_iterator atomiter =
183 _atommap.find(atomid);
184 if ((*iter)->getElementNo() != 1) {
185 if (atomiter == _atommap.end()) {
186 ELOG(2, "There are no fragments associated to " << atomid << ".");
187 continue;
188 }
189 const AtomFragmentsMap::keysets_t &keysets = atomiter->second;
190 LOG(2, "DEBUG: atom " << atomid << " has " << keysets.size() << " fragments.");
191 fragments.insert( keysets.begin(), keysets.end() );
192 } else {
193 LOG(3, "DEBUG: Skipping atom " << atomid << " as it's hydrogen.");
194 }
195 }
196 return fragments;
197}
198
199static
200void getKeySetsToGraphMapping(
201 std::map<KeySet, HomologyGraph> &_keyset_graphs,
202 std::map<HomologyGraph, PartialNucleiChargeFitter::charges_t> &_fittedcharges_per_fragment,
203 const std::set<KeySet> &_fragments,
204 const AtomFragmentsMap &_atomfragments)
205{
206 for (std::set<KeySet>::const_iterator fragmentiter = _fragments.begin();
207 fragmentiter != _fragments.end(); ++fragmentiter) {
208 const KeySet &keyset = *fragmentiter;
209 const AtomFragmentsMap::indices_t &forceindices = _atomfragments.getFullKeyset(keyset);
210 ASSERT( !forceindices.empty(),
211 "getKeySetsToGraphMapping() - force keyset to "+toString(keyset)+" is empty.");
212 KeySet forcekeyset;
213 forcekeyset.insert(forceindices.begin(), forceindices.end());
214 forcekeyset.erase(-1);
215 const HomologyGraph graph(forcekeyset);
216 LOG(2, "DEBUG: Associating keyset " << forcekeyset << " with graph " << graph);
217 _keyset_graphs.insert( std::make_pair(keyset, graph) );
218 _fittedcharges_per_fragment.insert( std::make_pair(graph, PartialNucleiChargeFitter::charges_t()) );
219 }
220}
221
222static
223bool getPartialChargesForAllGraphs(
224 std::map<HomologyGraph, PartialNucleiChargeFitter::charges_t> &_fittedcharges_per_fragment,
225 const HomologyContainer &_homologies,
226 const double _mask_radius,
227 const bool enforceZeroCharge)
228{
229 typedef std::map<HomologyGraph, PartialNucleiChargeFitter::charges_t> GraphFittedChargeMap_t;
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 ELOG(0, "HomologyContainer does not contain specified fragment.");
237 return false;
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, _mask_radius);
244 if ((NoFragments == 0) && (range.first != range.second)) {
245 ELOG(0, "Fitting for fragment "+toString(*graphiter)+" failed.");
246 return false;
247 }
248
249 // make sum of charges zero if desired
250 if (enforceZeroCharge)
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 return true;
258}
259
260double fitAverageChargeToAtom(
261 const atom * const _walker,
262 const AtomFragmentsMap &_atomfragments,
263 const std::map<KeySet, HomologyGraph> &_keyset_graphs,
264 const std::map<HomologyGraph, PartialNucleiChargeFitter::charges_t> &_fittedcharges_per_fragment)
265{
266 typedef std::map<HomologyGraph, PartialNucleiChargeFitter::charges_t> GraphFittedChargeMap_t;
267 const atom * surrogate = _walker;
268 if (surrogate->getElementNo() == 1) {
269 // it's hydrogen, check its bonding and use its bond partner instead to request
270 // keysets
271 const BondList &ListOfBonds = surrogate->getListOfBonds();
272 if ( ListOfBonds.size() != 1) {
273 ELOG(1, "Solitary hydrogen in atom " << surrogate->getId() << " detected, skipping.");
274 return 0.;
275 }
276 surrogate = (*ListOfBonds.begin())->GetOtherAtom(surrogate);
277 }
278 const atomId_t walkerid = surrogate->getId();
279 const AtomFragmentsMap::AtomFragmentsMap_t &atommap = _atomfragments.getMap();
280 const AtomFragmentsMap::AtomFragmentsMap_t::const_iterator keysetsiter =
281 atommap.find(walkerid);
282 ASSERT(keysetsiter != atommap.end(),
283 "fitAverageChargeToAtom() - we checked already that "+toString(walkerid)
284 +" should be present!");
285 const AtomFragmentsMap::keysets_t & keysets = keysetsiter->second;
286
287 double average_charge = 0.;
288 size_t NoFragments = 0;
289 // go over all fragments associated to this atom
290 for (AtomFragmentsMap::keysets_t::const_iterator keysetsiter = keysets.begin();
291 keysetsiter != keysets.end(); ++keysetsiter) {
292 const KeySet &keyset = *keysetsiter;
293
294 const AtomFragmentsMap::indices_t &forcekeyset = _atomfragments.getFullKeyset(keyset);
295 ASSERT( !forcekeyset.empty(),
296 "fitAverageChargeToAtom() - force keyset to "+toString(keyset)+" is empty.");
297
298 // find the associated charge in the charge vector
299 std::map<KeySet, HomologyGraph>::const_iterator keysetgraphiter =
300 _keyset_graphs.find(keyset);
301 ASSERT( keysetgraphiter != _keyset_graphs.end(),
302 "fitAverageChargeToAtom() - keyset "+toString(keyset)
303 +" not contained in keyset_graphs.");
304 const HomologyGraph &graph = keysetgraphiter->second;
305 const GraphFittedChargeMap_t::const_iterator chargesiter =
306 _fittedcharges_per_fragment.find(graph);
307 ASSERT(chargesiter != _fittedcharges_per_fragment.end(),
308 "fitAverageChargeToAtom() - no charge to "+toString(keyset)
309 +" any longer present in fittedcharges_per_fragment?");
310 const PartialNucleiChargeFitter::charges_t &charges = chargesiter->second;
311 ASSERT( charges.size() == forcekeyset.size(),
312 "fitAverageChargeToAtom() - charges "+toString(charges.size())+" and keyset "
313 +toString(forcekeyset.size())+" do not have the same length?");
314 PartialNucleiChargeFitter::charges_t::const_iterator chargeiter =
315 charges.begin();
316 const AtomFragmentsMap::indices_t::const_iterator forcekeysetiter =
317 std::find(forcekeyset.begin(), forcekeyset.end(), _walker->getId());
318 ASSERT( forcekeysetiter != forcekeyset.end(),
319 "fitAverageChargeToAtom() - atom "+toString(_walker->getId())
320 +" not contained in force keyset "+toString(forcekeyset));
321 std::advance(chargeiter, std::distance(forcekeyset.begin(), forcekeysetiter));
322
323 // and add onto charge sum
324 const double & charge_in_fragment = *chargeiter;
325 average_charge += charge_in_fragment;
326 ++NoFragments;
327 }
328 // average to obtain final partial charge for this atom
329 average_charge *= 1./(double)NoFragments;
330
331 return average_charge;
332}
333
334void addToParticleRegistry(
335 const ParticleFactory &factory,
336 const periodentafel &periode,
337 const std::map<atomId_t, double> &_fitted_charges)
338{
339 typedef std::map<atomId_t, double> fitted_charges_t;
340 for (fitted_charges_t::const_iterator chargeiter = _fitted_charges.begin();
341 chargeiter != _fitted_charges.end(); ++chargeiter) {
342 const atom * const walker = World::getInstance().getAtom(AtomById(chargeiter->first));
343 ASSERT( walker != NULL,
344 "PotentialFitPartialChargesAction::performCall() - atom "
345 +toString(chargeiter->first)+" not present in the World?");
346 const double &charge = chargeiter->second;
347 const atomicNumber_t elementno = walker->getElementNo();
348 const std::string name = Particle::findFreeName(periode, elementno);
349 LOG(1, "INFO: Adding particle " << name << " for atom "
350 << *walker << " with element " << elementno << ", charge " << charge);
351 factory.createInstance(name, elementno, charge);
352 }
353}
354
355ActionState::ptr PotentialFitPartialChargesAction::performCall()
356{
357 // check for selected atoms
358 const World &world = const_cast<const World &>(World::getInstance());
359 if (world.beginAtomSelection() == world.endAtomSelection()) {
360 STATUS("There are no atoms selected for fitting partial charges to.");
361 return Action::failure;
362 }
363
364 /// obtain possible fragments to each selected atom
365 const AtomFragmentsMap &atomfragments = AtomFragmentsMap::getConstInstance();
366 if (!atomfragments.checkCompleteness()) {
367 STATUS("AtomFragmentsMap failed internal consistency check, missing forcekeysets?");
368 return Action::failure;
369 }
370 std::set<KeySet> fragments =
371 accumulateKeySetsForAtoms( atomfragments.getMap(), world.getSelectedAtoms());
372
373 // reduce given fragments to homologous graphs to avoid multiple fittings
374 typedef std::map<KeySet, HomologyGraph> KeysetsToGraph_t;
375 KeysetsToGraph_t keyset_graphs;
376 typedef std::map<HomologyGraph, PartialNucleiChargeFitter::charges_t> GraphFittedChargeMap_t;
377 GraphFittedChargeMap_t fittedcharges_per_fragment;
378 getKeySetsToGraphMapping(keyset_graphs, fittedcharges_per_fragment, fragments, atomfragments);
379
380 /// then go through all fragments and get partial charges for each
381 const bool status = getPartialChargesForAllGraphs(
382 fittedcharges_per_fragment,
383 World::getInstance().getHomologies(),
384 params.radius.get(),
385 params.enforceZeroCharge.get());
386 if (!status)
387 return Action::failure;
388
389 /// obtain average charge for each atom the fitted charges over all its fragments
390 typedef std::map<atomId_t, double> fitted_charges_t;
391 fitted_charges_t fitted_charges;
392 for (World::AtomSelectionConstIterator atomiter = world.beginAtomSelection();
393 atomiter != world.endAtomSelection(); ++atomiter) {
394 const double average_charge = fitAverageChargeToAtom(
395 atomiter->second, atomfragments, keyset_graphs, fittedcharges_per_fragment);
396
397 if (average_charge != 0.) {
398 LOG(2, "DEBUG: For atom " << atomiter->first << " we have an average charge of "
399 << average_charge);
400
401 fitted_charges.insert( std::make_pair(atomiter->first, average_charge) );
402 }
403 }
404
405 /// place all fitted charges into ParticleRegistry
406 addToParticleRegistry(
407 ParticleFactory::getConstInstance(),
408 *World::getInstance().getPeriode(),
409 fitted_charges);
410
411 return Action::success;
412}
413
414ActionState::ptr PotentialFitPartialChargesAction::performUndo(ActionState::ptr _state) {
415 return Action::success;
416}
417
418ActionState::ptr PotentialFitPartialChargesAction::performRedo(ActionState::ptr _state){
419 return Action::success;
420}
421
422bool PotentialFitPartialChargesAction::canUndo() {
423 return false;
424}
425
426bool PotentialFitPartialChargesAction::shouldUndo() {
427 return false;
428}
429/** =========== end of function ====================== */
Note: See TracBrowser for help on using the repository browser.