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

#include <BasinHoppingJob.h>

Inheritance diagram for eonc::BasinHoppingJob:

Public Member Functions

 BasinHoppingJob (std::unique_ptr< Parameters > parameters, Runtime &rt)
 ~BasinHoppingJob (void)=default
std::vector< std::string > run (void) override
 Virtual run; used solely for dynamic dispatch.
Public Member Functions inherited from eonc::Job
void adoptRuntime (std::unique_ptr< Runtime > rt)
 Take ownership of a Runtime previously passed as Runtime&.
 Job (std::unique_ptr< Parameters > parameters, Runtime &rt)
 Borrow: caller keeps Runtime alive (CLI stack / Python Session).
 Job (std::unique_ptr< Parameters > parameters, std::unique_ptr< Runtime > rt)
 Own a Runtime (one-shot makeJob / rvalue).
 Job (std::unique_ptr< Parameters > parameters)
 Own a default-constructed Runtime.
 Job (std::shared_ptr< Potential > potPassed, const Parameters &parameters)
virtual ~Job ()=default
JobType getType ()
PotRegistry & pots () noexcept
void releasePotential ()
 Drop the Potential so on_destroyed is recorded before Runtime dies.

Static Public Member Functions

static double metropolisProbability (double de, double kB, double temperature)
 Metropolis weight for energy change de.

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< Matter > current
std::shared_ptr< Matter > trial
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

Friends

class BasinHoppingDisplaceAccess
struct BasinHoppingElementsTest

Additional Inherited Members

Protected Attributes inherited from eonc::Job
JobType jtype
Parameters params
std::unique_ptr< Runtime > owned_runtime_
 Non-null when this Job owns the composition root (one-shot makeJob).
Runtime * runtime_
 Always valid: either owned_runtime_.get() or a caller-owned Runtime.
std::shared_ptr< Potential > pot

Detailed Description

Definition at line 22 of file BasinHoppingJob.h.

Constructor & Destructor Documentation

◆ BasinHoppingJob()

eonc::BasinHoppingJob::BasinHoppingJob ( std::unique_ptr< Parameters > parameters,
Runtime & rt )
inline

Definition at line 24 of file BasinHoppingJob.h.

25 : Job(std::move(parameters), rt),
26 current{std::make_shared<Matter>(pot, params)},
27 trial{std::make_shared<Matter>(pot, params)}, fcalls{0} {}
std::shared_ptr< Matter > current
std::shared_ptr< Matter > trial
Job(std::unique_ptr< Parameters > parameters, Runtime &rt)
Borrow: caller keeps Runtime alive (CLI stack / Python Session).
Definition Job.h:76
std::shared_ptr< Potential > pot
Definition Job.h:63
Parameters params
Definition Job.h:58

◆ ~BasinHoppingJob()

eonc::BasinHoppingJob::~BasinHoppingJob ( void )
default

Member Function Documentation

◆ calculateDistanceFromCenter()

VectorXd eonc::BasinHoppingJob::calculateDistanceFromCenter ( Matter * matter)
private

Definition at line 475 of file BasinHoppingJob.cpp.

475 {
476 AtomMatrix pos = matter->getPositions();
477 Vector3d cen(0, 0, 0);
478 int num = matter->numberOfAtoms();
479
480 cen = pos.colwise().sum() / static_cast<double>(num);
481
482 VectorXd dist(num);
483
484 for (int n = 0; n < num; n++) {
485 pos.row(n) -= cen;
486 dist(n) = pos.row(n).norm();
487 }
488
489 return dist;
490}
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37

◆ displaceRandom()

AtomMatrix eonc::BasinHoppingJob::displaceRandom ( double maxDisplacement)
private

Definition at line 333 of file BasinHoppingJob.cpp.

