Loading...
Searching...
No Matches
BasinHoppingJob Class Reference

#include <BasinHoppingJob.h>

Inheritance diagram for BasinHoppingJob:

Public Member Functions

 BasinHoppingJob (std::unique_ptr< Parameters > parameters)
 ~BasinHoppingJob (void)=default
std::vector< std::string > run (void) override
 Virtual run; used solely for dynamic dispatch.
Public Member Functions inherited from eonc::Job
 Job (std::unique_ptr< Parameters > parameters)
 Job (std::shared_ptr< Potential > potPassed, const Parameters &parameters)
virtual ~Job ()=default
JobType getType ()

Private Member Functions

VectorXd calculateDistanceFromCenter (Matter *matter)
AtomMatrix displaceRandom (double maxDisplacement)
void randomSwap (Matter *matter)
std::vector< long > getElements (Matter *matter)

Private Attributes

std::shared_ptr< Mattercurrent
std::shared_ptr< Mattertrial
std::vector< std::string > returnFiles
int jump_count {0}
int disp_count {0}
int swap_count {0}
int fcalls {0}
std::vector< std::shared_ptr< Matter > > uniqueStructures
std::vector< double > uniqueEnergies
eonc::log::Scoped log

Additional Inherited Members

Protected Attributes inherited from eonc::Job
JobType jtype
Parameters params
std::shared_ptr< Potentialpot

Detailed Description

Definition at line 20 of file BasinHoppingJob.h.

Constructor & Destructor Documentation

◆ BasinHoppingJob()

eonc::BasinHoppingJob::BasinHoppingJob ( std::unique_ptr< Parameters > parameters)
inline

Definition at line 22 of file BasinHoppingJob.h.

23 : Job(std::move(parameters)),
24 current{std::make_shared<Matter>(pot, params)},
25 trial{std::make_shared<Matter>(pot, params)},
26 fcalls{0} {}
std::shared_ptr< Matter > current
std::shared_ptr< Matter > trial
Job(std::unique_ptr< Parameters > parameters)
Definition Job.h:58
std::shared_ptr< Potential > pot
Definition Job.h:55
Parameters params
Definition Job.h:54

◆ ~BasinHoppingJob()

Member Function Documentation

◆ calculateDistanceFromCenter()

Definition at line 454 of file BasinHoppingJob.cpp.

454 {
455 AtomMatrix pos = matter->getPositions();
456 Vector3d cen(0, 0, 0);
457 int num = matter->numberOfAtoms();
458
459 cen = pos.colwise().sum() / static_cast<double>(num);
460
461 VectorXd dist(num);
462
463 for (int n = 0; n < num; n++) {
464 pos.row(n) -= cen;
465 dist(n) = pos.row(n).norm();
466 }
467
468 return dist;
469}
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
long int numberOfAtoms() const
Definition Matter.cpp:209
const AtomMatrix & getPositions() const
Definition Matter.cpp:236

◆ displaceRandom()

AtomMatrix BasinHoppingJob::displaceRandom ( double maxDisplacement)
private

Definition at line 334 of file BasinHoppingJob.cpp.

334 {
335 disp_count++;
336 // Create a random displacement
337 AtomMatrix displacement;
338 displacement.resize(trial->numberOfAtoms(), 3);
339 displacement.setZero();
340 VectorXd distvec = calculateDistanceFromCenter(current.get());
341 int num = trial->numberOfAtoms();
342 int m = 0;
343 if (params.basin_hopping_options.single_atom_displace) {
344 m = randomInt(0, trial->numberOfAtoms() - 1);
345 num = m + 1;
346 }
347
348 for (int i = m; i < num; i++) {
349 double dist = distvec(i);
350 double disp = 0.0; // displacement size, possibly scaled
351
352 if (!trial->getFixed(i)) {
353 if (params.basin_hopping_options.displacement_algorithm == "standard") {
354 disp = curDisplacement;
355 }
356 // scale displacement linearly with the particle radius
357 else if (params.basin_hopping_options.displacement_algorithm ==
358 "linear") {
359 double Cs = curDisplacement / distvec.maxCoeff();
360 disp = Cs * dist;
361 }
362 // scale displacement quadratically with the particle radius
363 else if (params.basin_hopping_options.displacement_algorithm ==
364 "quadratic") {
365 double Cq = curDisplacement / (distvec.maxCoeff() * distvec.maxCoeff());
366 disp = Cq * dist * dist;
367 } else {
369 QUILL_LOG_CRITICAL(log, "Unknown displacement_algorithm\n");
370 throw std::invalid_argument(
371 std::format("[Basin Hopping] unknown displacement_algorithm: {}",
372 params.basin_hopping_options.displacement_algorithm));
373 }
374 for (int j = 0; j < 3; j++) {
375 if (params.basin_hopping_options.displacement_distribution ==
376 "uniform") {
377 displacement(i, j) = randomDouble(2 * disp) - disp;
378 } else if (params.basin_hopping_options.displacement_distribution ==
379 "gaussian") {
380 displacement(i, j) = gaussRandom(0.0, disp);
381 } else {
383 QUILL_LOG_CRITICAL(log, "Unknown displacement_distribution\n");
384 throw std::invalid_argument(std::format(
385 "[Basin Hopping] unknown displacement_distribution: {}",
386 params.basin_hopping_options.displacement_distribution));
387 }
388 }
389 }
390 }
391 return displacement;
392}
eonc::log::Scoped log
VectorXd calculateDistanceFromCenter(Matter *matter)
long randomInt(int lower, int upper)
double randomDouble()
double gaussRandom(double avg, double std)
quill::Logger * traceback() noexcept
Get or create the "_traceback" logger for traceback logging.
Definition EonLogger.h:88

