votca 2026-dev
Loading...
Searching...
No Matches
jobtopology.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2023 The VOTCA Development Team
3 * (http://www.votca.org)
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License")
6 *
7 * You may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19
20// Standard includes
21#include <algorithm>
22#include <numeric>
23#include <stdexcept>
24#include <string>
25
26// Local VOTCA includes
28#include "votca/tools/version.h"
33#include "votca/xtp/qmregion.h"
36
37namespace votca {
38namespace xtp {
39
40namespace {
41// Unweighted mean of a mapped molecule's own atom positions, matching
42// EwaldRegion's own Centroid (and legacy's PolarSeg::CalcPos). Only the
43// lattice image is taken from it there, so staying consistent with the
44// background's convention matters more than the choice itself.
45template <class T>
46Eigen::Vector3d UnweightedCentroid(const T& mol) {
47 Eigen::Vector3d pos = Eigen::Vector3d::Zero();
48 Index n = 0;
49 for (const auto& atom : mol) {
50 pos += atom.getPos();
51 ++n;
52 }
53 return (n > 0) ? Eigen::Vector3d(pos / double(n)) : pos;
54}
55} // namespace
56
57std::vector<const tools::Property*> JobTopology::SortRegionsDefbyId(
58 const tools::Property& regions_def) const {
59 std::vector<const tools::Property*> view;
60 for (const auto& child : regions_def) {
61 view.push_back(&child);
62 }
63 std::sort(view.begin(), view.end(),
64 [](const tools::Property* A, const tools::Property* B) {
65 return (A->get("id").as<Index>()) < (B->get("id").as<Index>());
66 });
67 return view;
68}
69
70std::vector<std::string> ModifyRegionOptionsFromJobFileRegion(
71 tools::Property& opt, const tools::Property& job_opt) {
72 std::vector<std::string> modified_options;
73 for (auto& child : opt) {
74 if (child.as<std::string>() == "jobfile") {
75 if (job_opt.exists(child.name())) {
76 const tools::Property& job_opt_child = job_opt.get(child.name());
77 modified_options.push_back(child.path() + "." + child.name());
78 if (job_opt_child.HasChildren()) {
79 child.value() = "";
80 for (const auto& job_childchild : job_opt_child) {
81 child.add(job_childchild);
82 }
83 } else {
84 child.value() = job_opt_child.value();
85 }
86 } else {
87 throw std::runtime_error("Requested to replace:" + child.path() + "." +
88 child.name() +
89 " but no option in jobfile found");
90 }
91 } else if (job_opt.exists(child.name())) {
92 std::vector<std::string> child_modified_options =
94 job_opt.get(child.name()));
95 modified_options.insert(modified_options.end(),
96 child_modified_options.begin(),
97 child_modified_options.end());
98 }
99 }
100 return modified_options;
101}
102
104
105 const tools::Property& prop = job_.getInput();
106 std::vector<const tools::Property*> regions_def_job =
107 prop.Select("regions.*region");
108
109 for (const auto& job_region : regions_def_job) {
110
111 Index job_region_id = job_region->get("id").as<Index>();
112
113 auto found_prop =
114 std::find_if(regions_def.begin(), regions_def.end(),
115 [job_region_id](const tools::Property& p) {
116 return p.get("id").as<Index>() == job_region_id;
117 });
118 if (found_prop == regions_def.end()) {
119 throw std::runtime_error(
120 "Your jobfile options want to modify a region with id " +
121 std::to_string(job_region_id) +
122 " it does not exist in your region specifications.");
123 }
124
125 tools::Property& region = *found_prop;
126 if (region.name() != job_region->name()) {
127 throw std::runtime_error("Types of region with id" +
128 std::to_string(job_region_id) +
129 " do not agree jobfile:" + job_region->name() +
130 " options:" + region.name());
131 }
132
133 std::vector<std::string> changed_options =
134 ModifyRegionOptionsFromJobFileRegion(region, *job_region);
135
136 if (!changed_options.empty()) {
137 XTP_LOG(Log::info, log_) << " Region " << job_region_id
138 << " is modified by jobfile" << std::flush;
140 << " Replacing the following paths with jobfile entries"
141 << std::flush;
142 for (const std::string& path : changed_options) {
143 XTP_LOG(Log::info, log_) << " - " << path << std::flush;
144 }
145 }
146 }
147}
148
150 const Topology& top, std::pair<std::string, tools::Property> options) {
151
152 CheckEnumerationOfRegions(options.second);
153 ModifyOptionsByJobFile(options.second);
154
155 std::vector<std::vector<SegId>> region_seg_ids =
156 PartitionRegions(options.second, top);
157
158 // // around this point the whole jobtopology will be centered
159 CreateRegions(options, top, region_seg_ids);
160 XTP_LOG(Log::error, log_) << " Regions created" << std::flush;
161 for (const auto& region : regions_) {
162 XTP_LOG(Log::error, log_) << *region << std::flush;
163 }
164
165 return;
166}
167
168template <class T>
169void JobTopology::ShiftPBC(const Topology& top, const Eigen::Vector3d& center,
170 T& mol) const {
171 Eigen::Vector3d r_pbc = top.PbShortestConnect(center, mol.getPos());
172 Eigen::Vector3d r = mol.getPos() - center;
173 Eigen::Vector3d shift = r_pbc - r;
174 if (shift.norm() > 1e-9) {
175 mol.Translate(shift);
176 }
177}
178
180 const std::pair<std::string, tools::Property>& options, const Topology& top,
181 const std::vector<std::vector<SegId>>& region_seg_ids) {
182 std::string mapfile = options.first;
183 // around this point the whole jobtopology will be centered for removing pbc
184 Eigen::Vector3d center = top.getSegment(region_seg_ids[0][0].Id()).getPos();
185
186 // The union of every segment carved out of the periodic cell, collected
187 // as each molecule is mapped and shifted. An EwaldRegion needs the whole
188 // union to know which background copies to suppress, and no single
189 // client region knows it -- see EwaldRegion::RegisterForeground. Built
190 // from the region types generically rather than naming qm and polar,
191 // because every region here draws its segments from the same Topology
192 // the background was converged on, so anything it owns is genuinely
193 // carved out of the cell.
194 std::vector<std::pair<Index, Eigen::Vector3d>> foreground_union;
195
196 for (const tools::Property& region_def : options.second) {
197 Index id = region_def.get("id").as<Index>();
198 const std::vector<SegId>& seg_ids = region_seg_ids[id];
199 std::string type = region_def.name();
200 std::unique_ptr<Region> region;
201 QMRegion QMdummy(0, log_, "");
202 StaticRegion Staticdummy(0, log_);
203 PolarRegion Polardummy(0, log_);
204 EwaldRegion Ewalddummy(0, log_);
205 if (type == QMdummy.identify()) {
206 std::unique_ptr<QMRegion> qmregion =
207 std::make_unique<QMRegion>(id, log_, workdir_);
208 QMMapper qmmapper(log_);
209 qmmapper.LoadMappingFile(mapfile);
210 for (const SegId& seg_index : seg_ids) {
211 const Segment& segment = top.getSegment(seg_index.Id());
212 QMMolecule mol = qmmapper.map(segment, seg_index);
213 mol.setType("qm" + std::to_string(id));
214 ShiftPBC(top, center, mol);
215 foreground_union.push_back({seg_index.Id(), UnweightedCentroid(mol)});
216 qmregion->push_back(mol);
217 }
218 region = std::move(qmregion);
219 } else if (type == Polardummy.identify()) {
220 std::unique_ptr<PolarRegion> polarregion =
221 std::make_unique<PolarRegion>(id, log_);
222 PolarMapper polmap(log_);
223 polmap.LoadMappingFile(mapfile);
224 for (const SegId& seg_index : seg_ids) {
225 const Segment& segment = top.getSegment(seg_index.Id());
226
227 PolarSegment mol = polmap.map(segment, seg_index);
228
229 ShiftPBC(top, center, mol);
230 mol.setType("mm" + std::to_string(id));
231 foreground_union.push_back({seg_index.Id(), UnweightedCentroid(mol)});
232 polarregion->push_back(mol);
233 }
234 region = std::move(polarregion);
235 } else if (type == Staticdummy.identify()) {
236 std::unique_ptr<StaticRegion> staticregion =
237 std::make_unique<StaticRegion>(id, log_);
238 StaticMapper staticmap(log_);
239 staticmap.LoadMappingFile(mapfile);
240 for (const SegId& seg_index : seg_ids) {
241 const Segment& segment = top.getSegment(seg_index.Id());
242 StaticSegment mol = staticmap.map(segment, seg_index);
243 mol.setType("mm" + std::to_string(id));
244 ShiftPBC(top, center, mol);
245 foreground_union.push_back({seg_index.Id(), UnweightedCentroid(mol)});
246 staticregion->push_back(mol);
247 }
248 region = std::move(staticregion);
249
250 } else if (type == Ewalddummy.identify()) {
251 // Owns no segments of the job's topology: it represents the whole
252 // periodic cell, loaded from the background calculator's own
253 // checkpoint by its Initialize below. seg_ids is empty for it by
254 // construction (see CreateRegionSegIds).
255 region = std::make_unique<EwaldRegion>(id, log_);
256 } else {
257 throw std::runtime_error("Region type not known!");
258 }
259 region->Initialize(region_def);
260 regions_.push_back(std::move(region));
261 }
262
263 // After every region exists, so the declaration covers the complete
264 // union, and before any of them is evaluated.
265 for (std::unique_ptr<Region>& reg : regions_) {
266 EwaldRegion* ewald = dynamic_cast<EwaldRegion*>(reg.get());
267 if (ewald != nullptr) {
268 ewald->RegisterForeground(foreground_union);
269 }
270 }
271}
272
273void JobTopology::WriteToPdb(std::string filename) const {
274
275 csg::PDBWriter writer;
276 writer.Open(filename, false);
277 writer.WriteHeader("Job:" + std::to_string(this->job_.getId()));
278 for (const std::unique_ptr<Region>& reg : regions_) {
279 reg->WritePDB(writer);
280 }
281 writer.Close();
282}
283
284std::vector<std::vector<SegId>> JobTopology::PartitionRegions(
285 const tools::Property& regions_def, const Topology& top) const {
286
287 std::vector<const tools::Property*> sorted_regions =
288 SortRegionsDefbyId(regions_def);
289
290 std::vector<Index> explicitly_named_segs_per_region;
291 std::vector<std::vector<SegId>> segids_per_region;
292 std::vector<bool> processed_segments =
293 std::vector<bool>(top.Segments().size(), false);
294 for (const tools::Property* region_def : sorted_regions) {
295
296 // An ewaldregion is the exception: it takes no segments from this
297 // topology at all. Its content is the periodic background written by
298 // the ewaldbackground calculator, which it loads from a checkpoint,
299 // so demanding segments or a cutoff for it would be meaningless.
300 EwaldRegion Ewalddummy(0, log_);
301 const bool is_ewald = region_def->name() == Ewalddummy.identify();
302 if (!is_ewald && !region_def->exists("segments") &&
303 !region_def->exists("cutoff")) {
304 throw std::runtime_error(
305 "Region definition needs either segments or a cutoff to find "
306 "segments");
307 }
308 if (is_ewald) {
309 segids_per_region.push_back(std::vector<SegId>());
310 continue;
311 }
312 std::vector<SegId> seg_ids;
313 if (region_def->exists("segments")) {
314 seg_ids = region_def->get("segments").as<std::vector<SegId>>();
315 for (const SegId& seg_id : seg_ids) {
316 if (seg_id.Id() > Index(top.Segments().size() - 1)) {
317 throw std::runtime_error("Segment id is not in topology");
318 }
319 processed_segments[seg_id.Id()] = true;
320 }
321 }
322 explicitly_named_segs_per_region.push_back(Index(seg_ids.size()));
323
324 if (region_def->exists("cutoff")) {
325 double cutoff =
326 tools::conv::nm2bohr * region_def->get("cutoff.radius").as<double>();
327
328 std::string seg_geometry =
329 region_def->get("cutoff.geometry").as<std::string>();
330 double min = top.getBox().diagonal().minCoeff();
331 if (cutoff > 0.5 * min) {
332 throw std::runtime_error(
333 (boost::format("Cutoff is larger than half the box size. Maximum "
334 "allowed cutoff is %1$1.1f") %
335 (tools::conv::bohr2nm * 0.5 * min))
336 .str());
337 }
338 std::vector<SegId> center = seg_ids;
339
340 Index id = region_def->get("cutoff.region").as<Index>();
341 bool only_explicit = region_def->get("cutoff.explicit_segs").as<bool>();
342 if (id < Index(segids_per_region.size())) {
343 center = segids_per_region[id];
344 if (only_explicit) {
345 Index no_of_segs = explicitly_named_segs_per_region[id];
346 if (no_of_segs == 0) {
347 throw std::runtime_error(
348 "Region with id '" + std::to_string(id) +
349 "' does not have explicitly named segments");
350 }
351 center.resize(no_of_segs, SegId(0, "n"));
352 // need the second argument because resize can also increase
353 // capacity of vector and then needs a constructor,
354 // here we shrink, so should not happen
355 }
356 } else if (id != region_def->get("id").as<Index>()) {
357 throw std::runtime_error("Region with id '" + std::to_string(id) +
358 "' used for cutoff does not exist");
359 }
360
361 if (center.empty()) {
362 throw std::runtime_error(
363 "Region needs either a segment or another region to which to "
364 "apply "
365 "the cutoff");
366 }
367 for (const SegId& segid : center) {
368 const Segment& center_seg = top.getSegment(segid.Id());
369 for (const Segment& otherseg : top.Segments()) {
370 if (center_seg.getId() == otherseg.getId() ||
371 processed_segments[otherseg.getId()]) {
372 continue;
373 }
374 if (top.PbShortestConnect(center_seg.getPos(), otherseg.getPos())
375 .norm() < cutoff) {
376 seg_ids.push_back(SegId(otherseg.getId(), seg_geometry));
377 processed_segments[otherseg.getId()] = true;
378 }
379 }
380 }
381 }
382 segids_per_region.push_back(seg_ids);
383 }
384 return segids_per_region;
385}
386
388 const tools::Property& regions_def) const {
389 std::vector<Index> reg_ids;
390 for (const tools::Property& region_def : regions_def) {
391 reg_ids.push_back(region_def.get("id").as<Index>());
392 }
393
394 std::vector<Index> v(reg_ids.size());
395 std::iota(v.begin(), v.end(), 0);
396 if (!std::is_permutation(reg_ids.begin(), reg_ids.end(), v.begin())) {
397 throw std::runtime_error(
398 "Region id definitions are not clear. You must start at id 0 and "
399 "then "
400 "ascending order. i.e. 0 1 2 3.");
401 }
402
403 // The whole job topology is centred on region 0's FIRST segment, and an
404 // ewaldregion owns none -- its entry in region_seg_ids is empty by
405 // construction. Reading element zero of it is undefined behaviour, so
406 // the rule is stated here rather than left to a crash far downstream.
407 EwaldRegion Ewalddummy(0, log_);
408 for (const tools::Property& region_def : regions_def) {
409 if (region_def.name() == Ewalddummy.identify() &&
410 region_def.get("id").as<Index>() == 0) {
411 throw std::runtime_error(
412 "An ewaldregion cannot be region 0: it owns no segments of the "
413 "job's topology, and the job is centred on region 0's first "
414 "segment.");
415 }
416 }
417}
418
419void JobTopology::WriteToHdf5(std::string filename) const {
420 std::lock_guard<std::recursive_mutex> lock(
423 CheckpointWriter a = cpf.getWriter();
424 a(job_.getId(), "jobid");
425 a(votca::tools::ToolsVersionStr(), "XTPVersion");
426 a(jobtopology_version(), "version");
427 for (const auto& region : regions_) {
429 cpf.getWriter("region_" + std::to_string(region->getId()));
430 region->WriteToCpt(w);
431 }
432}
433
434void JobTopology::ReadFromHdf5(std::string filename) {
435 std::lock_guard<std::recursive_mutex> lock(
438 CheckpointReader a = cpf.getReader();
439 Index id = -1;
440 a(id, "jobid");
441 if (id != job_.getId()) {
442 throw std::runtime_error(
443 "Jobid from checkpoint file does not agree with jobid" +
444 std::to_string(id) + ":" + std::to_string(job_.getId()));
445 }
446 regions_.clear();
447 Index no_regions = a.getNumDataSets();
448 regions_.reserve(no_regions);
449
450 QMRegion QMdummy(0, log_, "");
451 StaticRegion Staticdummy(0, log_);
452 PolarRegion Polardummy(0, log_);
453
454 for (Index i = 0; i < no_regions; i++) {
455 CheckpointReader r = cpf.getReader("region_" + std::to_string(i));
456 std::string type;
457 r(type, "type");
458
459 std::unique_ptr<Region> region = nullptr;
460 if (type == QMdummy.identify()) {
461 region = std::make_unique<QMRegion>(i, log_, workdir_);
462 } else if (type == Polardummy.identify()) {
463 region = std::make_unique<PolarRegion>(i, log_);
464 } else if (type == Staticdummy.identify()) {
465 region = std::make_unique<StaticRegion>(i, log_);
466 } else {
467 throw std::runtime_error("Region type:" + type + " not known!");
468 }
469 region->ReadFromCpt(r);
470 if (id != region->getId()) {
471 throw std::runtime_error("read in the wrong region, ids are mismatched.");
472 }
473 regions_.push_back(std::move(region));
474 }
475}
476
477} // namespace xtp
478} // namespace votca
void WriteHeader(std::string header)
Definition pdbwriter.cc:41
void Close() override
Definition pdbwriter.cc:51
void Open(std::string file, bool bAppend=false) override
Definition pdbwriter.cc:33
class to manage program options with xml serialization functionality
Definition property.h:55
Property & get(const std::string &key)
get existing property
Definition property.cc:79
std::string & value()
reference to value of property
Definition property.h:153
iterator end()
end iterator for child properties
Definition property.h:191
bool exists(const std::string &key) const
check whether property exists
Definition property.cc:122
T as() const
return value as type
Definition property.h:283
iterator begin()
iterator to first child property
Definition property.h:188
bool HasChildren() const
does the property have children?
Definition property.h:182
std::string & name()
name of property
Definition property.h:159
std::vector< Property * > Select(const std::string &filter)
select property based on a filter
Definition property.cc:185
void setType(std::string type)
const Eigen::Vector3d & getPos() const
CheckpointReader getReader()
CheckpointWriter getWriter()
The periodic Ewald background, as a Region.
Definition ewaldregion.h:76
std::string identify() const override
Definition ewaldregion.h:81
void RegisterForeground(const std::vector< std::pair< Index, Eigen::Vector3d > > &foreground)
std::vector< std::unique_ptr< Region > > regions_
void ShiftPBC(const Topology &top, const Eigen::Vector3d &center, T &mol) const
std::vector< const tools::Property * > SortRegionsDefbyId(const tools::Property &regions_def) const
void ReadFromHdf5(std::string filename)
static constexpr int jobtopology_version()
void CreateRegions(const std::pair< std::string, tools::Property > &options, const Topology &top, const std::vector< std::vector< SegId > > &region_seg_ids)
std::vector< std::vector< SegId > > PartitionRegions(const tools::Property &regions_def, const Topology &top) const
void WriteToPdb(std::string filename) const
void CheckEnumerationOfRegions(const tools::Property &regions_def) const
void BuildRegions(const Topology &top, std::pair< std::string, tools::Property > options)
void WriteToHdf5(std::string filename) const
void ModifyOptionsByJobFile(tools::Property &regions_def) const
std::string identify() const override
Definition polarregion.h:49
std::string identify() const override
Definition qmregion.h:82
void LoadMappingFile(const std::string &mapfile)
AtomContainer map(const Segment &seg, const SegId &segid) const
std::string identify() const override
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
std::vector< Segment > & Segments()
Definition topology.h:58
const Eigen::Matrix3d & getBox() const
Definition topology.h:64
Segment & getSegment(Index id)
Definition topology.h:55
#define XTP_LOG(level, log)
Definition logger.h:40
const double bohr2nm
Definition constants.h:46
const double nm2bohr
Definition constants.h:47
const std::string & ToolsVersionStr()
Definition version.cc:33
std::recursive_mutex & Hdf5Mutex()
Charge transport classes.
Definition ERIs.h:28
ClassicalSegment< PolarSite > PolarSegment
SegmentMapper< PolarSegment > PolarMapper
SegmentMapper< QMMolecule > QMMapper
ClassicalSegment< StaticSite > StaticSegment
SegmentMapper< StaticSegment > StaticMapper
std::vector< std::string > ModifyRegionOptionsFromJobFileRegion(tools::Property &opt, const tools::Property &job_opt)
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26