Skip to content

Commit 2033017

Browse files
committed
Adding NEB reader
1 parent 4e3d499 commit 2033017

13 files changed

Lines changed: 2261 additions & 148 deletions

CMakeLists.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,16 @@ add_executable(atom-architect WIN32
3939
src/gui/toolbar.cpp
4040
src/gui/user_action.cpp
4141
src/gui/scene.cpp
42-
src/gui/analysis_geometry_optimization.cpp
43-
src/gui/geometry_optimization_viewer.cpp
44-
src/gui/geometry_optimization_graph.cpp
42+
src/gui/structure_analysis.cpp
43+
src/gui/structure_analysis_viewer.cpp
44+
src/gui/structure_analysis_graph.cpp
4545
src/gui/analysis_neb.cpp
4646
src/gui/logwindow.cpp
4747
src/data/atom_settings.cpp
4848
src/data/atom.cpp
4949
src/data/bond.cpp
5050
src/data/fragment.cpp
51+
src/data/neb_calculation_loader.cpp
5152
src/data/model.cpp
5253
src/data/model_loader.cpp
5354
src/data/structure.cpp

codex.patch

Lines changed: 1911 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#include "neb_calculation_loader.h"
2+
3+
#include <QDir>
4+
#include <QFileInfo>
5+
#include <QRegularExpression>
6+
#include <QObject>
7+
8+
#include <algorithm>
9+
10+
#include "structure_loader.h"
11+
12+
bool NebCalculationLoader::load(const QString& root_directory, QString* error_message)
13+
{
14+
structures_.clear();
15+
16+
const QDir root(root_directory);
17+
if(!root.exists()) {
18+
if(error_message) {
19+
*error_message = QObject::tr("Selected directory does not exist.");
20+
}
21+
return false;
22+
}
23+
24+
QFileInfoList all_subdirs = root.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot,
25+
QDir::Name | QDir::IgnoreCase);
26+
std::vector<QFileInfo> image_directories;
27+
image_directories.reserve((size_t)all_subdirs.size());
28+
29+
const QRegularExpression image_directory_regex("^\\d+$");
30+
31+
for(const QFileInfo& entry : all_subdirs) {
32+
if(image_directory_regex.match(entry.fileName()).hasMatch()) {
33+
image_directories.push_back(entry);
34+
}
35+
}
36+
37+
std::sort(image_directories.begin(), image_directories.end(),
38+
[](const QFileInfo& lhs, const QFileInfo& rhs) {
39+
bool ok_lhs = false;
40+
bool ok_rhs = false;
41+
const int lhs_value = lhs.fileName().toInt(&ok_lhs);
42+
const int rhs_value = rhs.fileName().toInt(&ok_rhs);
43+
44+
if(ok_lhs && ok_rhs && lhs_value != rhs_value) {
45+
return lhs_value < rhs_value;
46+
}
47+
48+
return lhs.fileName() < rhs.fileName();
49+
});
50+
51+
if(image_directories.size() < 3) {
52+
if(error_message) {
53+
*error_message = QObject::tr("No valid VASP NEB folder structure was found. "
54+
"Expected image folders (e.g. 00, 01, 02, ...) with "
55+
"at least one intermediate image.");
56+
}
57+
return false;
58+
}
59+
60+
StructureLoader structure_loader;
61+
for(size_t i = 1; i + 1 < image_directories.size(); ++i) {
62+
const QString outcar_path = QDir(image_directories[i].absoluteFilePath()).filePath("OUTCAR");
63+
const QFileInfo outcar_info(outcar_path);
64+
65+
if(!outcar_info.exists() || !outcar_info.isFile()) {
66+
if(error_message) {
67+
*error_message = QObject::tr("Folder '%1' does not contain an OUTCAR file.")
68+
.arg(image_directories[i].fileName());
69+
}
70+
structures_.clear();
71+
return false;
72+
}
73+
74+
try {
75+
auto trajectory = structure_loader.load_outcar(outcar_path.toStdString());
76+
if(trajectory.empty()) {
77+
if(error_message) {
78+
*error_message = QObject::tr("OUTCAR in folder '%1' does not contain ionic images.")
79+
.arg(image_directories[i].fileName());
80+
}
81+
structures_.clear();
82+
return false;
83+
}
84+
85+
structures_.push_back(trajectory.back());
86+
} catch (const std::exception& e) {
87+
if(error_message) {
88+
*error_message = QObject::tr("Failed to read OUTCAR in folder '%1': %2")
89+
.arg(image_directories[i].fileName(), e.what());
90+
}
91+
structures_.clear();
92+
return false;
93+
}
94+
}
95+
96+
if(structures_.empty()) {
97+
if(error_message) {
98+
*error_message = QObject::tr("No intermediate NEB images were found.");
99+
}
100+
return false;
101+
}
102+
103+
return true;
104+
}
105+

