7 #ifndef READUCT_BSPLINEINTERPOLATIONTASK_H_
8 #define READUCT_BSPLINEINTERPOLATIONTASK_H_
10 #include "../../Readuct/ElementaryStepOptimization/ElementaryStepOptimizer.h"
11 #include "../../Readuct/ElementaryStepOptimization/ProfileEnergies.h"
12 #include "../../Readuct/ElementaryStepOptimization/ReactionProfile.h"
25 #include <boost/filesystem.hpp>
40 std::shared_ptr<Core::Log> logger =
nullptr)
41 :
Task(std::move(input), std::move(output), std::move(logger)) {
44 std::string
name()
const override {
45 return "BSpline Interpolation";
48 bool run(std::map<std::string, std::shared_ptr<Core::Calculator>>& systems,
51 observers = {})
const final {
52 if (_input.size() != 2) {
53 throw std::logic_error(
"The B-Spline task needs two input systems.");
56 if (observers.size() > 0) {
57 throw std::logic_error(
58 "BSplineInterpolationTask does not feature algorithm accepting observers, yet one was given");
61 alignStructuresBeforeInterpolation_ = taskSettings.extract(
"align_structures", alignStructuresBeforeInterpolation_);
62 numberStructuresForMolecularTrajectory_ = taskSettings.extract(
"num_structures", numberStructuresForMolecularTrajectory_);
63 optimize_ = taskSettings.extract(
"optimize", optimize_);
64 numberControlPointsForInterpolation_ = taskSettings.extract(
"num_control_points", numberControlPointsForInterpolation_);
65 optimizer_ = taskSettings.extract(
"optimizer", optimizer_);
66 std::transform(optimizer_.begin(), optimizer_.end(), optimizer_.begin(), ::tolower);
67 extractTsGuess_ = taskSettings.extract(
"extract_ts_guess", extractTsGuess_);
68 extractTsGuessNeighbours_ = taskSettings.extract(
"extract_ts_guess_neighbours", extractTsGuessNeighbours_);
69 tangentFileName_ = taskSettings.extract(
"tangent_file", tangentFileName_);
70 coordinateThresholdForMaximumExtraction_ =
71 taskSettings.extract(
"extract_threshold", coordinateThresholdForMaximumExtraction_);
72 bool silentCalculator = taskSettings.extract(
"silent_stdout_calculator",
true);
80 auto calc = copyCalculator(systems, _input.front(),
name());
81 auto secondCalculator = systems.at(_input.back());
82 Utils::CalculationRoutines::setLog(*calc,
true,
true, !silentCalculator);
83 Utils::CalculationRoutines::setLog(*secondCalculator,
true,
true, !silentCalculator);
84 if (calc->settings() != secondCalculator->settings()) {
86 <<
" Warning: The given systems have different settings. Only taking first and ignoring second.\n";
90 auto start = calc->getStructure();
91 auto end = secondCalculator->getStructure();
92 if (start->getElements() != end->getElements()) {
93 throw std::logic_error(
"The provided structures have different elements. Impossible to find path between them.");
97 if (start->size() == 1) {
98 throw std::runtime_error(
"Cannot perform " +
name() +
" for monoatomic systems.");
101 Utils::MolecularTrajectory trajectoryGuess = Utils::MolecularTrajectory();
102 trajectoryGuess.setElementTypes(start->getElements());
103 if (taskSettings.valueExists(
"trajectory_guess")) {
104 std::vector<std::string> paths = taskSettings.getStringList(
"trajectory_guess");
105 for (
unsigned long i = 0; i < paths.size(); ++i) {
106 Utils::MolecularTrajectory traj = readTrajectory(paths[i]);
107 if (traj.getElementTypes() != trajectoryGuess.getElementTypes()) {
108 throw std::logic_error(
"The provided trajectory guess no. " + std::to_string(i) +
109 " has different elements than the provided input structures.");
111 for (
auto& step : traj) {
112 trajectoryGuess.push_back(step);
115 taskSettings.dropValue(
"trajectory_guess");
119 auto cout = _logger->output;
120 cout <<
" Interpolating Reaction Path\n";
121 ElementaryStepOptimization::ReactionProfile interpolatedProfile = interpolateElementaryStep(*start, *end, trajectoryGuess);
122 Utils::MolecularTrajectory interpolatedTrajectory = convertProfileToTrajectory(interpolatedProfile);
124 using Writer = Utils::ChemicalFileHandler;
125 const std::string& outputSystem = ((!_output.empty()) ? _output[0] : _input[0]);
126 boost::filesystem::path dir(outputSystem);
127 boost::filesystem::create_directory(dir);
128 systems[outputSystem] = calc;
129 boost::filesystem::path xyzfile(outputSystem +
"_interpolated.xyz");
130 writeTrajectory(interpolatedTrajectory, (dir / xyzfile).
string());
131 cout <<
" Interpolating Complete\n\n";
134 Utils::MolecularTrajectory optimizedTrajectory;
135 ElementaryStepOptimization::ReactionProfile optimizedProfile;
136 bool converged =
true;
138 cout <<
" Optimizing Reaction Path\n";
139 auto result = optimizeElementaryStep(std::move(interpolatedProfile), calc, taskSettings);
140 optimizedProfile = result.first;
141 converged = result.second;
142 optimizedTrajectory = convertProfileToTrajectory(optimizedProfile);
144 boost::filesystem::path xyzfile2(outputSystem +
"_optimized.xyz");
145 writeTrajectory(optimizedTrajectory, (dir / xyzfile2).
string());
146 std::string info = (converged) ?
"Complete" :
"Stopped";
147 cout <<
" Optimization " + info +
"\n\n";
149 auto reactionProfileTrajectory = this->reconstructProfile(optimizedProfile);
150 boost::filesystem::path xyzfile3(outputSystem +
"_profile.xyz");
151 writeTrajectory(reactionProfileTrajectory, (dir / xyzfile3).
string());
154 std::vector<Utils::AtomCollection> tsGuess;
155 if (optimize_ && extractTsGuess_) {
156 cout <<
" Extracting Transition State Guess\n";
157 tsGuess = extractTransitionStateGuessStructure(optimizedProfile, calc, dir);
159 boost::filesystem::path xyzfile3 = dir / (outputSystem +
"_tsguess.xyz");
160 Writer::write(xyzfile3.string(), tsGuess.at(0));
162 if (extractTsGuessNeighbours_) {
163 boost::filesystem::path xyzfile3_1 = dir / (outputSystem +
"_tsguess-1.xyz");
164 Writer::write(xyzfile3_1.string(), tsGuess.at(1));
166 boost::filesystem::path xyzfile3_2 = dir / (outputSystem +
"_tsguess+1.xyz");
167 Writer::write(xyzfile3_2.string(), tsGuess.at(2));
170 cout <<
" Extraction Complete\n\n";
177 ElementaryStepOptimization::ReactionProfile
178 interpolateElementaryStep(
const Utils::AtomCollection& start, Utils::AtomCollection end,
179 Utils::MolecularTrajectory trajectoryGuess = Utils::MolecularTrajectory())
const {
180 auto cout = _logger->output;
181 if (alignStructuresBeforeInterpolation_) {
182 auto positionsToAlign = end.getPositions();
184 end.setPositions(std::move(positionsToAlign));
187 Eigen::VectorXd startVector = Eigen::Map<const Eigen::VectorXd>(
188 start.getPositions().data(), start.getPositions().cols() * start.getPositions().rows());
189 Eigen::VectorXd endVector =
190 Eigen::Map<const Eigen::VectorXd>(end.getPositions().data(), end.getPositions().cols() * end.getPositions().rows());
191 Utils::BSplines::BSpline spline;
192 if (trajectoryGuess.empty()) {
193 spline = Utils::BSplines::LinearInterpolator::generate(startVector, endVector, numberControlPointsForInterpolation_);
196 if (start.size() != trajectoryGuess.molecularSize()) {
197 throw std::logic_error(
"The given trajectory guess has a different size than the given start system.");
200 bool viableTrajectory =
true;
201 double rmsdThreshold = 1.0;
204 Utils::QuaternionFit fit(start.getPositions(), trajectoryGuess.front());
205 if (fit.getRMSD() <= rmsdThreshold) {
206 trajectoryGuess.erase(trajectoryGuess.begin());
207 if (trajectoryGuess.empty()) {
209 <<
"Warning: no viable trajectory guess provided, all frames are too similar to the endpoints, now "
210 "performing linear interpolation."
212 viableTrajectory =
false;
220 if (viableTrajectory) {
224 Utils::QuaternionFit fit(end.getPositions(), trajectoryGuess.back());
225 if (fit.getRMSD() <= rmsdThreshold) {
226 trajectoryGuess.erase(trajectoryGuess.end());
227 if (trajectoryGuess.empty()) {
229 <<
"WARNING, no viable trajectory guess provided, all frames are too similar to the endpoints, now "
230 "performing linear interpolation."
232 viableTrajectory =
false;
241 if (viableTrajectory) {
242 Eigen::MatrixXd interpolationPoints(trajectoryGuess.size() + 2, startVector.size());
243 interpolationPoints.row(0) = startVector;
244 interpolationPoints.row(trajectoryGuess.size() + 1) = endVector;
245 for (
int i = 1; i < trajectoryGuess.size() + 1; ++i) {
246 interpolationPoints.row(i) = Eigen::Map<const Eigen::VectorXd>(
247 trajectoryGuess.at(i - 1).data(), trajectoryGuess.at(i - 1).cols() * trajectoryGuess.at(i - 1).rows());
249 Utils::BSplines::FixedEndsPenalizedLeastSquaresGenerator generator(interpolationPoints,
250 numberControlPointsForInterpolation_);
251 spline = generator.generateBSpline();
254 spline = Utils::BSplines::LinearInterpolator::generate(startVector, endVector, numberControlPointsForInterpolation_);
257 auto molecularSpline = Utils::BSplines::MolecularSpline{start.getElements(), std::move(spline)};
258 auto profile = ElementaryStepOptimization::ReactionProfile{std::move(molecularSpline)};
263 std::pair<ElementaryStepOptimization::ReactionProfile, bool>
264 optimizeElementaryStep(ElementaryStepOptimization::ReactionProfile interpolatedProfile,
265 std::shared_ptr<Core::Calculator> calculator,
266 Utils::UniversalSettings::ValueCollection& taskSettings)
const {
267 auto cout = _logger->output;
269 bool stopOnError = stopOnErrorExtraction(taskSettings);
270 std::shared_ptr<ElementaryStepOptimization::ElementaryStepOptimizerBase> optimizer;
271 if (optimizer_ ==
"steepestdescent" || optimizer_ ==
"sd") {
272 auto tmp = std::make_shared<ElementaryStepOptimization::ElementaryStepOptimizer<Utils::SteepestDescent>>(
273 *calculator, std::move(interpolatedProfile));
275 tmp->check.requirement = 4;
276 tmp->check.gradRMS = 1.0e-3;
277 tmp->check.stepMaxCoeff = 1.0e+10;
278 tmp->check.stepRMS = 1.0e+10;
279 tmp->check.gradMaxCoeff = 1.0e+10;
280 tmp->check.deltaValue = 1.0e+10;
281 optimizer = std::move(tmp);
283 else if (optimizer_ ==
"lbfgs") {
284 auto tmp = std::make_shared<ElementaryStepOptimization::ElementaryStepOptimizer<Utils::Lbfgs>>(
285 *calculator, std::move(interpolatedProfile));
286 tmp->optimizer.linesearch =
false;
288 tmp->check.requirement = 4;
289 tmp->check.gradRMS = 1.0e-3;
290 tmp->check.stepMaxCoeff = 1.0e+10;
291 tmp->check.stepRMS = 1.0e+10;
292 tmp->check.gradMaxCoeff = 1.0e+10;
293 tmp->check.deltaValue = 1.0e+10;
294 optimizer = std::move(tmp);
297 throw std::runtime_error(
"Optimizer not supported.");
301 auto settings = optimizer->getSettings();
302 settings.merge(taskSettings);
303 if (!settings.valid()) {
304 settings.throwIncorrectSettings();
306 optimizer->setSettings(settings);
307 if (!Utils::GeometryOptimization::settingsMakeSense(*optimizer)) {
308 throw std::logic_error(
"The given calculator settings are too inaccurate for the given convergence criteria of "
309 "this optimization Task");
312 const int cycles = optimizer->optimize(*_logger);
313 const int maxiter = settings.getInt(
"convergence_max_iterations");
315 bool converged = cycles < maxiter;
326 throw std::runtime_error(
"Problem: Path optimization did not converge.");
329 const auto& optimizedProfile = optimizer->getReactionProfile();
331 return std::make_pair(optimizedProfile, converged);
334 std::vector<Utils::AtomCollection> extractTransitionStateGuessStructure(ElementaryStepOptimization::ReactionProfile profile,
335 std::shared_ptr<Core::Calculator> calculator,
336 const boost::filesystem::path& dir)
const {
337 auto cout = _logger->output;
338 const auto& molecularSpline = profile.getMolecularSpline();
341 const Utils::ElementTypeCollection& ec = molecularSpline.getElements();
342 Utils::PositionCollection pc(ec.size(), 3);
344 calculator->setStructure(Utils::AtomCollection(ec, pc));
345 calculator->setRequiredProperties(Utils::Property::Energy);
347 std::vector<double> coordinates;
348 std::vector<double> energies;
350 getInitialValues(maxEnergyIndex, coordinates, energies, calculator, profile);
352 while (pointDistance(coordinates, maxEnergyIndex) > coordinateThresholdForMaximumExtraction_) {
353 coordinates = getNewCoordinates(coordinates, maxEnergyIndex);
354 energies = getNewEnergies(energies, coordinates, maxEnergyIndex, calculator, molecularSpline);
355 maxEnergyIndex = getIndexForMaxEnergyAndCheckValidity(energies);
359 if (!tangentFileName_.empty()) {
360 Utils::BSplines::BSpline spline = molecularSpline.getBSpline();
361 Utils::BSplines::BSpline derivative = spline.getDerivativeBSpline(1);
362 Utils::BSplines::MolecularSpline molecularDerivative(ec, derivative);
363 Utils::PositionCollection derivativePosition = molecularDerivative.getPositions(coordinates.at(maxEnergyIndex));
364 Eigen::Map<Eigen::VectorXd> tangent(derivativePosition.data(), derivativePosition.size());
365 writeTangentToFile(tangent, dir);
368 std::vector<Utils::AtomCollection> result;
369 result.push_back(molecularSpline.at(coordinates.at(maxEnergyIndex)));
370 if (extractTsGuessNeighbours_) {
371 result.push_back(molecularSpline.at(coordinates.at(maxEnergyIndex - 1)));
372 result.push_back(molecularSpline.at(coordinates.at(maxEnergyIndex + 1)));
378 void writeTangentToFile(
const Eigen::VectorXd& tangent,
const boost::filesystem::path& generalOutputDir)
const {
381 boost::filesystem::path tangentPath(tangentFileName_);
382 if (tangentFileName_.at(0) !=
'/') {
383 tangentPath = boost::filesystem::absolute(generalOutputDir / tangentFileName_);
386 boost::filesystem::path parent = tangentPath.parent_path();
387 if (!parent.empty()) {
389 boost::filesystem::detail::create_directories(parent);
391 std::ofstream fout(tangentPath.string());
392 if (!fout.is_open()) {
393 throw std::runtime_error(
"Problem when opening/creating file: " + tangentPath.string());
395 fout.imbue(std::locale(
"C"));
396 for (
int i = 0; i < tangent.size(); ++i) {
397 fout << tangent.row(i) <<
'\n';
402 Utils::MolecularTrajectory convertProfileToTrajectory(ElementaryStepOptimization::ReactionProfile profile)
const {
403 auto elements = profile.getMolecularSpline().getElements();
404 auto spline = profile.getMolecularSpline().getBSpline();
405 Utils::MolecularTrajectory trajectory = discretizeSpline(elements, spline, numberStructuresForMolecularTrajectory_);
409 static Utils::MolecularTrajectory discretizeSpline(
const Utils::ElementTypeCollection& elements,
410 const Utils::BSplines::BSpline& spline,
int numberPoints) {
411 Utils::MolecularTrajectory t;
412 t.setElementTypes(elements);
413 double deltaU = 1.0 / (numberPoints - 1);
414 for (
int i = 0; i < numberPoints; ++i) {
415 double u = i * deltaU;
416 Utils::PositionCollection pc =
417 Eigen::Map<const Utils::PositionCollection>(spline.evaluate(u).data(), elements.size(), 3);
418 t.push_back(std::move(pc));
423 static Utils::MolecularTrajectory reconstructProfile(ElementaryStepOptimization::ReactionProfile profile) {
424 auto elements = profile.getMolecularSpline().getElements();
425 auto spline = profile.getMolecularSpline().getBSpline();
426 const auto& profileEnergies = profile.getProfileEnergies();
427 Utils::MolecularTrajectory t;
428 t.setElementTypes(elements);
429 std::vector<double> energies;
430 for (
int i = 0; i < profileEnergies.size(); ++i) {
431 const auto pair = profileEnergies.getPair(i);
432 double u = pair.first;
433 double e = pair.second;
434 Utils::PositionCollection pc =
435 Eigen::Map<const Utils::PositionCollection>(spline.evaluate(u).data(), elements.size(), 3);
436 t.push_back(std::move(pc));
437 energies.push_back(e);
439 t.setEnergies(energies);
443 static void writeTrajectory(
const Utils::MolecularTrajectory& trajectory,
const std::string& filepath) {
444 std::ofstream ostream(filepath, std::ofstream::out);
449 static Utils::MolecularTrajectory readTrajectory(
const std::string& filepath) {
450 Utils::MolecularTrajectory trajectory;
452 if (fb.open(filepath, std::istream::in)) {
453 std::istream is(&fb);
460 static void getInitialValues(
int& maxEnergyIndex, std::vector<double>& coordinates, std::vector<double>& energies,
461 std::shared_ptr<Core::Calculator> calculator,
462 const ElementaryStepOptimization::ReactionProfile& profile) {
463 const auto& profileEnergies = profile.getProfileEnergies();
464 if (!profile.getProfileEnergies().empty()) {
465 coordinates = profileEnergies.getCoordinates();
466 energies = profileEnergies.getEnergies();
467 maxEnergyIndex = getIndexForMaxEnergyAndCheckValidity(energies);
470 const auto& molecularSpline = profile.getMolecularSpline();
471 coordinates = {0.0, 0.5, 1.0};
474 calculator->modifyPositions(molecularSpline.getPositions(coordinates[0]));
475 energies[0] = calculator->calculate(
"").get<Utils::Property::Energy>();
477 calculator->modifyPositions(molecularSpline.getPositions(coordinates[1]));
478 energies[1] = calculator->calculate(
"").get<Utils::Property::Energy>();
480 calculator->modifyPositions(molecularSpline.getPositions(coordinates[2]));
481 energies[2] = calculator->calculate(
"").get<Utils::Property::Energy>();
487 static double pointDistance(
const std::vector<double>& coordinates,
int maxEnergyIndex) {
488 double diff = coordinates[maxEnergyIndex + 1] - coordinates[maxEnergyIndex - 1];
492 static std::vector<double> getNewCoordinates(
const std::vector<double>& oldCoordinates,
int maxEnergyIndex) {
493 std::vector<double> u(5);
494 u[0] = oldCoordinates[maxEnergyIndex - 1];
495 u[2] = oldCoordinates[maxEnergyIndex];
496 u[4] = oldCoordinates[maxEnergyIndex + 1];
497 u[1] = 0.5 * (u[0] + u[2]);
498 u[3] = 0.5 * (u[2] + u[4]);
502 static std::vector<double> getNewEnergies(
const std::vector<double>& oldEnergies,
const std::vector<double>& coordinates,
503 int maxEnergyIndex, std::shared_ptr<Core::Calculator> calculator,
504 const Utils::BSplines::MolecularSpline& spline) {
505 std::vector<double> e(5);
506 e[0] = oldEnergies[maxEnergyIndex - 1];
507 e[2] = oldEnergies[maxEnergyIndex];
508 e[4] = oldEnergies[maxEnergyIndex + 1];
510 calculator->modifyPositions(spline.getPositions(coordinates[1]));
511 e[1] = calculator->calculate(
"").get<Utils::Property::Energy>();
513 calculator->modifyPositions(spline.getPositions(coordinates[3]));
514 e[3] = calculator->calculate(
"").get<Utils::Property::Energy>();
519 static int getIndexForMaxEnergyAndCheckValidity(
const std::vector<double>& energies) {
520 auto maxIt = std::max_element(energies.begin(), energies.end());
521 auto dist = std::distance(energies.begin(), maxIt);
522 if (dist == 0 || dist == static_cast<int>(energies.size()) - 1) {
523 throw std::runtime_error(
"Problem: End point has maximal energy.");
525 return static_cast<int>(dist);
529 mutable bool alignStructuresBeforeInterpolation_ =
true;
530 mutable int numberControlPointsForInterpolation_ = 5;
531 mutable int numberStructuresForMolecularTrajectory_ = 10;
532 mutable bool optimize_ =
true;
533 mutable std::string optimizer_ =
"lbfgs";
534 mutable bool extractTsGuess_ =
true;
535 mutable bool extractTsGuessNeighbours_ =
false;
536 mutable std::string tangentFileName_;
537 mutable double coordinateThresholdForMaximumExtraction_ = 1e-3;
543 #endif // READUCT_BSPLINEINTERPOLATIONTASK_H_
void warningIfMultipleOutputsGiven() const
Warn if more than one output system was specified.
Definition: Task.h:107
static std::ostream & endl(std::ostream &os)
static MolecularTrajectory read(format f, const std::string &fileName)
const std::vector< std::string > & input() const
Getter for the expected names of the input systems.
Definition: Task.h:85
static void write(format f, const std::string &fileName, const MolecularTrajectory &m, const BondOrderCollection &bondOrders)
static std::ostream & nl(std::ostream &os)
Definition: BSplineInterpolationTask.h:31
void alignPositions(const PositionCollection &reference, PositionCollection &positions)
const std::vector< std::string > & output() const
Getter for the names of the output systems generated by this task.
Definition: Task.h:92
BSplineInterpolationTask(std::vector< std::string > input, std::vector< std::string > output, std::shared_ptr< Core::Log > logger=nullptr)
Construct a new BSplineInterpolationTask.
Definition: BSplineInterpolationTask.h:39
The base class for all tasks in Readuct.
Definition: Task.h:29
std::string name() const override
Getter for the tasks name.
Definition: BSplineInterpolationTask.h:44