Scine::Readuct  6.0.0
This is the SCINE module ReaDuct.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Pages
GeometryOptimizationTask.h
Go to the documentation of this file.
1 
7 #ifndef READUCT_GEOMETRYOPTIMIZATIONTASK_H_
8 #define READUCT_GEOMETRYOPTIMIZATIONTASK_H_
9 
10 /* Readuct */
11 #include "Tasks/Task.h"
12 /* Scine */
24 /* External includes */
25 #include <boost/exception/diagnostic_information.hpp>
26 #include <boost/filesystem.hpp>
27 #include <cstdio>
28 #include <fstream>
29 
30 namespace Scine {
31 namespace Readuct {
32 
34  public:
41  GeometryOptimizationTask(std::vector<std::string> input, std::vector<std::string> output,
42  std::shared_ptr<Core::Log> logger = nullptr)
43  : Task(std::move(input), std::move(output), std::move(logger)) {
44  }
45 
46  std::string name() const override {
47  return "Geometry Optimization";
48  }
49 
50  bool run(SystemsMap& systems, Utils::UniversalSettings::ValueCollection taskSettings, bool testRunOnly = false,
51  std::vector<std::function<void(const int&, const Utils::AtomCollection&, const Utils::Results&, const std::string&)>>
52  observers = {}) const final {
55  bool silentCalculator = taskSettings.extract("silent_stdout_calculator", true);
56  bool isQmmm = false;
57  // Get/Copy Calculator
58  std::shared_ptr<Core::Calculator> calc;
59  if (!testRunOnly) { // leave out in case of task chaining --> attention calc is NULL
60  // Note: _input is guaranteed not to be empty by Task constructor
61  calc = copyCalculator(systems, _input.front(), name());
62  Utils::CalculationRoutines::setLog(*calc, true, true, !silentCalculator);
63  if (calc->name() == "QMMM") {
64  isQmmm = true;
65  }
66  }
67 
68  // Generate optimizer
69  auto optimizertype = taskSettings.extract("optimizer", std::string{"BFGS"});
70  auto unitcelloptimizertype = taskSettings.extract("unitcelloptimizer", std::string{""});
71  auto optimizer = constructOptimizer(*calc, optimizertype, unitcelloptimizertype, isQmmm);
72 
73  // Have to exclude settings check from test run due to newly introduced optimizer types
74  // that cannot be constructed in test runs, because they require the knowledge of the
75  // calculator type which we do not have in a test run
76  if (testRunOnly) {
77  return true;
78  }
79 
80  // Read and delete special settings
81  bool stopOnError = stopOnErrorExtraction(taskSettings);
82  // Apply settings
83  auto settings = optimizer->getSettings();
84  settings.merge(taskSettings);
85  if (!testRunOnly && systems.at(_input[0])->getStructure()->size() == 1 &&
86  Utils::CoordinateSystemInterpreter::getCoordinateSystemFromString(
87  settings.getString("geoopt_coordinate_system")) != Utils::CoordinateSystem::Cartesian) {
88  /* For monoatomic systems avoid transformation to internals resulting into
89  * empty optimization parameters. Necessary because observer and
90  * convergence check use transformed parameters
91  */
92  _logger->warning << " Cannot treat a monoatomic system in internal coordinates. Switching to Cartesians."
94  << Core::Log::endl;
95  settings.modifyString("geoopt_coordinate_system", Utils::CoordinateSystemInterpreter::getStringFromCoordinateSystem(
96  Utils::CoordinateSystem::Cartesian));
97  }
98  if (!settings.valid()) {
99  settings.throwIncorrectSettings();
100  }
101  optimizer->setSettings(settings);
102  if (!testRunOnly && !Utils::GeometryOptimization::settingsMakeSense(*optimizer)) {
103  throw std::logic_error("The given calculator settings are too inaccurate for the given convergence criteria of "
104  "this optimization Task");
105  }
106 
107  // Add observer
108  // Trajectory stream
109  const std::string& outputSystem = (!_output.empty() ? _output.front() : _input.front());
110  using Writer = Utils::XyzStreamHandler;
111  boost::filesystem::path dir(outputSystem);
112  boost::filesystem::create_directory(dir);
113  boost::filesystem::path trjfile(outputSystem + ".opt.trj.xyz");
114  std::ofstream trajectory((dir / trjfile).string(), std::ofstream::out);
115  double oldEnergy = 0.0;
116  Eigen::VectorXd oldParams;
117  auto cout = _logger->output;
118  auto func = [&](const int& cycle, const double& energy, const Eigen::VectorXd& params) {
119  if (oldParams.size() != params.size()) {
120  oldParams = params;
121  }
122  if (cycle == 1) {
123  cout.printf("%7s %16s %16s %16s %16s\n", "Cycle", "Energy", "Energy Diff.", "Step RMS", "Max. Step");
124  }
125  auto diff = (params - oldParams).eval();
126  cout.printf("%7d %+16.9f %+16.9f %+16.9f %+16.9f\n", cycle, energy, energy - oldEnergy,
127  sqrt(diff.squaredNorm() / diff.size()), diff.cwiseAbs().maxCoeff());
128  oldEnergy = energy;
129  oldParams = params;
130  auto structure = calc->getStructure();
131  Writer::write(trajectory, *structure, std::to_string(energy));
132  };
133  optimizer->addObserver(func);
134 
135  // Add custom observers
136  auto customObservers = [&calc, &observers](const int& cycle, const double& /*energy*/, const Eigen::VectorXd& /*params*/) {
137  for (auto& observer : observers) {
138  auto atoms = calc->getStructure();
139  Utils::Results& results = calc->results();
140  observer(cycle, *atoms, results, "geometry_optimization");
141  }
142  };
143  optimizer->addObserver(customObservers);
144 
145  // Run optimization
146  auto structure = calc->getStructure();
147  int cycles = 0;
148  try {
149  cycles = optimizer->optimize(*structure, *_logger);
150  }
151  catch (...) {
152  Writer::write(trajectory, *calc->getStructure());
153  trajectory.close();
154  _logger->error << "Optimization failed with error!" << Core::Log::endl;
155  if (stopOnError) {
156  throw;
157  }
158  _logger->error << boost::current_exception_diagnostic_information() << Core::Log::endl;
159  return false;
160  }
161  trajectory.close();
162 
163  int maxiter = settings.getInt("convergence_max_iterations");
164  if (cycles < maxiter) {
165  cout << Core::Log::endl
166  << " Converged after " << cycles << " iterations." << Core::Log::endl
167  << Core::Log::endl;
168  }
169  else {
170  cout << Core::Log::endl
171  << " Stopped after " << maxiter << " iterations." << Core::Log::endl
172  << Core::Log::endl;
173  if (stopOnError) {
174  throw std::runtime_error("Problem: Structure optimization did not converge.");
175  }
176  }
177 
178  // Print/Store result
179  systems[outputSystem] = calc;
180  boost::filesystem::path xyzfile(outputSystem + ".xyz");
181  std::ofstream xyz((dir / xyzfile).string(), std::ofstream::out);
182  Writer::write(xyz, *(calc->getStructure()));
183  xyz.close();
184 
185  return cycles < maxiter;
186  }
187 
188  inline static std::shared_ptr<Utils::GeometryOptimizerBase> constructOptimizer(Core::Calculator& calc, std::string type,
189  std::string cellType, bool isQmmm) {
190  // this method does not fail in test runs for QM/MM optimizers, because in test runs the isQmmm flag is always false
191  std::transform(type.begin(), type.end(), type.begin(), ::toupper);
192  std::transform(cellType.begin(), cellType.end(), cellType.begin(), ::toupper);
193  if (type == "LBFGS") {
194  if (cellType.empty()) {
195  if (isQmmm) {
196  return std::make_shared<Utils::QmmmGeometryOptimizer<Utils::Lbfgs>>(calc);
197  }
198  return std::make_shared<Utils::GeometryOptimizer<Utils::Lbfgs>>(calc);
199  }
200  else {
201  if (cellType == "LBFGS") {
202  return std::make_shared<Utils::UnitCellGeometryOptimizer<Utils::Lbfgs, Utils::Lbfgs>>(calc);
203  }
204  else if (cellType == "BFGS") {
205  return std::make_shared<Utils::UnitCellGeometryOptimizer<Utils::Lbfgs, Utils::Bfgs>>(calc);
206  }
207  else if (cellType == "SD" || cellType == "STEEPESTDESCENT") {
208  return std::make_shared<Utils::UnitCellGeometryOptimizer<Utils::Lbfgs, Utils::SteepestDescent>>(calc);
209  }
210  throw std::runtime_error(
211  "Unknown CellOptimizer requested for a geometry optimization, available are: SD, BFGS and LBFGS!");
212  }
213  }
214  else if (type == "BFGS") {
215  if (cellType.empty()) {
216  if (isQmmm) {
217  return std::make_shared<Utils::QmmmGeometryOptimizer<Utils::Bfgs>>(calc);
218  }
219  return std::make_shared<Utils::GeometryOptimizer<Utils::Bfgs>>(calc);
220  }
221 
222  else {
223  if (cellType == "LBFGS") {
224  return std::make_shared<Utils::UnitCellGeometryOptimizer<Utils::Bfgs, Utils::Lbfgs>>(calc);
225  }
226  else if (cellType == "BFGS") {
227  return std::make_shared<Utils::UnitCellGeometryOptimizer<Utils::Bfgs, Utils::Bfgs>>(calc);
228  }
229  else if (cellType == "SD" || cellType == "STEEPESTDESCENT") {
230  return std::make_shared<Utils::UnitCellGeometryOptimizer<Utils::Bfgs, Utils::SteepestDescent>>(calc);
231  }
232  throw std::runtime_error(
233  "Unknown CellOptimizer requested for a geometry optimization, available are: SD, BFGS and LBFGS!");
234  }
235  }
236  else if (type == "SD" || type == "STEEPESTDESCENT") {
237  if (cellType.empty()) {
238  if (isQmmm) {
239  return std::make_shared<Utils::QmmmGeometryOptimizer<Utils::SteepestDescent>>(calc);
240  }
241  return std::make_shared<Utils::GeometryOptimizer<Utils::SteepestDescent>>(calc);
242  }
243  else {
244  if (cellType == "LBFGS") {
245  return std::make_shared<Utils::UnitCellGeometryOptimizer<Utils::SteepestDescent, Utils::Lbfgs>>(calc);
246  }
247  else if (cellType == "BFGS") {
248  return std::make_shared<Utils::UnitCellGeometryOptimizer<Utils::SteepestDescent, Utils::Bfgs>>(calc);
249  }
250  else if (cellType == "SD" || cellType == "STEEPESTDESCENT") {
251  return std::make_shared<Utils::UnitCellGeometryOptimizer<Utils::SteepestDescent, Utils::SteepestDescent>>(calc);
252  }
253  throw std::runtime_error(
254  "Unknown CellOptimizer requested for a geometry optimization, available are: SD, BFGS and LBFGS!");
255  }
256  }
257  else if (type == "NR" || type == "NEWTONRAPHSON") {
258  if (cellType.empty()) {
259  if (isQmmm) {
260  return std::make_shared<Utils::QmmmGeometryOptimizer<Utils::NewtonRaphson>>(calc);
261  }
262  return std::make_shared<Utils::GeometryOptimizer<Utils::NewtonRaphson>>(calc);
263  }
264  else {
265  if (cellType == "LBFGS") {
266  return std::make_shared<Utils::UnitCellGeometryOptimizer<Utils::NewtonRaphson, Utils::Lbfgs>>(calc);
267  }
268  else if (cellType == "BFGS") {
269  return std::make_shared<Utils::UnitCellGeometryOptimizer<Utils::NewtonRaphson, Utils::Bfgs>>(calc);
270  }
271  else if (cellType == "SD" || cellType == "STEEPESTDESCENT") {
272  return std::make_shared<Utils::UnitCellGeometryOptimizer<Utils::NewtonRaphson, Utils::SteepestDescent>>(calc);
273  }
274  throw std::runtime_error(
275  "Unknown CellOptimizer requested for a geometry optimization, available are: SD, BFGS and LBFGS!");
276  }
277  }
278  throw std::runtime_error(
279  "Unknown Optimizer requested for a geometry optimization, available are: SD, NR, BFGS and LBFGS!");
280  };
281 };
282 
283 } // namespace Readuct
284 } // namespace Scine
285 
286 #endif // READUCT_GEOMETRYOPTIMIZATIONTASK_H_
void warningIfMultipleOutputsGiven() const
Warn if more than one output system was specified.
Definition: Task.h:107
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
std::string name() const override
Getter for the tasks name.
Definition: GeometryOptimizationTask.h:46
void warningIfMultipleInputsGiven() const
Warn if more than one input system was specified.
Definition: Task.h:98
const std::vector< std::string > & output() const
Getter for the names of the output systems generated by this task.
Definition: Task.h:92
GeometryOptimizationTask(std::vector< std::string > input, std::vector< std::string > output, std::shared_ptr< Core::Log > logger=nullptr)
Construct a new GeometryOptimizationTask.
Definition: GeometryOptimizationTask.h:41
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: GeometryOptimizationTask.h:50
The base class for all tasks in Readuct.
Definition: Task.h:29
Definition: GeometryOptimizationTask.h:33