Scine::Readuct  6.0.0
This is the SCINE module ReaDuct.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Pages
BSplineInterpolationTask.h
Go to the documentation of this file.
1 
7 #ifndef READUCT_BSPLINEINTERPOLATIONTASK_H_
8 #define READUCT_BSPLINEINTERPOLATIONTASK_H_
9 
10 #include "../../Readuct/ElementaryStepOptimization/ElementaryStepOptimizer.h"
11 #include "../../Readuct/ElementaryStepOptimization/ProfileEnergies.h"
12 #include "../../Readuct/ElementaryStepOptimization/ReactionProfile.h"
13 #include "Tasks/Task.h"
25 #include <boost/filesystem.hpp>
26 #include <fstream>
27 
28 namespace Scine {
29 namespace Readuct {
30 
32  public:
39  BSplineInterpolationTask(std::vector<std::string> input, std::vector<std::string> output,
40  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 "BSpline Interpolation";
46  }
47 
48  bool run(std::map<std::string, std::shared_ptr<Core::Calculator>>& systems,
49  Utils::UniversalSettings::ValueCollection taskSettings, bool testRunOnly = false,
50  std::vector<std::function<void(const int&, const Utils::AtomCollection&, const Utils::Results&, const std::string&)>>
51  observers = {}) const final {
52  if (_input.size() != 2) {
53  throw std::logic_error("The B-Spline task needs two input systems.");
54  }
56  if (observers.size() > 0) {
57  throw std::logic_error(
58  "BSplineInterpolationTask does not feature algorithm accepting observers, yet one was given");
59  }
60  // Read and set user-specified settings
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);
73 
74  // If no errors encountered until here, the basic settings should be alright
75  if (testRunOnly) {
76  return true; // leave out rest in case of task chaining
77  }
78 
79  // Note: _input is guaranteed not to be empty by Task constructor
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()) {
85  _logger->warning
86  << " Warning: The given systems have different settings. Only taking first and ignoring second.\n";
87  }
88 
89  // Get inputs
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.");
94  }
95 
96  // Check system size
97  if (start->size() == 1) {
98  throw std::runtime_error("Cannot perform " + name() + " for monoatomic systems.");
99  }
100 
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.");
110  }
111  for (auto& step : traj) {
112  trajectoryGuess.push_back(step);
113  }
114  }
115  taskSettings.dropValue("trajectory_guess");
116  }
117 
118  // Interpolate between start and end structure
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);
123  // Print/Store results
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";
132 
133  // Optimize the interpolated path
134  Utils::MolecularTrajectory optimizedTrajectory;
135  ElementaryStepOptimization::ReactionProfile optimizedProfile;
136  bool converged = true;
137  if (optimize_) {
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);
143  // Output
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";
148 
149  auto reactionProfileTrajectory = this->reconstructProfile(optimizedProfile);
150  boost::filesystem::path xyzfile3(outputSystem + "_profile.xyz");
151  writeTrajectory(reactionProfileTrajectory, (dir / xyzfile3).string());
152  }
153 
154  std::vector<Utils::AtomCollection> tsGuess;
155  if (optimize_ && extractTsGuess_) {
156  cout << " Extracting Transition State Guess\n";
157  tsGuess = extractTransitionStateGuessStructure(optimizedProfile, calc, dir);
158  // Output
159  boost::filesystem::path xyzfile3 = dir / (outputSystem + "_tsguess.xyz");
160  Writer::write(xyzfile3.string(), tsGuess.at(0));
161 
162  if (extractTsGuessNeighbours_) {
163  boost::filesystem::path xyzfile3_1 = dir / (outputSystem + "_tsguess-1.xyz");
164  Writer::write(xyzfile3_1.string(), tsGuess.at(1));
165 
166  boost::filesystem::path xyzfile3_2 = dir / (outputSystem + "_tsguess+1.xyz");
167  Writer::write(xyzfile3_2.string(), tsGuess.at(2));
168  }
169 
170  cout << " Extraction Complete\n\n";
171  }
172  cout << Core::Log::nl << Core::Log::endl;
173  return converged;
174  }
175 
176  private:
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();
183  Utils::Geometry::Manipulations::alignPositions(start.getPositions(), positionsToAlign);
184  end.setPositions(std::move(positionsToAlign));
185  }
186 
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_);
194  }
195  else {
196  if (start.size() != trajectoryGuess.molecularSize()) {
197  throw std::logic_error("The given trajectory guess has a different size than the given start system.");
198  }
199  /* Remove frames from trajectory at end and beginning which are too similar to given endpoints */
200  bool viableTrajectory = true;
201  double rmsdThreshold = 1.0;
202  bool stop = false;
203  while (!stop) {
204  Utils::QuaternionFit fit(start.getPositions(), trajectoryGuess.front());
205  if (fit.getRMSD() <= rmsdThreshold) {
206  trajectoryGuess.erase(trajectoryGuess.begin());
207  if (trajectoryGuess.empty()) {
208  _logger->warning
209  << "Warning: no viable trajectory guess provided, all frames are too similar to the endpoints, now "
210  "performing linear interpolation."
211  << Core::Log::endl;
212  viableTrajectory = false;
213  stop = true;
214  }
215  }
216  else {
217  stop = true;
218  }
219  }
220  if (viableTrajectory) {
221  stop = false;
222  }
223  while (!stop) {
224  Utils::QuaternionFit fit(end.getPositions(), trajectoryGuess.back());
225  if (fit.getRMSD() <= rmsdThreshold) {
226  trajectoryGuess.erase(trajectoryGuess.end());
227  if (trajectoryGuess.empty()) {
228  _logger->warning
229  << "WARNING, no viable trajectory guess provided, all frames are too similar to the endpoints, now "
230  "performing linear interpolation."
231  << Core::Log::endl;
232  viableTrajectory = false;
233  stop = true;
234  }
235  }
236  else {
237  stop = true;
238  }
239  }
240  /* Combine input for spline generator or perform linear interpolation if no viable trajectory given */
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());
248  }
249  Utils::BSplines::FixedEndsPenalizedLeastSquaresGenerator generator(interpolationPoints,
250  numberControlPointsForInterpolation_);
251  spline = generator.generateBSpline();
252  }
253  else {
254  spline = Utils::BSplines::LinearInterpolator::generate(startVector, endVector, numberControlPointsForInterpolation_);
255  }
256  }
257  auto molecularSpline = Utils::BSplines::MolecularSpline{start.getElements(), std::move(spline)};
258  auto profile = ElementaryStepOptimization::ReactionProfile{std::move(molecularSpline)};
259 
260  return profile;
261  }
262 
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;
268  // Read and delete special settings
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));
274  // The original ReaDuct implementation's settings converted into the new form.
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);
282  }
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;
287  // The original ReaDuct implementation's settings converted into the new form.
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);
295  }
296  else {
297  throw std::runtime_error("Optimizer not supported.");
298  }
299 
300  // Apply user settings
301  auto settings = optimizer->getSettings();
302  settings.merge(taskSettings);
303  if (!settings.valid()) {
304  settings.throwIncorrectSettings();
305  }
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");
310  }
311 
312  const int cycles = optimizer->optimize(*_logger);
313  const int maxiter = settings.getInt("convergence_max_iterations");
314 
315  bool converged = cycles < maxiter;
316  if (converged) {
317  cout << Core::Log::endl
318  << " Converged after " << cycles << " iterations." << Core::Log::endl
319  << Core::Log::endl;
320  }
321  else {
322  cout << Core::Log::endl
323  << " Stopped after " << maxiter << " iterations." << Core::Log::endl
324  << Core::Log::endl;
325  if (stopOnError) {
326  throw std::runtime_error("Problem: Path optimization did not converge.");
327  }
328  }
329  const auto& optimizedProfile = optimizer->getReactionProfile();
330 
331  return std::make_pair(optimizedProfile, converged);
332  }
333 
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();
339 
340  // Generate an empty molecule with the correct element types
341  const Utils::ElementTypeCollection& ec = molecularSpline.getElements();
342  Utils::PositionCollection pc(ec.size(), 3);
343 
344  calculator->setStructure(Utils::AtomCollection(ec, pc));
345  calculator->setRequiredProperties(Utils::Property::Energy);
346 
347  std::vector<double> coordinates;
348  std::vector<double> energies;
349  int maxEnergyIndex;
350  getInitialValues(maxEnergyIndex, coordinates, energies, calculator, profile);
351 
352  while (pointDistance(coordinates, maxEnergyIndex) > coordinateThresholdForMaximumExtraction_) {
353  coordinates = getNewCoordinates(coordinates, maxEnergyIndex);
354  energies = getNewEnergies(energies, coordinates, maxEnergyIndex, calculator, molecularSpline);
355  maxEnergyIndex = getIndexForMaxEnergyAndCheckValidity(energies);
356  }
357 
358  /* Write out tangent */
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);
366  }
367 
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)));
373  }
374 
375  return result;
376  }
377 
378  void writeTangentToFile(const Eigen::VectorXd& tangent, const boost::filesystem::path& generalOutputDir) const {
379  // if absolute path given by user, path is directly used
380  // else the relative path is used relative to the general output of the calculation
381  boost::filesystem::path tangentPath(tangentFileName_);
382  if (tangentFileName_.at(0) != '/') {
383  tangentPath = boost::filesystem::absolute(generalOutputDir / tangentFileName_);
384  }
385  // create necessary directories for file if necessary
386  boost::filesystem::path parent = tangentPath.parent_path();
387  if (!parent.empty()) {
388  // does not give error if directories already exist
389  boost::filesystem::detail::create_directories(parent);
390  }
391  std::ofstream fout(tangentPath.string());
392  if (!fout.is_open()) {
393  throw std::runtime_error("Problem when opening/creating file: " + tangentPath.string());
394  }
395  fout.imbue(std::locale("C"));
396  for (int i = 0; i < tangent.size(); ++i) {
397  fout << tangent.row(i) << '\n';
398  }
399  fout.close();
400  };
401 
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_);
406  return trajectory;
407  }
408 
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));
419  }
420  return t;
421  }
422 
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);
438  }
439  t.setEnergies(energies);
440  return t;
441  }
442 
443  static void writeTrajectory(const Utils::MolecularTrajectory& trajectory, const std::string& filepath) {
444  std::ofstream ostream(filepath, std::ofstream::out);
446  ostream.close();
447  }
448 
449  static Utils::MolecularTrajectory readTrajectory(const std::string& filepath) {
450  Utils::MolecularTrajectory trajectory;
451  std::filebuf fb;
452  if (fb.open(filepath, std::istream::in)) {
453  std::istream is(&fb);
455  fb.close();
456  }
457  return trajectory;
458  }
459 
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);
468  }
469  else {
470  const auto& molecularSpline = profile.getMolecularSpline();
471  coordinates = {0.0, 0.5, 1.0};
472  energies.resize(3);
473 
474  calculator->modifyPositions(molecularSpline.getPositions(coordinates[0]));
475  energies[0] = calculator->calculate("").get<Utils::Property::Energy>();
476 
477  calculator->modifyPositions(molecularSpline.getPositions(coordinates[1]));
478  energies[1] = calculator->calculate("").get<Utils::Property::Energy>();
479 
480  calculator->modifyPositions(molecularSpline.getPositions(coordinates[2]));
481  energies[2] = calculator->calculate("").get<Utils::Property::Energy>();
482 
483  maxEnergyIndex = 1;
484  }
485  }
486 
487  static double pointDistance(const std::vector<double>& coordinates, int maxEnergyIndex) {
488  double diff = coordinates[maxEnergyIndex + 1] - coordinates[maxEnergyIndex - 1];
489  return diff / 2;
490  }
491 
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]);
499  return u;
500  }
501 
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];
509 
510  calculator->modifyPositions(spline.getPositions(coordinates[1]));
511  e[1] = calculator->calculate("").get<Utils::Property::Energy>();
512 
513  calculator->modifyPositions(spline.getPositions(coordinates[3]));
514  e[3] = calculator->calculate("").get<Utils::Property::Energy>();
515 
516  return e;
517  }
518 
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.");
524  }
525  return static_cast<int>(dist);
526  }
527 
528  // Default settings
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;
538 };
539 
540 } // namespace Readuct
541 } // namespace Scine
542 
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