333 {
334 disp_count++;
335 // Create a random displacement
336 AtomMatrix displacement;
337 displacement.resize(trial->numberOfAtoms(), 3);
338 displacement.setZero();
339 VectorXd distvec = calculateDistanceFromCenter(current.get());
340 // Coincident atoms share a zero radius. Dividing by it writes NaN and
341 // the hop cannot leave the center. Those atoms are the outer shell, so
342 // the step stays the unscaled displacement.
343 const double radius = distvec.size() > 0 ? distvec.maxCoeff() : 0.0;
344 int num = trial->numberOfAtoms();
345 int m = 0;
346 if (params.basin_hopping_options().single_atom_displace) {
347 m = eonc::rng::randomInt(0, trial->numberOfAtoms() - 1);
348 num = m + 1;
349 }
350
351 for (int i = m; i < num; i++) {
352 double dist = distvec(i);
353 double disp = 0.0; // displacement size, possibly scaled
354
355 if (!trial->getFixed(i)) {
356 const std::string &algorithm =
357 params.basin_hopping_options().displacement_algorithm;
358 if (algorithm == "standard") {
359 disp = curDisplacement;
360 }
361 // scale displacement linearly with the particle radius
362 else if (algorithm == "linear") {
363 disp =
364 radius == 0.0 ? curDisplacement : curDisplacement * (dist / radius);
365 }
366 // scale displacement quadratically with the particle radius
367 else if (algorithm == "quadratic") {
368 if (radius == 0.0) {
369 disp = curDisplacement;
370 } else {
371 const double scale = dist / radius;
372 disp = curDisplacement * scale * scale;
373 }
374 } else {
376 QUILL_LOG_CRITICAL(log, "Unknown displacement_algorithm\n");
377 throw std::invalid_argument(
378 std::format("[Basin Hopping] unknown displacement_algorithm: {}",
379 params.basin_hopping_options().displacement_algorithm));
380 }
381 for (int j = 0; j < 3; j++) {
382 if (params.basin_hopping_options().displacement_distribution ==
383 "uniform") {
384 displacement(i, j) = eonc::rng::randomDouble(2 * disp) - disp;
385 } else if (params.basin_hopping_options().displacement_distribution ==
386 "gaussian") {
387 displacement(i, j) = eonc::rng::gaussRandom(0.0, disp);
388 } else {
390 QUILL_LOG_CRITICAL(log, "Unknown displacement_distribution\n");
391 throw std::invalid_argument(std::format(
392 "[Basin Hopping] unknown displacement_distribution: {}",
393 params.basin_hopping_options().displacement_distribution));
394 }
395 }
396 }
397 }
398 return displacement;
399}
VectorXd calculateDistanceFromCenter(Matter *matter)
eonc::log::Scoped log
quill::Logger * traceback() noexcept
Get or create the "_traceback" logger for traceback logging.
Definition EonLogger.h:88
long randomInt(int lower, int upper)
double randomDouble()
double gaussRandom(double avg, double std)

◆ getElements()

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

Definition at line 451 of file BasinHoppingJob.cpp.

451 {
452 // Z is 0..118. A 118-slot table stores 118 one past the end.
453 constexpr int kElementSlots = 119;
454 std::array<int, kElementSlots> allElements{};
455 std::vector<long> elements;
456
457 for (long y = 0; y < matter->numberOfAtoms(); ++y) {
458 if (!matter->getFixed(y)) {
459 const long z = matter->getAtomicNr(y);
460 if (z >= 0 && z < kElementSlots) {
461 allElements[static_cast<size_t>(z)] = 1;
462 }
463 }
464 }
465
466 for (int i = 0; i < kElementSlots; ++i) {
467 if (allElements[static_cast<size_t>(i)] != 0) {
468 elements.push_back(i);
469 }
470 }
471
472 return elements;
473}

◆ metropolisProbability()

double eonc::BasinHoppingJob::metropolisProbability ( double de,
double kB,
double temperature )
static

Metropolis weight for energy change de.

Divides by kB * temperature only for an uphill hop when both are positive. Non-positive temperature or kB refuses that hop.

Definition at line 322 of file BasinHoppingJob.cpp.

323 {
324 if (!(de > 0.0)) {
325 return 1.0;
326 }
327 if (!(temperature > 0.0) || !(kB > 0.0)) {
328 return 0.0;
329 }
330 return std::exp(-de / (kB * temperature));
331}

◆ randomSwap()

void eonc::BasinHoppingJob::randomSwap ( Matter * matter)
private

Definition at line 401 of file BasinHoppingJob.cpp.

401 {
402 swap_count++;
403 std::vector<long> Elements;
404 Elements = getElements(matter);
405
406 long ela;
407 long elb;
408 long ia = eonc::rng::randomInt(0, Elements.size() - 1);
409 ela = Elements.at(ia);
410 Elements.erase(Elements.begin() + ia);
411
412 long ib = eonc::rng::randomInt(0, Elements.size() - 1);
413 elb = Elements.at(ib);
414
415 int changera = 0;
416 int changerb = 0;
417
418 changera = eonc::rng::randomInt(0, matter->numberOfAtoms() - 1);
419 int guard = 0;
420 while ((matter->getAtomicNr(changera) != ela || matter->getFixed(changera)) &&
421 guard < 10000) {
422 changera = eonc::rng::randomInt(0, matter->numberOfAtoms() - 1);
423 guard++;
424 }
425 changerb = eonc::rng::randomInt(0, matter->numberOfAtoms() - 1);
426 guard = 0;
427 while ((matter->getAtomicNr(changerb) != elb || matter->getFixed(changerb) ||
428 changerb == changera) &&
429 guard < 10000) {
430 changerb = eonc::rng::randomInt(0, matter->numberOfAtoms() - 1);
431 guard++;
432 }
433 if (matter->getAtomicNr(changera) != ela || matter->getFixed(changera) ||
434 matter->getAtomicNr(changerb) != elb || matter->getFixed(changerb)) {
435 return;
436 }
437
438 double posax = matter->getPosition(changera, 0);
439 double posay = matter->getPosition(changera, 1);
440 double posaz = matter->getPosition(changera, 2);
441
442 matter->setPosition(changera, 0, matter->getPosition(changerb, 0));
443 matter->setPosition(changera, 1, matter->getPosition(changerb, 1));
444 matter->setPosition(changera, 2, matter->getPosition(changerb, 2));
445
446 matter->setPosition(changerb, 0, posax);
447 matter->setPosition(changerb, 1, posay);
448 matter->setPosition(changerb, 2, posaz);
449}
std::vector< long > getElements(Matter *matter)

◆ run()

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

Virtual run; used solely for dynamic dispatch.

Implements eonc::Job.

Definition at line 32 of file BasinHoppingJob.cpp.

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

◆ BasinHoppingDisplaceAccess

friend class BasinHoppingDisplaceAccess
friend

Definition at line 37 of file BasinHoppingJob.h.

◆ BasinHoppingElementsTest

friend struct BasinHoppingElementsTest
friend

Definition at line 44 of file BasinHoppingJob.h.

Member Data Documentation

◆ current

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

Definition at line 41 of file BasinHoppingJob.h.

◆ disp_count

int eonc::BasinHoppingJob::disp_count {0}
private

Definition at line 47 of file BasinHoppingJob.h.

47{0}; // count of displacement moves

◆ fcalls

int eonc::BasinHoppingJob::fcalls {0}
private

Definition at line 49 of file BasinHoppingJob.h.

49{0};

◆ jump_count

int eonc::BasinHoppingJob::jump_count {0}
private

Definition at line 46 of file BasinHoppingJob.h.

46{0}; // count of jump moves

◆ log

eonc::log::Scoped eonc::BasinHoppingJob::log
private

Definition at line 53 of file BasinHoppingJob.h.

◆ returnFiles

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

Definition at line 45 of file BasinHoppingJob.h.

◆ swap_count

int eonc::BasinHoppingJob::swap_count {0}
private

Definition at line 48 of file BasinHoppingJob.h.

48{0}; // count of swap moves

◆ trial

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

Definition at line 42 of file BasinHoppingJob.h.

◆ uniqueEnergies

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

Definition at line 52 of file BasinHoppingJob.h.

◆ uniqueStructures

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

Definition at line 51 of file BasinHoppingJob.h.


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