src/data/neb_calculation_loader.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#pragma once
2+
3+
#include <QString>
4+
5+
#include <memory>
6+
#include <vector>
7+
8+
#include "structure.h"
9+
10+
class NebCalculationLoader {
11+
public:
12+
bool load(const QString& root_directory, QString* error_message = nullptr);
13+
14+
const std::vector<std::shared_ptr<Structure>>& structures() const {
15+
return structures_;
16+
}
17+
18+
private:
19+
std::vector<std::shared_ptr<Structure>> structures_;
20+
};
21+

src/gui/interface_window.cpp

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,8 @@ InterfaceWindow::InterfaceWindow(MainWindow *mw)
237237
// ------------------------------------------------------------
238238
// Geometry optimization viewer (BOTTOM-LEFT)
239239
// ------------------------------------------------------------
240-
geometryOptimization = new AnalysisGeometryOptimization(this);
241-
this->analysis_panel_ = geometryOptimization->viewer();
240+
structureAnalysis = new StructureAnalysis(this);
241+
this->analysis_panel_ = structureAnalysis->viewer();
242242

243243
QMenuBar *analysisMenuBar = new QMenuBar(this);
244244
analysisMenuBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
@@ -273,6 +273,7 @@ InterfaceWindow::InterfaceWindow(MainWindow *mw)
273273

274274
QAction *analysisActionOpen = analysisMenuFile->addAction(tr("Open"));
275275
analysisActionOpen->setShortcuts(QKeySequence::Open);
276+
QAction *analysisActionOpenNeb = analysisMenuFile->addAction(tr("Open NEB calculation"));
276277
QAction *analysisActionSendToEditor = analysisMenuFile->addAction(tr("Send to editor"));
277278

278279
analysisActionCameraDefault->setText(tr("Default"));
@@ -321,7 +322,7 @@ InterfaceWindow::InterfaceWindow(MainWindow *mw)
321322
analysisActionProjectionInterlacedCheckerboardLr->setIcon(QIcon(":/assets/icon/interlaced_checkerboard_lr_32.png"));
322323
analysisActionProjectionInterlacedCheckerboardRl->setIcon(QIcon(":/assets/icon/interlaced_checkerboard_rl_32.png"));
323324