◆ getElements()

std::vector< long > BasinHoppingJob::getElements ( Matter * matter)
private

Definition at line 434 of file BasinHoppingJob.cpp.

434 {
435 std::array<int, 118> allElements{};
436 std::vector<long> elements;
437
438 for (long y = 0; y < matter->numberOfAtoms(); ++y) {
439 if (!matter->getFixed(y)) {
440 const int index = matter->getAtomicNr(y);
441 allElements[index] = 1;
442 }
443 }
444
445 for (int i = 0; i < 118; ++i) {
446 if (allElements[i] != 0) {
447 elements.push_back(i);
448 }
449 }
450
451 return elements;
452}
long getAtomicNr(long int atom) const
Definition Matter.cpp:392
int getFixed(long int atom) const
1 if every Cartesian axis of the atom is fixed, else 0.
Definition Matter.cpp:402

◆ randomSwap()

void BasinHoppingJob::randomSwap ( Matter * matter)
private

Definition at line 394 of file BasinHoppingJob.cpp.

394 {
395 swap_count++;
396 std::vector<long> Elements;
397 Elements = getElements(matter);
398
399 long ela;
400 long elb;
401 long ia = randomInt(0, Elements.size() - 1);
402 ela = Elements.at(ia);
403 Elements.erase(Elements.begin() + ia);
404
405 long ib = randomInt(0, Elements.size() - 1);
406 elb = Elements.at(ib);
407
408 int changera = 0;
409 int changerb = 0;
410
411 changera = randomInt(0, matter->numberOfAtoms() - 1);
412 while (matter->getAtomicNr(changera) != ela) {
413 changera = randomInt(0, matter->numberOfAtoms() - 1);
414 }
415
416 changerb = randomInt(0, matter->numberOfAtoms() - 1);
417 while (matter->getAtomicNr(changerb) != elb) {
418 changerb = randomInt(0, matter->numberOfAtoms() - 1);
419 }
420
421 double posax = matter->getPosition(changera, 0);
422 double posay = matter->getPosition(changera, 1);
423 double posaz = matter->getPosition(changera, 2);
424
425 matter->setPosition(changera, 0, matter->getPosition(changerb, 0));
426 matter->setPosition(changera, 1, matter->getPosition(changerb, 1));
427 matter->setPosition(changera, 2, matter->getPosition(changerb, 2));
428
429 matter->setPosition(changerb, 0, posax);
430 matter->setPosition(changerb, 1, posay);
431 matter->setPosition(changerb, 2, posaz);
432}
std::vector< long > getElements(Matter *matter)
void setPosition(long int atom, int axis, double position)
Definition Matter.cpp:222
double getPosition(long int atom, int axis) const
Definition Matter.cpp:218

◆ run()

std::vector< std::string > BasinHoppingJob::run ( void )
overridevirtual

Virtual run; used solely for dynamic dispatch.

Implements eonc::Job.

Definition at line 29 of file BasinHoppingJob.cpp.

