votca 2026-dev
Loading...
Searching...
No Matches
md2qmengine.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2021 The VOTCA Development Team (http://www.votca.org)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18// Local VOTCA includes
20
21namespace votca {
22namespace xtp {
23
25 std::string molkey = "topology.molecules.molecule";
26 std::vector<tools::Property*> molecules = topology_map.Select(molkey);
27 if (SameValueForMultipleEntries<std::string>(molecules, "mdname")) {
28 throw std::runtime_error("Multiple molecules have same mdname");
29 }
30 std::string segkey = "segments.segment";
31 std::vector<tools::Property*> segments_all;
32 for (tools::Property* mol : molecules) {
33 std::vector<tools::Property*> segments = mol->Select(segkey);
34 if (SameValueForMultipleEntries<std::string>(segments, "name")) {
35 throw std::runtime_error("Multiple segments in molecule:" +
36 mol->get("mdname").as<std::string>() +
37 " have same name");
38 }
39 segments_all.insert(segments_all.end(), segments.begin(), segments.end());
40 for (tools::Property* seg : segments) {
41 std::string fragkey = "fragments.fragment";
42 std::vector<tools::Property*> fragments = seg->Select(fragkey);
43 if (SameValueForMultipleEntries<std::string>(fragments, "name")) {
44 throw std::runtime_error(
45 "Multiple fragments have same name in molecule " +
46 mol->get("mdname").as<std::string>() + " segment " +
47 seg->get("name").as<std::string>());
48 }
49
50 std::vector<std::string> atomnames_seg;
51 for (tools::Property* frag : fragments) {
52 std::vector<std::string> atomnames =
53 frag->get("mdatoms").as<std::vector<std::string>>();
54 atomnames_seg.insert(atomnames_seg.end(), atomnames.begin(),
55 atomnames.end());
56 }
57 std::sort(atomnames_seg.begin(), atomnames_seg.end()); // O(N log N)
58 if (adjacent_find(atomnames_seg.begin(), atomnames_seg.end()) !=
59 atomnames_seg.end()) {
60 throw std::runtime_error(
61 "Multiple mdatoms have same identifier in molecule " +
62 mol->get("mdname").as<std::string>() + " segment " +
63 seg->get("name").as<std::string>());
64 }
65 }
66 }
67 if (SameValueForMultipleEntries<std::string>(segments_all, "name")) {
68 throw std::runtime_error("Multiple segments have same name");
69 }
70}
71
73 const csg::Molecule* mol, const std::vector<Index>& atom_ids_map) const {
74 std::vector<Index> IDs;
75 IDs.reserve(mol->BeadCount());
76 for (const csg::Bead* bead : mol->Beads()) {
77 IDs.push_back(bead->getId());
78 }
79 std::sort(IDs.begin(), IDs.end());
80 Index offset = IDs[0] - atom_ids_map[0];
81 for (Index i = 1; i < Index(IDs.size()); i++) {
82 if (IDs[i] - atom_ids_map[i] != offset) {
83 throw std::runtime_error(
84 "AtomIds offset could not be determined, either our MD trajectory or "
85 "your mapping file have wrong Atom ids");
86 }
87 }
88 return offset;
89}
90
91bool Md2QmEngine::CheckMolWhole(const Topology& top, const Segment& seg) const {
92 Eigen::Vector3d CoM = seg.getPos();
93 bool whole = true;
94 for (const Atom& a : seg) {
95 Eigen::Vector3d r = a.getPos() - CoM;
96 Eigen::Vector3d r_pbc = top.PbShortestConnect(CoM, a.getPos());
97 Eigen::Vector3d shift = r_pbc - r;
98 if (shift.norm() > 1e-9) {
99 whole = false;
100 break;
101 }
102 }
103 return whole;
104}
105
107 for (Segment& seg : top.Segments()) {
108 seg.calcPos();
109 while (!CheckMolWhole(top, seg)) {
110 Eigen::Vector3d CoM = seg.getPos();
111 for (Atom& a : seg) {
112 Eigen::Vector3d r = a.getPos() - CoM;
113 Eigen::Vector3d r_pbc = top.PbShortestConnect(CoM, a.getPos());
114 Eigen::Vector3d shift = r_pbc - r;
115 if (shift.norm() > 1e-9) {
116 a.Translate(shift);
117 }
118 }
119 seg.calcPos();
120 }
121 }
122}
123
125
126 tools::Property topology_map;
127 topology_map.LoadFromXML(mapfile_);
128 CheckMappingFile(topology_map);
129 Topology xtptop;
130 xtptop.setStep(top.getStep());
131 xtptop.setTime(top.getTime());
132 xtptop.setBox(top.getBox() * tools::conv::nm2bohr, top.getBoxType());
133
134 // which segmentname does an atom belong to molname atomid
135 std::map<std::string, std::map<Index, std::string>> MolToSegMap;
136
137 // which atomids belong to molname
138 std::map<std::string, std::vector<Index>> MolToAtomIds;
139
140 // names of segments in one molecule;
141 std::map<std::string, std::vector<std::string>> SegsinMol;
142
143 std::string molkey = "topology.molecules.molecule";
144 std::vector<tools::Property*> molecules = topology_map.Select(molkey);
145 std::string segkey = "segments.segment";
146
147 for (tools::Property* mol : molecules) {
148 // get the name of this molecule
149 std::string molname = mol->get("mdname").as<std::string>();
150 // get all segment-mapping info
151 std::vector<tools::Property*> segments = mol->Select(segkey);
152 std::vector<std::string> segnames;
153 std::vector<Index> atomids;
154 // now go through all the defined segments
155 for (tools::Property* seg : segments) {
156 // get the name of this segment and add to segnames vector
157 std::string segname = seg->get("name").as<std::string>();
158 segnames.push_back(segname);
159 std::string fragkey = "fragments.fragment";
160 // get all fragement mapping info
161 std::vector<tools::Property*> fragments = seg->Select(fragkey);
162 // go over all fragments in this segement
163 for (tools::Property* frag : fragments) {
164 // get all mdatom names from this fragment
165 std::vector<std::string> atomnames =
166 frag->get("mdatoms").as<std::vector<std::string>>();
167 // go over all atoms
168 for (const std::string& atomname : atomnames) {
169 // split atom entry at :
170 tools::Tokenizer tok_atom_name(atomname, ":");
171 std::vector<std::string> entries = tok_atom_name.ToVector();
172 if (entries.size() != 3) {
173 throw std::runtime_error("Atom entry " + atomname +
174 " is not well formatted");
175 }
176 // format should be RESNUM:ATOMNAME:ATOMID we do not care about the
177 // first two
178 Index atomid = 0;
179 try {
180 atomid = std::stoi(entries[2]);
181 } catch (std::invalid_argument& e) {
182 throw std::runtime_error("Atom entry " + atomname +
183 " is not well formatted");
184 }
185 if (votca::Log::verbose()) {
186 std::cout << "... ... processing mapping information for atom "
187 << atomname << " with ID " << atomid << std::endl;
188 }
189 atomids.push_back(atomid);
190 MolToSegMap[molname][atomid] = segname;
191 }
192 }
193 }
194 std::sort(atomids.begin(), atomids.end());
195 MolToAtomIds[molname] = atomids;
196 SegsinMol[molname] = segnames;
197 }
198
199 // Build a direct, one-time "bead ID -> directly-bonded partner bead
200 // IDs" lookup, from the MD-level topology's own real, actual bond
201 // connectivity (csg::Topology::BondedInteractions(), confirmed
202 // directly, by reading csg's own interaction.h/topology.h, to
203 // already exist and be available exactly here -- this is a purely
204 // *geometric* bond list, from the original MD topology itself, NOT
205 // yet aware of segment/fragment membership at all). Only 2-bead
206 // ("B"/IBond-style) interactions are relevant here -- angles (3-bead)
207 // and dihedrals (4-bead) do not represent a direct bond between two
208 // atoms at all, so BeadCount() != 2 entries are skipped.
209 std::map<Index, std::vector<Index>> bead_bonded_partners;
210 for (csg::Interaction* interaction : top.BondedInteractions()) {
211 if (interaction->BeadCount() != 2) {
212 continue;
213 }
214 Index id1 = interaction->getBeadId(0);
215 Index id2 = interaction->getBeadId(1);
216 bead_bonded_partners[id1].push_back(id2);
217 bead_bonded_partners[id2].push_back(id1);
218 }
219
220 // Real, direct, always-visible (not gated behind -v/verbose at all)
221 // warning, worked through directly with the user: if the underlying
222 // MD topology genuinely has no real bond connectivity at all (e.g.
223 // a topology reader that only ever provides atom positions, no real
224 // bond data at all -- this exact situation is exactly what this
225 // whole session's own real, direct debugging arc started from,
226 // csg::GMXTopologyReader itself never having read any real bonds at
227 // all, before that specific fix), external-bond detection itself
228 // (below) can never find anything at all, meaning
229 // IPodCoupling::EvalJob's own H-saturation at cut segment boundaries
230 // (see transport_theory.rst's own "H-Saturation of Cut Segment
231 // Boundaries" section) can never actually fire at all either --
232 // silently producing dangling valences at every cut segment
233 // boundary instead, with no other, direct signal of this at all
234 // until (if ever) a user separately, manually notices something is
235 // wrong much further downstream. Warning here instead, directly, at
236 // the earliest point this is actually knowable at all (right after
237 // bead_bonded_partners is built, genuinely reflecting the real,
238 // complete state of top.BondedInteractions() itself), gives a real,
239 // direct, upfront signal instead.
240 if (bead_bonded_partners.empty()) {
241 std::cout
242 << "\nWARNING: the MD topology being mapped contains no real bond "
243 "connectivity at all (no bonded interactions were found within "
244 "it) -- this topology reader may only provide atom positions, "
245 "not real, actual bond data. Automatic H-saturation of cut "
246 "segment boundaries (used by e.g. the ipodcoupling calculator) "
247 "will not be able to detect any external bonds at all, and will "
248 "silently do nothing at all, rather than saturating anything -- "
249 "check that the real, actual topology file/reader used here "
250 "genuinely provides real bond data, not just atom positions."
251 << std::endl;
252 }
253
254 // go through all molecules in MD topology
255 for (const csg::Molecule& mol : top.Molecules()) {
256
257 // lookup all segment *names* in this molecule
258 const std::vector<std::string> segnames = SegsinMol[mol.getName()];
259 std::vector<Segment>& topology_segments = xtptop.Segments();
260 Index IdOffset = DetermineAtomNumOffset(&mol, MolToAtomIds[mol.getName()]);
261
262 if (votca::Log::verbose()) {
263 std::cout << "... Mapping molecule " << mol.getId() << ", name "
264 << mol.getName() << ", # of segments " << segnames.size()
265 << ", atomID offset " << IdOffset << std::endl;
266 }
267
268 for (const std::string& segname : segnames) {
269
270 Index segid = topology_segments.size();
271 // construct a segment
272 Segment this_segment = Segment(segname, segid);
273 this_segment.AddMoleculeId(mol.getId());
274
275 // create atomlist
276 for (const csg::Bead* bead : mol.Beads()) {
277 // check if it belongs to this segment, and add it
278 if (segname == MolToSegMap[mol.getName()][bead->getId() - IdOffset]) {
279 Atom atom(bead->getResnr(), bead->getName(), bead->getId(),
280 bead->getPos() * tools::conv::nm2bohr, bead->getType());
281
282 // Check each of this bead's own, real, direct bonded
283 // partners (from the lookup built above): if a partner's
284 // own segment assignment differs from this atom's own
285 // segname (or the partner has no segment assignment at
286 // all -- e.g. an unmapped, non-charge-transport-relevant
287 // spectator atom), the bond crosses the segment boundary --
288 // record the direction toward it directly on this atom, for
289 // later use (H-saturation) once this direction has also
290 // been carried through SegmentMapper's own, later,
291 // rigid-body MD->QM-template transform. Only the first such
292 // partner found is recorded (an atom with more than one
293 // external bond is rare, and not handled specially here).
294 auto it = bead_bonded_partners.find(bead->getId());
295 if (it != bead_bonded_partners.end()) {
296 for (Index partner_id : it->second) {
297 const csg::Bead* partner_bead = top.getBead(partner_id);
298 // MoleculeByIndex() is not const-qualified (confirmed
299 // directly, from a real compile error), and this
300 // function only ever sees top as const -- so the
301 // partner's own parent molecule is found directly here
302 // instead, by matching getMoleculeId() against each
303 // molecule's own getId() (the same, canonical way this
304 // function itself already identifies molecules, per its
305 // own, pre-existing this_segment.AddMoleculeId(mol.getId())
306 // call above), via the const-compatible Molecules()
307 // overload -- this also sidesteps a second, separate
308 // uncertainty MoleculeByIndex() would have carried:
309 // whether its own index parameter expects a molecule ID
310 // or an array position, which are not necessarily the
311 // same thing.
312 const csg::Molecule* partner_mol = nullptr;
313 for (const csg::Molecule& candidate : top.Molecules()) {
314 if (candidate.getId() == partner_bead->getMoleculeId()) {
315 partner_mol = &candidate;
316 break;
317 }
318 }
319 if (partner_mol == nullptr) {
320 continue;
321 }
322 Index partner_offset = DetermineAtomNumOffset(
323 partner_mol, MolToAtomIds[partner_mol->getName()]);
324 std::string partner_segname =
325 MolToSegMap[partner_mol->getName()]
326 [partner_bead->getId() - partner_offset];
327 if (partner_segname != segname) {
328 Eigen::Vector3d direction =
329 (partner_bead->getPos() - bead->getPos()) *
331 atom.setExternalBondDirection(direction, partner_bead->getId());
332 break;
333 }
334 }
335 }
336
337 // Records ALL of this bead's own, real, direct bonded
338 // partners (not just the ones crossing a segment boundary,
339 // unlike the external-bond-direction detection above, which
340 // deliberately stops at the first one found) -- needed for
341 // FragmentSaturator's own, planned OpenBabel-based
342 // relaxation step, which needs a fragment's own, full,
343 // internal connectivity to set up its own force field
344 // correctly at all (see Atom::getBondedPartnerIds's own
345 // header comment for why). These are RAW, MD-level partner
346 // IDs -- not yet translated into QM-level IDs here, matching
347 // the same "raw here, translated later, in SegmentMapper"
348 // split already used for the external-bond direction.
349 if (it != bead_bonded_partners.end()) {
350 for (Index partner_id : it->second) {
351 atom.AddBondedPartner(partner_id);
352 }
353 }
354
355 this_segment.push_back(atom);
356 }
357 }
358 // add segment to topology
359 topology_segments.push_back(this_segment);
360 }
361 }
362
363 // Second pass: resolves each atom's own external-bond partner
364 // (recorded above only as a raw MD-level ATOM id,
365 // getExternalBondPartnerAtomId() -- deliberately transient, see its
366 // own header comment) to the actual SEGMENT that partner atom
367 // belongs to. Cannot be done inline, in the loop above -- at the
368 // point a given atom's own external bond is first detected, later
369 // segments (in molecule/segname iteration order) do not exist yet
370 // at all, so there is no way yet to know which segment a partner
371 // atom that happens to belong to one of them will end up in.
372 //
373 // First builds a direct "MD-level atom id -> segment id" lookup,
374 // spanning every segment in the whole, now-complete xtptop, then
375 // uses it to resolve every atom's own, already-recorded
376 // getExternalBondPartnerAtomId() into the real, actual, persisted
377 // getExternalBondPartnerSegmentId() -- needed downstream (a planned
378 // linking-segment graph, and the decision of whether a given
379 // external bond is already satisfied within an assembled
380 // supermolecule, worked through directly with the user before this
381 // was implemented) to identify not just THAT a given bond crosses a
382 // segment boundary, but SPECIFICALLY WHICH segment it crosses into.
383 std::map<Index, Index> md_atom_id_to_segment_id;
384 for (const Segment& seg : xtptop.Segments()) {
385 for (const Atom& segatom : seg) {
386 md_atom_id_to_segment_id[segatom.getId()] = seg.getId();
387 }
388 }
389 for (Segment& seg : xtptop.Segments()) {
390 for (Atom& segatom : seg) {
391 if (!segatom.hasExternalBond()) {
392 continue;
393 }
394 auto it =
395 md_atom_id_to_segment_id.find(segatom.getExternalBondPartnerAtomId());
396 if (it != md_atom_id_to_segment_id.end()) {
397 segatom.setExternalBondPartnerSegmentId(it->second);
398 }
399 }
400 }
401
402 MakeSegmentsWholePBC(xtptop);
403
404 return xtptop;
405}
406
407template <class T>
409 const std::vector<tools::Property*>& props, std::string valuetag) const {
410 std::vector<T> entries;
411 for (tools::Property* prop : props) {
412 entries.push_back(prop->get(valuetag).as<T>());
413 }
414 std::sort(entries.begin(), entries.end()); // O(N log N)
415 return adjacent_find(entries.begin(), entries.end()) != entries.end();
416}
417
418} // namespace xtp
419} // namespace votca
virtual const Eigen::Vector3d & getPos() const
Definition basebead.h:166
Index getMoleculeId() const noexcept
Get the id of the molecule the bead is a part of, if the molecule id has not been set return topology...
Definition basebead.h:78
Index getId() const noexcept
Gets the id of the bead.
Definition basebead.h:52
information about a bead
Definition bead.h:50
base class for all interactions
Definition interaction.h:40
information about molecules
Definition molecule.h:45
const std::string & getName() const
get the name of the molecule
Definition molecule.h:51
Index BeadCount() const
get the number of beads in the molecule
Definition molecule.h:65
const std::vector< Bead * > & Beads() const
Definition molecule.h:67
topology of the whole system
Definition topology.h:81
double getTime() const
Definition topology.h:317
BoundaryCondition::eBoxtype getBoxType() const
Definition topology.h:394
const Eigen::Matrix3d & getBox() const
Definition topology.h:298
Index getStep() const
Definition topology.h:329
Bead * getBead(const Index i)
Returns a pointer to the bead with index i.
Definition topology.h:227
MoleculeContainer & Molecules()
Definition topology.h:182
InteractionContainer & BondedInteractions()
Definition topology.h:189
class to manage program options with xml serialization functionality
Definition property.h:55
std::vector< Property * > Select(const std::string &filter)
select property based on a filter
Definition property.cc:185
void LoadFromXML(std::string filename)
Definition property.cc:238
break string into words
Definition tokenizer.h:72
std::vector< T > ToVector()
store all words in a vector of type T, does type conversion.
Definition tokenizer.h:109
void push_back(const T &atom)
const Eigen::Vector3d & getPos() const
void AddBondedPartner(Index partner_id)
Definition atom.h:168
void setExternalBondDirection(const Eigen::Vector3d &dir, Index partner_atom_id)
Definition atom.h:120
void CheckMappingFile(tools::Property &topology_map) const
void MakeSegmentsWholePBC(Topology &top) const
Index DetermineAtomNumOffset(const csg::Molecule *mol, const std::vector< Index > &atom_ids_map) const
bool SameValueForMultipleEntries(const std::vector< tools::Property * > &props, std::string tag) const
Topology map(const csg::Topology &top) const
bool CheckMolWhole(const Topology &top, const Segment &seg) const
void AddMoleculeId(Index id)
Definition segment.h:91
Container for segments and box and atoms.
Definition topology.h:41
Eigen::Vector3d PbShortestConnect(const Eigen::Vector3d &r1, const Eigen::Vector3d &r2) const
Definition topology.cc:134
void setTime(double time)
Definition topology.h:78
void setBox(const Eigen::Matrix3d &box, csg::BoundaryCondition::eBoxtype boxtype=csg::BoundaryCondition::typeAuto)
Definition topology.cc:79
std::vector< Segment > & Segments()
Definition topology.h:58
void setStep(Index step)
Definition topology.h:76
const double nm2bohr
Definition constants.h:47
Charge transport classes.
Definition ERIs.h:28
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
static bool verbose()
Definition globals.h:32