324-
for(QAction *action : {analysisActionOpen, analysisActionSendToEditor,
325+
for(QAction *action : {analysisActionOpen, analysisActionOpenNeb, analysisActionSendToEditor,
325326
analysisActionCameraDefault, analysisActionCameraTop, analysisActionCameraBottom,
326327
analysisActionCameraLeft, analysisActionCameraRight, analysisActionCameraFront,
327328
analysisActionCameraBack, analysisActionCameraPerspective, analysisActionCameraOrthographic,
@@ -356,13 +357,13 @@ InterfaceWindow::InterfaceWindow(MainWindow *mw)
356357
analysisMenuProjectionInterlaced->addAction(analysisActionProjectionInterlacedCheckerboardLr);
357358
analysisMenuProjectionInterlaced->addAction(analysisActionProjectionInterlacedCheckerboardRl);
358359

359-
geometryOptimization->viewer()->set_header_widget(analysisMenuBar);
360+
structureAnalysis->viewer()->set_header_widget(analysisMenuBar);
360361

361-
analysis_toolbar = new ToolBarWidget(geometryOptimization->viewer(), false);
362+
analysis_toolbar = new ToolBarWidget(structureAnalysis->viewer(), false);
362363
analysis_toolbar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
363-
geometryOptimization->viewer()->set_side_toolbar(analysis_toolbar);
364+
structureAnalysis->viewer()->set_side_toolbar(analysis_toolbar);
364365

365-
leftSplitter->addWidget(geometryOptimization->viewer());
366+
leftSplitter->addWidget(structureAnalysis->viewer());
366367

367368
// ============================================================
368369
// RIGHT COLUMN (Structure info + Optimization graph)
@@ -383,7 +384,7 @@ InterfaceWindow::InterfaceWindow(MainWindow *mw)
383384
// ------------------------------------------------------------
384385
// Optimization graph (BOTTOM-RIGHT)
385386
// ------------------------------------------------------------
386-
rightSplitter->addWidget(geometryOptimization->graph());
387+
rightSplitter->addWidget(structureAnalysis->graph());
387388

388389
// ============================================================
389390
// Initial 50/50/50/50 layout (relative to MainWindow size)
@@ -422,12 +423,12 @@ InterfaceWindow::InterfaceWindow(MainWindow *mw)
422423

423424
connect(analysis_toolbar->get_action("toggle_periodicity_xy"),
424425
SIGNAL(triggered()),
425-
geometryOptimization->viewer()->get_anaglyph_widget(),
426+
structureAnalysis->viewer()->get_anaglyph_widget(),
426427
SLOT(toggle_periodicity_xy()));
427428

428429
connect(analysis_toolbar->get_action("toggle_periodicity_z"),
429430
SIGNAL(triggered()),
430-
geometryOptimization->viewer()->get_anaglyph_widget(),
431+
structureAnalysis->viewer()->get_anaglyph_widget(),
431432
SLOT(toggle_periodicity_z()));
432433

433434
connect(anaglyph_widget, SIGNAL(signal_interaction_message(const QString&)),
@@ -449,12 +450,13 @@ InterfaceWindow::InterfaceWindow(MainWindow *mw)
449450
anaglyph_widget->get_user_action().get(),
450451
SLOT(set_fragment(const Fragment&)));
451452

452-
connect(geometryOptimization->viewer(), SIGNAL(edit_requested()),
453+
connect(structureAnalysis->viewer(), SIGNAL(edit_requested()),
453454
this, SLOT(load_structure_from_geometry_analysis()));
454455

455456
connect(editorActionOpen, &QAction::triggered, this, &InterfaceWindow::open_editor_file);
456457
connect(editorActionSave, &QAction::triggered, this, &InterfaceWindow::save_editor_file);
457458
connect(analysisActionOpen, &QAction::triggered, this, &InterfaceWindow::open_analysis_file);
459+
connect(analysisActionOpenNeb, &QAction::triggered, this, &InterfaceWindow::open_analysis_neb_calculation);
458460
connect(analysisActionSendToEditor, &QAction::triggered, this, &InterfaceWindow::load_structure_from_geometry_analysis);
459461

460462
connect(editorActionSelectAll, SIGNAL(triggered()), this, SLOT(select_all_atoms()));
@@ -474,16 +476,16 @@ InterfaceWindow::InterfaceWindow(MainWindow *mw)
474476
connect(editorActionProjectionInterlacedCheckerboardLr, &QAction::triggered, this, [this]{ this->anaglyph_widget->set_stereo("stereo_interlaced_checkerboard_lr"); });
475477
connect(editorActionProjectionInterlacedCheckerboardRl, &QAction::triggered, this, [this]{ this->anaglyph_widget->set_stereo("stereo_interlaced_checkerboard_rl"); });
476478

477-
connect(analysisMenuCameraAlign, SIGNAL(triggered(QAction*)), geometryOptimization, SLOT(set_camera_align(QAction*)));
478-
connect(analysisMenuCameraMode, SIGNAL(triggered(QAction*)), geometryOptimization, SLOT(set_camera_mode(QAction*)));
479-
connect(analysisActionProjectionTwoDimensional, &QAction::triggered, this, [this]{ this->geometryOptimization->set_stereo("no_stereo_flat"); });
480-
connect(analysisActionProjectionAnaglyphRedCyan, &QAction::triggered, this, [this]{ this->geometryOptimization->set_stereo("stereo_anaglyph_red_cyan"); });
481-
connect(analysisActionProjectionInterlacedRowsLr, &QAction::triggered, this, [this]{ this->geometryOptimization->set_stereo("stereo_interlaced_rows_lr"); });
482-
connect(analysisActionProjectionInterlacedRowsRl, &QAction::triggered, this, [this]{ this->geometryOptimization->set_stereo("stereo_interlaced_rows_rl"); });
483-
connect(analysisActionProjectionInterlacedColumnsLr, &QAction::triggered, this, [this]{ this->geometryOptimization->set_stereo("stereo_interlaced_columns_lr"); });
484-
connect(analysisActionProjectionInterlacedColumnsRl, &QAction::triggered, this, [this]{ this->geometryOptimization->set_stereo("stereo_interlaced_columns_rl"); });
485-
connect(analysisActionProjectionInterlacedCheckerboardLr, &QAction::triggered, this, [this]{ this->geometryOptimization->set_stereo("stereo_interlaced_checkerboard_lr"); });
486-
connect(analysisActionProjectionInterlacedCheckerboardRl, &QAction::triggered, this, [this]{ this->geometryOptimization->set_stereo("stereo_interlaced_checkerboard_rl"); });
479+
connect(analysisMenuCameraAlign, SIGNAL(triggered(QAction*)), structureAnalysis, SLOT(set_camera_align(QAction*)));
480+
connect(analysisMenuCameraMode, SIGNAL(triggered(QAction*)), structureAnalysis, SLOT(set_camera_mode(QAction*)));
481+
connect(analysisActionProjectionTwoDimensional, &QAction::triggered, this, [this]{ this->structureAnalysis->set_stereo("no_stereo_flat"); });
482+
connect(analysisActionProjectionAnaglyphRedCyan, &QAction::triggered, this, [this]{ this->structureAnalysis->set_stereo("stereo_anaglyph_red_cyan"); });
483+
connect(analysisActionProjectionInterlacedRowsLr, &QAction::triggered, this, [this]{ this->structureAnalysis->set_stereo("stereo_interlaced_rows_lr"); });
484+
connect(analysisActionProjectionInterlacedRowsRl, &QAction::triggered, this, [this]{ this->structureAnalysis->set_stereo("stereo_interlaced_rows_rl"); });
485+
connect(analysisActionProjectionInterlacedColumnsLr, &QAction::triggered, this, [this]{ this->structureAnalysis->set_stereo("stereo_interlaced_columns_lr"); });
486+
connect(analysisActionProjectionInterlacedColumnsRl, &QAction::triggered, this, [this]{ this->structureAnalysis->set_stereo("stereo_interlaced_columns_rl"); });
487+
connect(analysisActionProjectionInterlacedCheckerboardLr, &QAction::triggered, this, [this]{ this->structureAnalysis->set_stereo("stereo_interlaced_checkerboard_lr"); });
488+
connect(analysisActionProjectionInterlacedCheckerboardRl, &QAction::triggered, this, [this]{ this->structureAnalysis->set_stereo("stereo_interlaced_checkerboard_rl"); });
487489

488490
this->active_panel_timer_ = new QTimer(this);
489491
this->active_panel_timer_->setInterval(40);
@@ -543,8 +545,8 @@ void InterfaceWindow::set_active_panel(bool editor_active)
543545
this->anaglyph_widget->set_active_highlight(editor_active);
544546
}
545547

546-
if(this->geometryOptimization != nullptr && this->geometryOptimization->viewer() != nullptr) {
547-
auto *analysisAnaglyph = this->geometryOptimization->viewer()->get_anaglyph_widget();
548+
if(this->structureAnalysis != nullptr && this->structureAnalysis->viewer() != nullptr) {
549+
auto *analysisAnaglyph = this->structureAnalysis->viewer()->get_anaglyph_widget();
548550
if(analysisAnaglyph != nullptr) {
549551
analysisAnaglyph->set_active_highlight(!editor_active);
550552
}
@@ -627,7 +629,7 @@ void InterfaceWindow::open_file(const QString& filename)
627629

628630
// ---- Update analysis panels ----
629631
if (structures.size() == 1 && structures.front()->get_nr_eigenmodes() > 0) {
630-
geometryOptimization->set_frequency_structure(structures.front()->clone_for_view());
632+
structureAnalysis->set_frequency_structure(structures.front()->clone_for_view());
631633
} else {
632634
std::vector<std::shared_ptr<Structure>> geometry_structures;
633635
geometry_structures.reserve(structures.size());
@@ -636,7 +638,7 @@ void InterfaceWindow::open_file(const QString& filename)
636638
geometry_structures.push_back(s->clone_for_view());
637639
}
638640

639-
geometryOptimization->set_structures(geometry_structures);
641+
structureAnalysis->set_structures(geometry_structures);
640642
}
641643

642644
// ---- Also sync editor + info to first structure ----
@@ -717,7 +719,44 @@ void InterfaceWindow::open_analysis_file()
717719
return;
718720
}
719721

720-
geometryOptimization->load_file(filename);
722+
structureAnalysis->load_file(filename);
723+
}
724+
725+
void InterfaceWindow::open_analysis_neb_calculation()
726+
{
727+
const QString folder = QFileDialog::getExistingDirectory(
728+
this,
729+
tr("Open NEB calculation"),
730+
QString(),
731+
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks
732+
);
733+
734+
if(folder.isEmpty()) {
735+
return;
736+
}
737+
738+
NebCalculationLoader neb_loader;
739+
QString error_message;
740+
if(!neb_loader.load(folder, &error_message)) {
741+
QMessageBox message_box(this);
742+
message_box.setIcon(QMessageBox::Critical);
743+
message_box.setWindowTitle(tr("Could not open NEB calculation"));
744+
message_box.setText(tr("The selected folder is not a valid VASP NEB calculation."));
745+
message_box.setInformativeText(error_message);
746+
message_box.setStyleSheet("QLabel{min-width: 420px; font-weight: normal;}");
747+
message_box.exec();
748+
return;
749+
}
750+
751+
std::vector<std::shared_ptr<Structure>> geometry_structures;
752+
const auto& loaded_structures = neb_loader.structures();
753+
geometry_structures.reserve(loaded_structures.size());
754+
755+
for(const auto& structure : loaded_structures) {
756+
geometry_structures.push_back(structure->clone_for_view());
757+
}
758+
759+
structureAnalysis->set_structures(geometry_structures, StructureAnalysisViewer::SeriesKind::NEB);
721760
}
722761

723762
void InterfaceWindow::save_editor_file()
@@ -915,6 +954,6 @@ void InterfaceWindow::decrement_structure_stack_pointer() {
915954
*/
916955
void InterfaceWindow::load_structure_from_geometry_analysis() {
917956
this->anaglyph_widget->set_structure(
918-
this->geometryOptimization->viewer()->get_anaglyph_widget()->get_structure()->clone_for_view()
957+
this->structureAnalysis->viewer()->get_anaglyph_widget()->get_structure()->clone_for_view()
919958
);
920959
}

src/gui/interface_window.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@
3939
#include <QVector>
4040

4141
#include "anaglyph_widget.h"
42-
#include "analysis_geometry_optimization.h"
42+
#include "structure_analysis.h"
4343
#include "mainwindow.h"
4444
#include "../data/structure_loader.h"
45+
#include "../data/neb_calculation_loader.h"
4546
#include "structure_info_widget.h"
4647
#include "../data/structure_saver.h"
4748
#include "toolbar.h"
@@ -63,7 +64,7 @@ class InterfaceWindow : public QWidget {
6364
QLabel *interaction_label;
6465
QLabel *selection_label;
6566
StructureInfoWidget *structure_info_widget;
66-
AnalysisGeometryOptimization *geometryOptimization;
67+
StructureAnalysis *structureAnalysis;
6768

6869
ToolBarWidget *editor_toolbar;
6970
ToolBarWidget *analysis_toolbar;
@@ -124,6 +125,7 @@ public slots:
124125
void open_file(const QString& filename);
125126
void open_editor_file();
126127
void open_analysis_file();
128+
void open_analysis_neb_calculation();
127129

128130
/**
129131
* @brief Saves a file.

src/gui/mainwindow.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
#include <QStringList>
3737

3838
#include "interface_window.h"
39-
#include "analysis_geometry_optimization.h"
39+
#include "structure_analysis.h"
4040
#include "analysis_neb.h"
4141
#include "logwindow.h"
4242

0 commit comments

Comments
 (0)