29 {
30 bool swapMove;
31 double swap_accept = 0.0;
32 jump_count = 0; // count of jump movies
33 swap_count = 0; // count of swap moves
34 disp_count = 0; // count of displacement moves
35 int consecutive_rejected_trials = 0;
36 double totalAccept = 0.0;
37 std::unique_ptr<Matter> minTrial = std::make_unique<Matter>(pot, params);
38 std::unique_ptr<Matter> swapTrial = std::make_unique<Matter>(pot, params);
39
40 std::string conFilename = getRelevantFile(params.main_options.conFilename);
41 if (!eonc::io::io_ok(current->con2matter(conFilename))) {
42 QUILL_LOG_CRITICAL(log, "Failed to load {}", conFilename);
43 throw std::runtime_error("failed to load " + conFilename);
44 }
45
46 // Sanity Check
47 std::vector<long> Elements;
48 Elements = getElements(current.get());
49 if (params.basin_hopping_options.swap_probability > 0 &&
50 Elements.size() == 1) {
52 QUILL_LOG_CRITICAL(log,
53 "error: [Basin Hopping] swap move probability must be "
54 "zero if there is only one element type\n");
55 throw std::invalid_argument(
56 "[Basin Hopping] swap_probability must be zero with one element type");
57 }
58
59 double randomProb =
60 params.basin_hopping_options.initial_random_structure_probability;
61 if (randomProb > 0.0) {
62 QUILL_LOG_DEBUG(log, "generating random structure with probability {:.4f}",
63 randomProb);
64 }
65 double u = eonc::helpers::random();
66 if (u < params.basin_hopping_options.initial_random_structure_probability) {
67 AtomMatrix randomPositions = current->getPositionsFree();
68 for (int i = 0; i < current->numberOfFreeAtoms(); i++) {
69 for (int j = 0; j < 3; j++) {
70 randomPositions(i, j) = eonc::helpers::random();
71 }
72 }
73 randomPositions *= current->getCell();
74 current->setPositionsFree(randomPositions);
75
76 pushApart(current, params.basin_hopping_options.push_apart_distance);
77 }
78
79 *trial = *current;
80 *minTrial = *current;
81
82 current->relax(true);
83
84 double currentEnergy = current->getPotentialEnergy();
85 double minimumEnergy = currentEnergy;
86
87 auto minimumEnergyStructure = std::make_shared<Matter>(pot, params);
88 *minimumEnergyStructure = *current;
89 int nsteps = params.basin_hopping_options.steps +
90 params.basin_hopping_options.quenching_steps;
91 long totalfc;
92
93 QUILL_LOG_DEBUG(
94 log, "[Basin Hopping] {:4s} {:12s} {:12s} {:12s} {:4s} {:5s} {:5s}",
95 "step", "current", "trial", "global min", "fc", "ar", "md");
96 QUILL_LOG_DEBUG(
97 log, "[Basin Hopping] {:4s} {:12s} {:12s} {:12s} {:4s} {:5s} {:5s}",
98 "----", "-------", "-----", "----------", "--", "--", "--");
99
100 int recentAccept = 0;
101 double curDisplacement = params.basin_hopping_options.displacement;
102
103 for (int step = 0; step < nsteps; step++) {
104
105 // Swap or displace
106 if (randomDouble(1.0) < params.basin_hopping_options.swap_probability &&
107 step < params.basin_hopping_options.steps) {
108 *swapTrial = *current;
109 randomSwap(swapTrial.get());
110 swapMove = true;
111 *minTrial = *swapTrial;
112 } else {
113 AtomMatrix displacement;
114 displacement = displaceRandom(curDisplacement);
115
116 trial->setPositions(current->getPositions() + displacement);
117 swapMove = false;
118 pushApart(trial, params.basin_hopping_options.push_apart_distance);
119
120 *minTrial = *trial;
121 }
122
123 if (params.debug_options.write_movies) {
124 if (!eonc::io::io_ok(trial->matter2con("trials", true))) {
125 QUILL_LOG_WARNING(log, "Failed to append trials movie frame");
126 }
127 }
128
129 // Potential::fcalls = 0;
130 minTrial->relax(true);
131 // int minfcalls = Potential::fcalls;
132
133 double deltaE = minTrial->getPotentialEnergy() - currentEnergy;
134 double p = 0.0;
135 if (step >= params.basin_hopping_options.steps) {
136 if (deltaE <= 0.0) {
137 p = 1.0;
138 }
139 } else {
140 if (deltaE <= 0.0) {
141 p = 1.0;
142 } else {
143 p = std::exp(-deltaE /
144 (params.main_options.temperature * 8.6173324e-5));
145 }
146 }
147
148 bool accepted = false;
149 if (randomDouble(1.0) < p) {
150 accepted = true;
151 if (params.basin_hopping_options.significant_structure) {
152 *current = *minTrial;
153 } else {
154 *current = *trial;
155 }
156 if (swapMove) {
157 swap_accept += 1;
158 }
159 if (step < params.basin_hopping_options.steps) {
160 totalAccept += 1;
161 recentAccept += 1;
162 }
163
164 currentEnergy = minTrial->getPotentialEnergy();
165
166 if (currentEnergy < minimumEnergy) {
167 minimumEnergy = currentEnergy;
168 *minimumEnergyStructure = *minTrial;
169 if (!eonc::io::io_ok(minimumEnergyStructure->matter2con("min.con"))) {
170 QUILL_LOG_WARNING(log, "Failed to write min.con");
171 }
172 }
173
174 if (params.basin_hopping_options.write_unique) {
175 bool newStructure = true;
176 for (unsigned int i = 0; i < uniqueEnergies.size(); i++) {
177 // if minTrial has a different energy or a different structure
178 // it is new, otherwise it is old
179 if (std::fabs(currentEnergy - uniqueEnergies[i]) <
180 params.structure_comparison_options.energy_difference) {
181 if (current->compare(*uniqueStructures[i],
182 params.structure_comparison_options
183 .indistinguishable_atoms)) {
184 newStructure = false;
185 }
186 }
187 }
188
189 if (newStructure) {
190 uniqueEnergies.push_back(currentEnergy);
191 auto currentCopy = std::make_shared<Matter>(pot, params);
192 *currentCopy = *current;
193 uniqueStructures.push_back(currentCopy);
194
195 char fname[128];
196 snprintf(fname, 128, "min_%.5i.con", step + 1);
197 if (!eonc::io::io_ok(current->matter2con(fname))) {
198 QUILL_LOG_WARNING(log, "Failed to write {}", fname);
199 }
200 returnFiles.push_back(fname);
201
202 snprintf(fname, 128, "energy_%.5i.dat", step + 1);
203 returnFiles.push_back(fname);
204 {
205 std::ofstream fh(fname);
206 if (fh)
207 fh << std::format("{:.10e}\n", currentEnergy);
208 }
209 }
210 }
211
212 consecutive_rejected_trials = 0; // STC: I think this should go here.
213 } else {
214 consecutive_rejected_trials++;
215 }
216
217 if (params.debug_options.write_movies) {
218 if (!eonc::io::io_ok(minTrial->matter2con("movie", true))) {
219 QUILL_LOG_WARNING(log, "Failed to append basin-hopping movie frame");
220 }
221 }
222
223 // totalfc = Potential::fcallsTotal;
224 char acceptReject[2];
225 acceptReject[1] = '\0';
226 if (accepted) {
227 acceptReject[0] = 'A';
228 } else {
229 acceptReject[0] = 'R';
230 }
231 // QUILL_LOG_DEBUG(log, "[Basin Hopping] %5i %12.3f %12.3f %12.3f %4i
232 // %5.3f %5.3f %1s\n",
233 // step+1, currentEnergy, minTrial->getPotentialEnergy(),
234 // minimumEnergy, minfcalls, totalAccept/((double)step+1),
235 // curDisplacement, acceptReject);
236 // fprintf(pFile, "%6i %9ld %12.4e %12.4e\n",step+1,totalfc,currentEnergy,
237 // minTrial->getPotentialEnergy());
238
239 if (minimumEnergy < params.basin_hopping_options.stop_energy) {
240 break;
241 }
242
243 if (consecutive_rejected_trials == params.basin_hopping_options.jump_max &&
244 step < params.basin_hopping_options.steps) {
245 consecutive_rejected_trials = 0;
246 AtomMatrix jump;
247 for (int j = 0; j < params.basin_hopping_options.jump_steps; j++) {
248 jump_count++;
249 jump = displaceRandom(curDisplacement);
250 current->setPositions(current->getPositions() + jump);
251 if (params.basin_hopping_options.significant_structure) {
252 pushApart(current, params.basin_hopping_options.push_apart_distance);
253 current->relax(true);
254 }
255 currentEnergy = current->getPotentialEnergy();
256 if (currentEnergy < minimumEnergy) {
257 minimumEnergy = currentEnergy;
258 *minimumEnergyStructure = *current;
259 }
260 }
261 }
262
263 int nadjust = params.basin_hopping_options.adjust_period;
264 double adjustFraction = params.basin_hopping_options.adjust_fraction;
265 if ((step + 1) % nadjust == 0 &&
266 params.basin_hopping_options.adjust_displacement) {
267 double recentRatio =
268 static_cast<double>(recentAccept) / static_cast<double>(nadjust);
269 if (recentRatio > params.basin_hopping_options.target_ratio) {
270 curDisplacement *= 1.0 + adjustFraction;
271 } else {
272 curDisplacement *= 1.0 - adjustFraction;
273 }
274
275 // QUILL_LOG_DEBUG(log, "recentRatio %.3f md: %.3f\n", recentRatio,
276 // curDisplacement);
277 recentAccept = 0;
278 }
279 }
280 /* Save Results */
281
282 std::string resultsFilename("results.dat");
283
284 if (params.debug_options.write_movies) {
285 std::string movieFilename("movie.con");
286 returnFiles.push_back(movieFilename);
287 }
288
289 {
290 std::ofstream out(resultsFilename, std::ios::binary);
291 if (!out) {
292 QUILL_LOG_CRITICAL(log, "Failed to open {}", resultsFilename);
293 throw std::runtime_error("failed to open " + resultsFilename);
294 }
295 out << std::format("{} termination_reason\n", 0);
296 out << "GOOD termination_reason_text\n";
297 out << "basin_hopping job_type\n";
298 out << std::format("{:.12e} minimum_energy\n", minimumEnergy);
299 out << std::format("{} random_seed\n", params.main_options.randomSeed);
300 const double nsteps_ratio = params.basin_hopping_options.steps;
301 out << std::format("{:.3f} acceptance_ratio\n",
302 nsteps_ratio ? totalAccept / nsteps_ratio : 0.0);
303 if (params.basin_hopping_options.swap_probability > 0) {
304 out << std::format(
305 "{:.3f} swap_acceptance_ratio\n",
306 swap_count ? swap_accept / static_cast<double>(swap_count) : 0.0);
307 }
308 out << std::format("{} total_normal_displacement_steps\n",
310 params.basin_hopping_options.quenching_steps);
311 out << std::format("{} total_jump_steps\n", jump_count);
312 out << std::format("{} total_swap_steps\n", swap_count);
313 out << std::format("{} total_force_calls\n",
314 PotRegistry::get().total_force_calls());
315 out.close();
316 if (!out) {
317 QUILL_LOG_CRITICAL(log, "Failed to write {}", resultsFilename);
318 throw std::runtime_error("failed to write " + resultsFilename);
319 }
320 returnFiles.push_back(resultsFilename);
321 }
322
323 std::string productFilename("min.con");
324 if (eonc::io::io_ok(minimumEnergyStructure->matter2con(productFilename))) {
325 returnFiles.push_back(productFilename);
326 } else {
327 QUILL_LOG_ERROR(log, "Failed to write {}", productFilename);
328 }
329
330 // minTrial and swapTrial automatically cleaned up by unique_ptr
331 return returnFiles;
332}
std::vector< std::string > returnFiles
std::vector< std::shared_ptr< Matter > > uniqueStructures
std::vector< double > uniqueEnergies
void randomSwap(Matter *matter)
AtomMatrix displaceRandom(double maxDisplacement)
static PotRegistry & get() noexcept
Process-lifetime singleton.
double random(long newSeed=0)
void pushApart(std::shared_ptr< Matter > m1, double minDistance)
std::string getRelevantFile(std::string filename)
constexpr bool io_ok(IoStatus s) noexcept
Definition ConFileIO.h:38

