7 #ifndef READUCT_TSOPTIMIZATIONTASK_H_
8 #define READUCT_TSOPTIMIZATIONTASK_H_
24 #include <boost/exception/diagnostic_information.hpp>
25 #include <boost/filesystem.hpp>
44 :
Task(std::move(input), std::move(output), std::move(logger)) {
47 std::string
name()
const override {
48 return "TS Optimization";
53 observers = {})
const final {
57 bool silentCalculator = taskSettings.extract(
"silent_stdout_calculator",
true);
58 std::shared_ptr<Core::Calculator> calc;
61 calc = copyCalculator(systems, _input.front(),
name());
62 Utils::CalculationRoutines::setLog(*calc,
true,
true, !silentCalculator);
65 if (calc->getStructure()->size() == 1) {
66 throw std::runtime_error(
"Cannot calculate transition state for monoatomic systems.");
71 std::string optimizertype = taskSettings.extract(
"optimizer", std::string{
"bofill"});
72 std::transform(optimizertype.begin(), optimizertype.end(), optimizertype.begin(), ::tolower);
76 if (taskSettings.valueExists(
"automatic_mode_selection") &&
77 (taskSettings.valueExists(optimizertype +
"_follow_mode") || taskSettings.valueExists(
"ev_follow_mode"))) {
78 throw std::logic_error(
"You specified an automatic selection of the mode and gave a specific mode yourself. "
79 "This is not possible. Only give one of those options.");
81 std::vector<int> relevantAtoms = taskSettings.extract(
"automatic_mode_selection", std::vector<int>{});
83 if (!relevantAtoms.empty()) {
84 for (
const auto& index : relevantAtoms) {
86 throw std::logic_error(
"You gave an atom index smaller than 0 in automatic_mode_selection. "
87 "This does not make sense.");
92 using XyzHandler = Utils::XyzStreamHandler;
93 auto cout = _logger->output;
95 std::shared_ptr<Utils::GeometryOptimizerBase> optimizer;
96 if (optimizertype ==
"bofill") {
97 auto tmp = std::make_shared<Utils::GeometryOptimizer<Utils::Bofill>>(*calc);
98 optimizer = std::move(tmp);
100 else if (optimizertype ==
"ef" || optimizertype ==
"ev" || optimizertype ==
"evf" ||
101 optimizertype ==
"eigenvectorfollowing" || optimizertype ==
"eigenvector_following") {
102 auto tmp = std::make_shared<Utils::GeometryOptimizer<Utils::EigenvectorFollowing>>(*calc);
103 optimizer = std::move(tmp);
105 else if (optimizertype ==
"dimer") {
106 auto tmp = std::make_shared<Utils::GeometryOptimizer<Utils::Dimer>>(*calc);
109 auto coordinateSystem = tmp->coordinateSystem;
110 if (taskSettings.valueExists(
"geoopt_coordinate_system")) {
111 coordinateSystem = Utils::CoordinateSystemInterpreter::getCoordinateSystemFromString(
112 taskSettings.getString(
"geoopt_coordinate_system"));
116 bool useEigenvectorForFirstStep = taskSettings.extract(
"dimer_calculate_hessian_once",
false);
117 if (!useEigenvectorForFirstStep && !relevantAtoms.empty()) {
118 _logger->warning <<
"Specified Dimer optimizer with automatic mode selection, this means that a Hessian has to "
122 if (taskSettings.valueExists(
"dimer_follow_mode") || !relevantAtoms.empty()) {
123 useEigenvectorForFirstStep =
true;
125 int followedMode = taskSettings.extract(
"dimer_follow_mode", 0);
126 std::string guessVectorFileName = taskSettings.extract(
"dimer_guess_vector_file", std::string(
""));
127 std::vector<std::string> discreteGuessesFileNames =
128 taskSettings.extract(
"dimer_discrete_guesses", std::vector<std::string>{
"",
""});
131 if (discreteGuessesFileNames.size() != 2) {
132 throw std::runtime_error(
"Problem with discrete guesses. You may only give list with two filepaths.");
135 if (!testRunOnly && !useEigenvectorForFirstStep && calc->results().has<Utils::Property::Hessian>()) {
136 _logger->warning <<
"Did not specify to use eigenvector for initialization of Dimer, although your system "
137 "already includes a Hessian. The Hessian will therefore be ignored. "
138 "If you want to change that, activate the setting 'dimer_calculate_hessian_once': true"
141 if (useEigenvectorForFirstStep) {
142 if (!guessVectorFileName.empty()) {
143 throw std::logic_error(
"You specified to use the eigenvector of the Hessian as Dimer initialization, "
144 "but also specified a file to read a guess vector for the initialization. "
147 if (!discreteGuessesFileNames[0].empty()) {
148 throw std::logic_error(
"You specified to use the eigenvector of the Hessian as Dimer initialization, "
149 "but also specified files to read discrete guesses for the initialization. "
153 else if (!guessVectorFileName.empty() && !discreteGuessesFileNames[0].empty()) {
154 throw std::logic_error(
"You specified a file to read a guess vector for the Dimer initialization. "
155 "but also specified files to read discrete guesses for the initialization. "
163 else if (useEigenvectorForFirstStep) {
164 tmp->optimizer.skipFirstRotation =
true;
165 auto modes = getNormalModes(*calc);
166 auto system = calc->getStructure();
167 if (!relevantAtoms.empty()) {
168 followedMode = selectMode(*calc, relevantAtoms);
170 auto mode = modes.getMode(followedMode);
172 if (coordinateSystem == Utils::CoordinateSystem::Internal) {
174 Utils::InternalCoordinates internalCoordinates(*system);
175 tmp->optimizer.guessVector = std::make_shared<Eigen::VectorXd>(internalCoordinates.coordinatesToInternal(mode));
177 else if (coordinateSystem == Utils::CoordinateSystem::CartesianWithoutRotTrans) {
180 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es;
181 auto hessian = calc->results().get<Utils::Property::Hessian>();
183 int nEigenValues = es.eigenvalues().size();
184 Eigen::MatrixXd eigenValueMatrix = Eigen::MatrixXd::Identity(nEigenValues, nEigenValues);
185 eigenValueMatrix.diagonal() = es.eigenvalues();
186 eigenValueMatrix(followedMode, followedMode) = -es.eigenvalues()[followedMode];
187 Eigen::MatrixXd switchedHessian = es.eigenvectors() * eigenValueMatrix * es.eigenvectors().transpose();
189 Utils::InternalCoordinates internalCoordinates(*system,
true);
190 tmp->optimizer.guessVector = std::make_shared<Eigen::VectorXd>(internalCoordinates.coordinatesToInternal(mode));
192 Eigen::MatrixXd switchedTransformedHessian = internalCoordinates.hessianToInternal(switchedHessian);
193 tmp->optimizer.invH = switchedTransformedHessian.inverse();
195 else if (coordinateSystem == Utils::CoordinateSystem::Cartesian) {
197 tmp->optimizer.guessVector =
198 std::make_shared<Eigen::VectorXd>(Eigen::Map<const Eigen::VectorXd>(mode.data(), mode.cols() * mode.rows()));
201 throw std::logic_error(
"Unknown coordinate system " +
202 Utils::CoordinateSystemInterpreter::getStringFromCoordinateSystem(coordinateSystem));
206 else if (!guessVectorFileName.empty()) {
208 std::vector<double> container;
210 std::ifstream file(guessVectorFileName, std::ios::in);
211 if (!file.is_open()) {
212 throw std::runtime_error(
"Problem when opening file " + guessVectorFileName);
214 while (file >> num) {
215 container.push_back(num);
218 Eigen::Map<Eigen::VectorXd> guessVector(container.data(), container.size());
219 auto system = calc->getStructure();
221 if (coordinateSystem != Utils::CoordinateSystem::Cartesian &&
222 static_cast<unsigned long>(guessVector.size()) == 3 * system->getElements().size()) {
223 std::shared_ptr<Utils::InternalCoordinates> transformation =
nullptr;
224 if (coordinateSystem == Utils::CoordinateSystem::Internal) {
225 transformation = std::make_shared<Utils::InternalCoordinates>(*system);
227 else if (coordinateSystem == Utils::CoordinateSystem::CartesianWithoutRotTrans) {
228 transformation = std::make_shared<Utils::InternalCoordinates>(*system,
true);
231 Utils::PositionCollection guessPosition =
232 Eigen::Map<Utils::PositionCollection>(guessVector.data(), guessVector.size() / 3, 3);
234 tmp->optimizer.guessVector =
235 std::make_shared<Eigen::VectorXd>(transformation->coordinatesToInternal(guessPosition));
237 catch (
const Utils::InternalCoordinatesException& e) {
238 _logger->warning <<
"Could not transform given Cartesian coordinates to Internal coordinates.\n"
239 <<
"Omitting guess and initializing dimer with random vector. " <<
Core::Log::endl;
243 else if (coordinateSystem == Utils::CoordinateSystem::Cartesian &&
244 static_cast<unsigned long>(guessVector.size()) != 3 * system->getElements().size()) {
246 std::shared_ptr<Utils::InternalCoordinates> transformation =
nullptr;
247 if (static_cast<unsigned long>(guessVector.size()) == 3 * system->getElements().size() - 6 ||
248 static_cast<unsigned long>(guessVector.size()) == 3 * system->getElements().size() - 5) {
250 transformation = std::make_shared<Utils::InternalCoordinates>(*system,
true);
253 transformation = std::make_shared<Utils::InternalCoordinates>(*system);
257 auto cartPos0 = system->getPositions();
258 auto ircVec0 = transformation->coordinatesToInternal(cartPos0);
260 Eigen::VectorXd ircVec1 = ircVec0 + guessVector;
262 auto cartPos1 = transformation->coordinatesToCartesian(ircVec1);
263 Utils::PositionCollection posDiff = cartPos1 - cartPos0;
264 tmp->optimizer.guessVector =
265 std::make_shared<Eigen::VectorXd>(Eigen::Map<Eigen::VectorXd>(posDiff.data(), posDiff.size()));
267 catch (
const Utils::InternalCoordinatesException& e) {
268 _logger->warning <<
"Could not transform given non-Cartesian coordinates to Cartesian.\n"
269 <<
"Omitting guess and initializing dimer with random vector. " <<
Core::Log::endl;
274 tmp->optimizer.guessVector = std::make_shared<Eigen::VectorXd>(guessVector);
278 else if (!discreteGuessesFileNames.at(0).empty() && !discreteGuessesFileNames.at(1).empty()) {
279 Utils::AtomCollection system;
281 std::string filepath1 = discreteGuessesFileNames.at(0);
282 std::ifstream file1(filepath1, std::ios::in);
283 if (!file1.is_open()) {
284 throw std::runtime_error(
"Problem when opening file " + filepath1);
286 system = XyzHandler::read(file1);
287 auto position1 = system.getPositions();
291 std::string filepath2 = discreteGuessesFileNames.at(1);
292 std::ifstream file2(filepath2, std::ios::in);
293 if (!file2.is_open()) {
294 throw std::runtime_error(
"Problem when opening file " + filepath2);
296 system = XyzHandler::read(file2);
297 auto position2 = system.getPositions();
300 Utils::PositionCollection positionDiff = position2 - position1;
301 Eigen::Map<Eigen::VectorXd> mode(positionDiff.data(), positionDiff.size());
302 std::shared_ptr<Utils::InternalCoordinates> transformation =
nullptr;
303 if (coordinateSystem == Utils::CoordinateSystem::Internal) {
304 transformation = std::make_shared<Utils::InternalCoordinates>(system);
306 else if (coordinateSystem == Utils::CoordinateSystem::CartesianWithoutRotTrans) {
307 transformation = std::make_shared<Utils::InternalCoordinates>(system,
true);
309 if (transformation) {
310 tmp->optimizer.guessVector = std::make_shared<Eigen::VectorXd>(transformation->coordinatesToInternal(positionDiff));
313 tmp->optimizer.guessVector = std::make_shared<Eigen::VectorXd>(mode);
316 optimizer = std::move(tmp);
319 throw std::runtime_error(
320 "Unknown Optimizer requested for TS optimization, available are: Bofill, EVF and Dimer!");
324 bool stopOnError = stopOnErrorExtraction(taskSettings);
326 auto settings = optimizer->getSettings();
327 settings.merge(taskSettings);
331 if (!relevantAtoms.empty() && !testRunOnly) {
333 if (optimizertype !=
"dimer" && optimizertype !=
"bofill") {
334 settings.modifyValue(
"ev_follow_mode", selectMode(*calc, relevantAtoms));
336 else if (optimizertype ==
"bofill") {
337 settings.modifyValue(
"bofill_follow_mode", selectMode(*calc, relevantAtoms));
340 if (!settings.valid()) {
341 settings.throwIncorrectSettings();
343 optimizer->setSettings(settings);
344 if (!testRunOnly && !Utils::GeometryOptimization::settingsMakeSense(*optimizer)) {
345 throw std::logic_error(
"The given calculator settings are too inaccurate for the given convergence criteria of "
346 "this optimization Task");
356 Utils::XyzStreamHandler writer;
357 const std::string& partialOutput = (!_output.empty() ? _output[0] : _input[0]);
358 boost::filesystem::path dir(partialOutput);
359 boost::filesystem::create_directory(dir);
360 boost::filesystem::path trjfile(partialOutput +
".tsopt.trj.xyz");
361 std::ofstream trajectory((dir / trjfile).
string(), std::ofstream::out);
362 cout.printf(
"%7s %16s %16s %16s %16s\n",
"Cycle",
"Energy",
"Energy Diff.",
"Step RMS",
"Max. Step");
363 double oldEnergy = 0.0;
364 Eigen::VectorXd oldParams;
365 auto func = [&](
const int& cycle,
const double& energy,
const Eigen::VectorXd& params) {
366 if (oldParams.size() != params.size()) {
369 auto diff = (params - oldParams).eval();
370 cout.printf(
"%7d %+16.9f %+16.9f %+16.9f %+16.9f\n", cycle, energy, energy - oldEnergy,
371 sqrt(diff.squaredNorm() / diff.size()), diff.cwiseAbs().maxCoeff());
374 auto structure = calc->getStructure();
375 XyzHandler::write(trajectory, *structure, std::to_string(energy));
377 optimizer->addObserver(func);
380 auto customObservers = [&calc, &observers](
const int& cycle,
const double& ,
const Eigen::VectorXd& ) {
381 for (
auto& observer : observers) {
382 auto atoms = calc->getStructure();
383 Utils::Results& results = calc->results();
384 observer(cycle, *atoms, results,
"ts_optimization");
387 optimizer->addObserver(customObservers);
390 auto structure = calc->getStructure();
393 cycles = optimizer->optimize(*structure, *_logger);
396 XyzHandler::write(trajectory, *calc->getStructure());
398 _logger->error <<
"TS Optimization failed with error!" <<
Core::Log::endl;
402 _logger->error << boost::current_exception_diagnostic_information() <<
Core::Log::endl;
407 int maxiter = settings.getInt(
"convergence_max_iterations");
408 if (cycles < maxiter) {
418 throw std::runtime_error(
"Problem: TS optimization did not converge.");
423 systems[partialOutput] = calc;
424 boost::filesystem::path xyzfile(partialOutput +
".xyz");
425 std::ofstream xyz((dir / xyzfile).
string(), std::ofstream::out);
426 XyzHandler::write(xyz, *(calc->getStructure()));
429 return cycles < maxiter;
433 int selectMode(Core::Calculator& calc,
const std::vector<int>& relevantAtoms)
const {
434 _logger->output <<
" Automatically selecting normal mode " <<
Core::Log::endl;
435 int nAtoms = calc.getStructure()->size();
436 for (
const auto& index : relevantAtoms) {
437 if (index >= nAtoms) {
438 throw std::logic_error(
"You gave an atom index larger than the number of atoms in automatic_mode_selection. "
439 "This does not make sense.");
442 Utils::NormalModesContainer modes = getNormalModes(calc);
443 auto waveNumbers = modes.getWaveNumbers();
444 std::vector<double> contributions;
446 for (
int i = 0; i < static_cast<int>(waveNumbers.size()); ++i) {
447 if (waveNumbers[i] >= 0.0) {
449 throw std::runtime_error(
"Structure has no imaginary normal mode.");
453 auto mode = modes.getMode(i);
454 double contribution = 0.0;
456 for (
int j = 0; j < mode.size(); ++j) {
457 if (std::find(relevantAtoms.begin(), relevantAtoms.end(), j) != relevantAtoms.end()) {
458 contribution += mode.row(j).norm();
461 contributions.push_back(contribution);
463 int selection = std::distance(contributions.begin(), std::max_element(contributions.begin(), contributions.end()));
464 double tolerance = contributions[selection] * 0.1;
467 for (
unsigned int i = 0; i < contributions.size(); i++) {
468 if ((contributions[selection] - contributions[i]) < tolerance) {
473 _logger->output <<
" Automatically selected mode " << std::to_string(selection) <<
" with wave number "
474 << std::to_string(waveNumbers[selection]) <<
" cm^-1" <<
Core::Log::endl;
478 static Utils::NormalModesContainer getNormalModes(Core::Calculator& calc) {
479 if (!calc.results().has<Utils::Property::Hessian>()) {
480 calc.setRequiredProperties(Utils::Property::Hessian);
481 calc.calculate(
"Hessian Calculation");
483 auto hessian = calc.results().get<Utils::Property::Hessian>();
484 auto system = calc.getStructure();
485 return Utils::NormalModeAnalysis::calculateNormalModes(hessian, system->getElements(), system->getPositions());
492 #endif // READUCT_TSOPTIMIZATIONTASK_H_
void warningIfMultipleOutputsGiven() const
Warn if more than one output system was specified.
Definition: Task.h:104
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:82
std::string name() const override
Getter for the tasks name.
Definition: TsOptimizationTask.h:47
TsOptimizationTask(std::vector< std::string > input, std::vector< std::string > output, std::shared_ptr< Core::Log > logger=nullptr)
Construct a new TsOptimizationTask.
Definition: TsOptimizationTask.h:43
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: TsOptimizationTask.h:51
void warningIfMultipleInputsGiven() const
Warn if more than one input system was specified.
Definition: Task.h:95
const std::vector< std::string > & output() const
Getter for the names of the output systems generated by this task.
Definition: Task.h:89
The base class for all tasks in Readuct.
Definition: Task.h:28
Definition: TsOptimizationTask.h:35