Scine::Readuct  6.0.0
This is the SCINE module ReaDuct.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Pages
IrcTask.h
Go to the documentation of this file.
1 
7 #ifndef READUCT_IRCTASK_H_
8 #define READUCT_IRCTASK_H_
9 
10 /* Readuct */
11 #include "Tasks/Task.h"
12 /* Scine */
20 /* std c++ */
21 #include <boost/exception/diagnostic_information.hpp>
22 #include <boost/filesystem.hpp>
23 #include <cstdio>
24 #include <fstream>
25 
26 namespace Scine {
27 namespace Readuct {
28 
32 class IrcTask : public Task {
33  public:
40  IrcTask(std::vector<std::string> input, std::vector<std::string> output, std::shared_ptr<Core::Log> logger = nullptr)
41  : Task(std::move(input), std::move(output), std::move(logger)) {
42  }
43 
44  std::string name() const override {
45  return "IRC Optimizations";
46  }
47 
48  bool run(SystemsMap& systems, Utils::UniversalSettings::ValueCollection taskSettings, bool testRunOnly = false,
49  std::vector<std::function<void(const int&, const Utils::AtomCollection&, const Utils::Results&, const std::string&)>>
50  observers = {}) const final {
52  // Warn if only one output system was specified
53  if (_output.size() == 1) {
54  _logger->warning
55  << " Warning: To store IRC results in a new location two output systems have to be specified.\n";
56  }
57 
58  bool silentCalculator = taskSettings.extract("silent_stdout_calculator", true);
59  std::shared_ptr<Core::Calculator> calc;
60  if (!testRunOnly) { // leave out in case of task chaining --> attention calc is NULL
61  // Note: _input is guaranteed not to be empty by Task constructor
62  calc = copyCalculator(systems, _input.front(), name());
63  Utils::CalculationRoutines::setLog(*calc, true, true, !silentCalculator);
64 
65  // Check system size
66  if (calc->getStructure()->size() == 1) {
67  throw std::runtime_error("Cannot perform IRC task for monoatomic systems.");
68  }
69  }
70 
71  // Generate optimizer
72  std::string optimizertype = taskSettings.extract("optimizer", std::string{"SD"});
73  int mode = taskSettings.extract("irc_mode", 0);
74  // Read and delete special settings
75  bool stopOnError = stopOnErrorExtraction(taskSettings);
76  std::transform(optimizertype.begin(), optimizertype.end(), optimizertype.begin(), ::toupper);
77  std::shared_ptr<Utils::IrcOptimizerBase> optimizer;
78  if (optimizertype == "LBFGS") {
79  auto tmp = std::make_shared<Utils::IrcOptimizer<Utils::Lbfgs>>(*calc);
80  // Default convergence options
81  optimizer = std::move(tmp);
82  }
83  else if (optimizertype == "BFGS") {
84  auto tmp = std::make_shared<Utils::IrcOptimizer<Utils::Bfgs>>(*calc);
85  // Default convergence options
86  optimizer = std::move(tmp);
87  }
88  else if (optimizertype == "SD" || optimizertype == "STEEPESTDESCENT") {
89  auto tmp = std::make_shared<Utils::IrcOptimizer<Utils::SteepestDescent>>(*calc);
90  // Default convergence options
91  optimizer = std::move(tmp);
92  }
93  else {
94  throw std::runtime_error(
95  "Unknown Optimizer requested for an IRC optimization, available are: SD, BFGS and LBFGS!");
96  }
97  // Apply user settings
98  auto settings = optimizer->getSettings();
99  settings.merge(taskSettings);
100  if (!settings.valid()) {
101  settings.throwIncorrectSettings();
102  }
103  optimizer->setSettings(settings);
104  if (!testRunOnly && !Utils::GeometryOptimization::settingsMakeSense(*optimizer)) {
105  throw std::logic_error("The given calculator settings are too inaccurate for the given convergence criteria of "
106  "this optimization Task");
107  }
108 
109  // If no errors encountered until here, the basic settings should be alright
110  if (testRunOnly) {
111  return true;
112  }
113 
114  // Get/find mode
115  // handle Hessian
116  auto propertyToGet = Utils::Property::Hessian;
117  if (calc->possibleProperties().containsSubSet(Utils::Property::Hessian)) {
118  propertyToGet = Utils::Property::Hessian;
119  }
120  else if (calc->possibleProperties().containsSubSet(Utils::Property::PartialHessian)) {
121  propertyToGet = Utils::Property::PartialHessian;
122  }
123  else {
124  throw std::runtime_error("The calculator does not provide a Hessian or a partial Hessian!");
125  }
126  calc->setRequiredProperties(propertyToGet);
127  calc->calculate("Hessian Calculation");
128  // construct normal modes
129  Utils::NormalModesContainer modes;
130  auto system = calc->getStructure();
131  if (propertyToGet == Utils::Property::Hessian) {
132  auto hessian = calc->results().get<Utils::Property::Hessian>();
133  modes = Utils::NormalModeAnalysis::calculateNormalModes(hessian, system->getElements(), system->getPositions());
134  }
135  else if (propertyToGet == Utils::Property::PartialHessian) {
136  auto hessian = calc->results().get<Utils::Property::PartialHessian>();
137  if (mode >= hessian.getNumberOfAtoms()) {
138  throw std::runtime_error("The chosen normal mode number is larger than the number of atoms the partial "
139  "Hessian was constructed with!");
140  }
141  modes = Utils::NormalModeAnalysis::calculateNormalModes(hessian, system->getElements(), system->getPositions());
142  }
143  else {
144  throw std::runtime_error("Have not implemented normal mode analysis for the property " +
145  std::string(Utils::propertyTypeName(propertyToGet)));
146  }
147  if (mode < 0 || mode > modes.size() - 1) {
148  throw std::runtime_error(
149  "The chosen normal mode number is smaller than 0 or larger than the number of modes present!");
150  }
151  auto ircMode = modes.getMode(mode);
152  auto ircModeVector = Eigen::Map<const Eigen::VectorXd>(ircMode.data(), ircMode.cols() * ircMode.rows());
153 
154  /*========================*
155  * Forward optimization
156  *========================*/
157  auto cout = _logger->output;
158  cout << "\n IRC Optimization: Forward\n\n";
159  // Add observer
160  double oldEnergy = 0.0;
161  Eigen::VectorXd oldParams;
162  // Trajectory stream
163  using Writer = Utils::XyzStreamHandler;
164 
165  const std::string& partialOutput = ((_output.size() > 1) ? _output[0] : _input[0]);
166  boost::filesystem::path dirF(partialOutput);
167  boost::filesystem::create_directory(dirF);
168  boost::filesystem::path trjfileF(partialOutput + ".irc.forward.trj.xyz");
169  std::ofstream trajectoryF((dirF / trjfileF).string(), std::ofstream::out);
170  auto forward = [&](const int& cycle, const double& energy, const Eigen::VectorXd& params) {
171  if (oldParams.size() != params.size()) {
172  oldParams = params;
173  }
174  if (cycle == 1) {
175  cout.printf("%7s %16s %16s %16s %16s\n", "Cycle", "Energy", "Energy Diff.", "Step RMS", "Max. Step");
176  }
177  auto diff = (params - oldParams).eval();
178  cout.printf("%7d %+16.9f %+16.9f %+16.9f %+16.9f\n", cycle, energy, energy - oldEnergy,
179  sqrt(diff.squaredNorm() / diff.size()), diff.cwiseAbs().maxCoeff());
180  oldEnergy = energy;
181  oldParams = params;
182  auto structure = calc->getStructure();
183  Writer::write(trajectoryF, *structure, std::to_string(energy));
184  };
185  optimizer->addObserver(forward);
186 
187  // Add custom observers forward
188  auto customObserversForward = [&calc, &observers](const int& cycle, const double& /*energy*/,
189  const Eigen::VectorXd& /*params*/) {
190  for (auto& observer : observers) {
191  auto atoms = calc->getStructure();
192  Utils::Results& results = calc->results();
193  observer(cycle, *atoms, results, "irc_forward");
194  }
195  };
196  optimizer->addObserver(customObserversForward);
197 
198  // Run optimization
199  auto structure = systems.at(_input[0])->getStructure();
200  int cycles = 0;
201  bool forwardAborted = false;
202  try {
203  cycles = optimizer->optimize(*structure, *_logger, ircModeVector, true);
204  }
205  catch (...) {
206  Writer::write(trajectoryF, *calc->getStructure());
207  _logger->error << "Forward IRC failed with error!" << Core::Log::endl;
208  if (stopOnError) {
209  trajectoryF.close();
210  throw;
211  }
212  _logger->error << boost::current_exception_diagnostic_information() << Core::Log::endl;
213  forwardAborted = true;
214  }
215  trajectoryF.close();
216 
217  int maxiter = settings.getInt("convergence_max_iterations");
218  const bool forwardConverged = cycles < maxiter && !forwardAborted;
219  if (forwardConverged) {
220  cout << Core::Log::endl
221  << " Converged after " << cycles << " iterations." << Core::Log::endl
222  << Core::Log::endl;
223  }
224  else if (!forwardAborted) {
225  cout << Core::Log::endl
226  << " Stopped after " << maxiter << " iterations." << Core::Log::endl
227  << Core::Log::endl;
228  if (stopOnError) {
229  throw std::runtime_error("Problem: IRC optimization did not converge.");
230  }
231  }
232 
233  // only write separate file with single last frame if no error occurred
234  if (!forwardAborted) {
235  // Print/Store results
236  if (_output.size() > 1) {
237  systems[_output[0]] = calc->clone();
238  boost::filesystem::path xyzfile(_output[0] + ".xyz");
239  std::ofstream xyz((dirF / xyzfile).string(), std::ofstream::out);
240  Writer::write(xyz, *(calc->getStructure()));
241  xyz.close();
242  }
243  else {
244  boost::filesystem::path xyzfile(_input[0] + ".irc.forward.xyz");
245  std::ofstream xyz((dirF / xyzfile).string(), std::ofstream::out);
246  Writer::write(xyz, *(calc->getStructure()));
247  xyz.close();
248  }
249  }
250 
251  /*=========================*
252  * Backward optimization
253  *=========================*/
254  cout << "\n IRC Optimization: Backward\n\n";
255  // Add observer
256  oldEnergy = 0.0;
257  oldParams.resize(0);
258  // Reset optimizer
259  optimizer->setSettings(settings);
260  optimizer->reset();
261  // Trajectory stream
262  boost::filesystem::path dirB(((_output.size() > 1) ? _output[1] : _input[0]));
263  boost::filesystem::create_directory(dirB);
264  boost::filesystem::path trjfileB(((_output.size() > 1) ? _output[1] : _input[0]) + ".irc.backward.trj.xyz");
265  std::ofstream trajectoryB((dirB / trjfileB).string(), std::ofstream::out);
266  auto backward = [&](const int& cycle, const double& energy, const Eigen::VectorXd& params) {
267  if (oldParams.size() != params.size()) {
268  oldParams = params;
269  }
270  if (cycle == 1) {
271  cout.printf("%7s %16s %16s %16s %16s\n", "Cycle", "Energy", "Energy Diff.", "Step RMS", "Max. Step");
272  }
273  auto diff = (params - oldParams).eval();
274  cout.printf("%7d %+16.9f %+16.9f %+16.9f %+16.9f\n", cycle, energy, energy - oldEnergy,
275  sqrt(diff.squaredNorm() / diff.size()), diff.cwiseAbs().maxCoeff());
276  oldEnergy = energy;
277  oldParams = params;
278  auto structure = calc->getStructure();
279  Writer::write(trajectoryB, *structure, std::to_string(energy));
280  };
281  optimizer->clearObservers();
282  optimizer->addObserver(backward);
283 
284  // Add custom observers backward
285  auto customObserversBackward = [&calc, &observers](const int& cycle, const double& /*energy*/,
286  const Eigen::VectorXd& /*params*/) {
287  for (auto& observer : observers) {
288  auto atoms = calc->getStructure();
289  Utils::Results& results = calc->results();
290  observer(cycle, *atoms, results, "irc_backward");
291  }
292  };
293  optimizer->addObserver(customObserversBackward);
294 
295  // Run optimization
296  structure = systems.at(_input[0])->getStructure();
297  cycles = 0;
298  try {
299  cycles = optimizer->optimize(*structure, *_logger, ircModeVector, false);
300  }
301  catch (...) {
302  Writer::write(trajectoryB, *calc->getStructure());
303  trajectoryB.close();
304  _logger->error << "Calculation in IRC backward optimization failed!" << Core::Log::endl;
305  if (stopOnError) {
306  throw;
307  }
308  _logger->error << boost::current_exception_diagnostic_information() << Core::Log::endl;
309  return false;
310  }
311  trajectoryB.close();
312 
313  maxiter = settings.getInt("convergence_max_iterations");
314  if (cycles < maxiter) {
315  cout << Core::Log::endl
316  << " Converged after " << cycles << " iterations." << Core::Log::endl
317  << Core::Log::endl;
318  }
319  else {
320  cout << Core::Log::endl
321  << " Stopped after " << maxiter << " iterations." << Core::Log::endl
322  << Core::Log::endl;
323  if (stopOnError) {
324  throw std::runtime_error("Problem: IRC optimization did not converge.");
325  }
326  }
327 
328  // Print/Store results
329  if (_output.size() > 1) {
330  systems[_output[1]] = calc->clone();
331  boost::filesystem::path xyzfile(_output[1] + ".xyz");
332  std::ofstream xyz((dirB / xyzfile).string(), std::ofstream::out);
333  Writer::write(xyz, *(calc->getStructure()));
334  xyz.close();
335  }
336  else {
337  boost::filesystem::path xyzfile(_input[0] + ".irc.backward.xyz");
338  std::ofstream xyz((dirB / xyzfile).string(), std::ofstream::out);
339  Writer::write(xyz, *(calc->getStructure()));
340  xyz.close();
341  }
342 
343  return cycles < maxiter && forwardConverged;
344  }
345 };
346 
347 } // namespace Readuct
348 } // namespace Scine
349 
350 #endif // READUCT_IRCTASK_H_
bool run(SystemsMap &systems, Utils::UniversalSettings::ValueCollection taskSettings, bool testRunOnly=false, std::vector< std::function< void(const int &, const Utils::AtomCollection &, const Utils::Results &, const std::string &)>> observers={}) const final
Executes the actual task represented by this class.
Definition: IrcTask.h:48
static std::ostream & endl(std::ostream &os)
const std::vector< std::string > & input() const
Getter for the expected names of the input systems.
Definition: Task.h:85
IrcTask(std::vector< std::string > input, std::vector< std::string > output, std::shared_ptr< Core::Log > logger=nullptr)
Construct a new IrcTask.
Definition: IrcTask.h:40
void warningIfMultipleInputsGiven() const
Warn if more than one input system was specified.
Definition: Task.h:98
std::string name() const override
Getter for the tasks name.
Definition: IrcTask.h:44
const std::vector< std::string > & output() const
Getter for the names of the output systems generated by this task.
Definition: Task.h:92
The base class for all tasks in Readuct.
Definition: Task.h:29
A task to run an IRC optimization using a given normal mode.
Definition: IrcTask.h:32