Member Data Documentation

◆ current

std::shared_ptr<Matter> eonc::BasinHoppingJob::current
private

Definition at line 35 of file BasinHoppingJob.h.

◆ disp_count

Definition at line 40 of file BasinHoppingJob.h.

40{0}; // count of displacement moves

◆ fcalls

Definition at line 42 of file BasinHoppingJob.h.

42{0};

◆ jump_count

Definition at line 39 of file BasinHoppingJob.h.

39{0}; // count of jump moves

◆ log

◆ returnFiles

std::vector<std::string> eonc::BasinHoppingJob::returnFiles
private

Definition at line 38 of file BasinHoppingJob.h.

◆ swap_count

Definition at line 41 of file BasinHoppingJob.h.

41{0}; // count of swap moves

◆ trial

std::shared_ptr<Matter> eonc::BasinHoppingJob::trial
private

Definition at line 36 of file BasinHoppingJob.h.

◆ uniqueEnergies

std::vector<double> eonc::BasinHoppingJob::uniqueEnergies
private

Definition at line 45 of file BasinHoppingJob.h.

◆ uniqueStructures

std::vector<std::shared_ptr<Matter> > eonc::BasinHoppingJob::uniqueStructures
private

Definition at line 44 of file BasinHoppingJob.h.


The documentation for this class was generated from the following files: