From b172f97f52aba219da400f169475e935e715f096 Mon Sep 17 00:00:00 2001 From: Sergey Sozykin Date: Wed, 24 Apr 2024 21:45:31 +0500 Subject: [PATCH 1/9] Clusters to form --- src/core_atomistic/atomic_model.py | 51 ++++++++++++++++++---------- src/core_atomistic_qt/opengl_base.py | 6 ++-- src/models/capedswcnt.py | 8 ++--- src/qtbased/gui4dftform.py | 30 +++++++++++----- src/ui/form-v1.x.ui | 47 ++++++++++++++----------- src/ui/form.py | 29 +++++++++------- 6 files changed, 105 insertions(+), 66 deletions(-) diff --git a/src/core_atomistic/atomic_model.py b/src/core_atomistic/atomic_model.py index b17fd8a..9d6412f 100644 --- a/src/core_atomistic/atomic_model.py +++ b/src/core_atomistic/atomic_model.py @@ -130,7 +130,7 @@ def get_fragment_selected(self): def twist_z(self, alpha): cm = self.center_mass() self.move(-cm) - z0 = self.minZ() + z0 = self.min_z() z1 = np.linalg.norm(self.lat_vector3) for i in range(0, len(self.atoms)): @@ -182,15 +182,17 @@ def atoms_from_xyz_structure(number_of_atoms: int, ani_file, indexes=[0, 1, 2, 3 str2 = ani_file.readline() else: str2 = ani_file.readline() - atoms = [] + new_model = AtomicModel() mendeley = TPeriodTable() reg = re.compile('[^a-zA-Z ]') for i1 in range(0, number_of_atoms): str1 = helpers.spacedel(str2) s = str1.split(' ') - d1 = float(s[indexes[1]]) - d2 = float(s[indexes[2]]) - d3 = float(s[indexes[3]]) + xyz = np.array([s[indexes[1]], s[indexes[2]], s[indexes[3]]], dtype=float) + if max(indexes) < len(s) - 1: + tag = s[max(indexes) + 1] + else: + tag = "" c = s[indexes[0]] if c.isnumeric(): charge = int(c) @@ -199,10 +201,9 @@ def atoms_from_xyz_structure(number_of_atoms: int, ani_file, indexes=[0, 1, 2, 3 c = reg.sub('', s[indexes[0]]) charge = mendeley.get_charge_by_letter(c) if (charge > 0) or is_allow_charge_incorrect: - atoms.append([d1, d2, d3, c, charge]) + new_model.add_atom_with_data(xyz, charge, tag) if i1 < number_of_atoms -1: str2 = ani_file.readline() - new_model = AtomicModel(atoms) new_model.set_lat_vectors_default() return new_model @@ -395,6 +396,12 @@ def rotate(self, alpha, betta, gamma) -> None: self.rotate_y(betta) self.rotate_z(gamma) + def sub_model(self, inds): + new_model_atoms = [] + for i in inds: + new_model_atoms.append(self.atoms[i]) + return AtomicModel(new_model_atoms) + def projection_to_cylinder(self, atomslist, radius): """This method returns projections on cylinder with radius for atom at.""" row = [] @@ -490,7 +497,7 @@ def to_cif(self, f_name=""): cif_text += "\n\n\n\n#End data_GUI4dft_Data\n\n\n" return cif_text - def minX(self): + def min_x(self): """Minimum X-coordinate.""" minx = self.atoms[0].x for atom in self.atoms: @@ -498,7 +505,7 @@ def minX(self): minx = atom.x return float(minx) - def maxX(self): + def max_x(self): """Maximum X-coordinate.""" maxx = self.atoms[0].x for atom in self.atoms: @@ -508,9 +515,9 @@ def maxX(self): def size_x(self): """The length of the molecule along the X axis.""" - return self.maxX() - self.minX() + return self.max_x() - self.min_x() - def minY(self): + def min_y(self): """Minimum Y-coordinate.""" miny = self.atoms[0].y @@ -519,7 +526,7 @@ def minY(self): miny = atom.y return float(miny) - def maxY(self): + def max_y(self): """Maximum Y-coordinate.""" maxy = self.atoms[0].y @@ -530,9 +537,9 @@ def maxY(self): def size_y(self): """The length of the molecule along the Y axis.""" - return self.maxY() - self.minY() + return self.max_y() - self.min_y() - def minZ(self): + def min_z(self): """Minimum Z-coordinate.""" minz = self.atoms[0].z @@ -541,7 +548,7 @@ def minZ(self): minz = atom.z return float(minz) - def maxZ(self): + def max_z(self): """Maximum Z-coordinate.""" maxz = self.atoms[0].z @@ -552,7 +559,7 @@ def maxZ(self): def size_z(self): """The length of the molecule along the Z axis.""" - return self.maxZ() - self.minZ() + return self.max_z() - self.min_z() def sort_atoms_by_type(self): for i in range(0, self.n_atoms()): @@ -642,6 +649,16 @@ def find_clusters(self): clusters.append(cluster) return clusters + def find_clusters_by_tag(self): + clusters = [] + tags = np.array(self.get_tags(), dtype=int) + size = max(tags) + for i in range(0, size): + clusters.append([]) + for i in range(0, len(self.atoms)): + clusters[tags[i] - 1].append(i) + return clusters + def find_bonds_exact(self): """The method returns list of bonds of the molecule.""" if self.bonds_per: @@ -838,7 +855,7 @@ def go_to_positive_coordinates_translate(self): self.go_to_positive_array_translate(self.atoms) def go_to_positive_coordinates(self): - d_vec = np.array([self.minX(), self.minY(), self.minZ()]) + d_vec = np.array([self.min_x(), self.min_y(), self.min_z()]) self.move_array(self.atoms, d_vec) self.go_to_positive_array(self.atoms) diff --git a/src/core_atomistic_qt/opengl_base.py b/src/core_atomistic_qt/opengl_base.py index 128d50f..fddc5ac 100644 --- a/src/core_atomistic_qt/opengl_base.py +++ b/src/core_atomistic_qt/opengl_base.py @@ -773,9 +773,9 @@ def auto_zoom(self): if self.is_orthographic: self.scale_factor = aspect * 6.0 / model_size else: - x_max = self.main_model.maxX() - y_max = self.main_model.maxY() - z_max = self.main_model.maxZ() + x_max = self.main_model.max_x() + y_max = self.main_model.max_y() + z_max = self.main_model.max_z() rad = self.main_model.get_covalent_radii().max() h, w = self.height(), self.width() size = x_max + rad if h > w else y_max + rad diff --git a/src/models/capedswcnt.py b/src/models/capedswcnt.py index 0080015..76b4fd1 100644 --- a/src/models/capedswcnt.py +++ b/src/models/capedswcnt.py @@ -38,8 +38,8 @@ def __init__(self, n, m, leng, ncell, type, dist1, angle1, dist2, angle2): for i in range(0, size1): capatoms[i].z = -capatoms[i].z - minz = self.minZ() - maxz = capatoms.maxZ() + minz = self.min_z() + maxz = capatoms.max_z() capatoms.move(np.array([0, 0, minz - maxz - zaz])) capatoms.rotate_z(angle1) @@ -52,8 +52,8 @@ def __init__(self, n, m, leng, ncell, type, dist1, angle1, dist2, angle2): if not self.invert(): for i in range(0, size1): capatoms[i].z = -capatoms[i].z - maxz = self.maxZ() - minz = capatoms.minZ() + maxz = self.max_z() + minz = capatoms.min_z() zaz = dist2 diff --git a/src/qtbased/gui4dftform.py b/src/qtbased/gui4dftform.py index e213844..fcb3a4c 100644 --- a/src/qtbased/gui4dftform.py +++ b/src/qtbased/gui4dftform.py @@ -214,6 +214,7 @@ def setup_ui(self): # pragma: no cover self.ui.FormActionsPostButGetBonds.clicked.connect(self.get_bonds) self.ui.PropertyAtomAtomDistanceGet.clicked.connect(self.get_bond) self.ui.clusters_search.clicked.connect(self.clusters_search) + self.ui.clusters_from_tag.clicked.connect(self.clusters_from_tag) self.ui.FormStylesFor2DGraph.clicked.connect(self.set_2d_graph_styles) self.ui.FormModifyTwist.clicked.connect(self.twist_model) self.ui.model_move_by_vector.clicked.connect(self.move_model) @@ -956,10 +957,10 @@ def create_model_with_electrodes(self): model_scat.move(np.array([scat_move_x, scat_move_y, 0])) """ end: parts transformation""" - left_elec_max = model_left.maxZ() - left_bord = model_scat.minZ() + left_elec_max = model_left.max_z() + left_bord = model_scat.min_z() - right_elec_min = model_righ.minZ() + right_elec_min = model_righ.min_z() left_dist = self.ui.FormActionsPreSpinLeftElectrodeDist.value() right_dist = self.ui.FormActionsPreSpinRightElectrodeDist.value() @@ -968,7 +969,7 @@ def create_model_with_electrodes(self): model.add_atomic_model(model_left) model_scat.move(np.array([0, 0, -(left_bord - left_elec_max) + left_dist])) model.add_atomic_model(model_scat) - right_bord = model.maxZ() + right_bord = model.max_z() model_righ.move(np.array([0, 0, (right_bord - right_elec_min) + right_dist])) model.add_atomic_model(model_righ) @@ -1304,11 +1305,22 @@ def get_bond(self): # pragma: no cover def clusters_search(self): # pragma: no cover clusters = self.ui.openGLWidget.main_model.find_clusters() - print(len(clusters), "clusters") - for cluster in clusters: - print(len(cluster)) - - # self.ui.clusters_info.setText(str(len(clusters))) + self.cluster_data_to_form(clusters) + + def clusters_from_tag(self): + clusters = self.ui.openGLWidget.main_model.find_clusters_by_tag() + self.cluster_data_to_form(clusters) + + def cluster_data_to_form(self, clusters): + text = "Clusters: " + str(len(clusters)) + "\n" + for i in range(len(clusters)): + cluster = clusters[i] + if len(cluster) > 0: + text += "Cluster " + str(i + 1) + ": " + str(len(cluster)) + "\n" + m1 = self.ui.openGLWidget.main_model.sub_model(cluster) + text += "cm :" + str(m1.center_mass()) + "\n" + text += "size z :" + str(round(m1.max_z() - m1.min_z(), 4)) + "\n" + self.ui.clusters_info.setText(text) def fit_with(self): # pragma: no cover xf, yf, rf = self.models[self.active_model_id].fit_with_cylinder() diff --git a/src/ui/form-v1.x.ui b/src/ui/form-v1.x.ui index 5d63442..c21d1de 100644 --- a/src/ui/form-v1.x.ui +++ b/src/ui/form-v1.x.ui @@ -4004,19 +4004,6 @@ - - - - - 0 - 100 - - - - Сhange type of atoms - - - @@ -4374,8 +4361,8 @@ 0 0 - 98 - 28 + 100 + 30 @@ -10240,13 +10227,33 @@ - + Qt::Horizontal - 117 + 40 + 20 + + + + + + + + From tag + + + + + + + Qt::Horizontal + + + + 40 20 @@ -10260,13 +10267,13 @@ - + Qt::Horizontal - 117 + 40 20 @@ -10276,7 +10283,7 @@ - + diff --git a/src/ui/form.py b/src/ui/form.py index bc1b627..96de19d 100644 --- a/src/ui/form.py +++ b/src/ui/form.py @@ -2271,12 +2271,6 @@ def setupUi(self, MainWindow): self.verticalLayout_26.addWidget(self.groupBox_63) - self.groupBox_62 = QGroupBox(self.page_29) - self.groupBox_62.setObjectName(u"groupBox_62") - self.groupBox_62.setMinimumSize(QSize(0, 100)) - - self.verticalLayout_26.addWidget(self.groupBox_62) - self.verticalSpacer_31 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.verticalLayout_26.addItem(self.verticalSpacer_31) @@ -2451,7 +2445,7 @@ def setupUi(self, MainWindow): self.toolBox_6.addItem(self.page, u"Cell") self.page_9 = QWidget() self.page_9.setObjectName(u"page_9") - self.page_9.setGeometry(QRect(0, 0, 98, 28)) + self.page_9.setGeometry(QRect(0, 0, 100, 30)) self.FormModelComboModels_2 = QComboBox(self.page_9) self.FormModelComboModels_2.setObjectName(u"FormModelComboModels_2") self.FormModelComboModels_2.setGeometry(QRect(50, 10, 61, 22)) @@ -5532,23 +5526,32 @@ def setupUi(self, MainWindow): self.frame_185.setFrameShadow(QFrame.Raised) self.horizontalLayout_98 = QHBoxLayout(self.frame_185) self.horizontalLayout_98.setObjectName(u"horizontalLayout_98") - self.horizontalSpacer_71 = QSpacerItem(117, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer_131 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) - self.horizontalLayout_98.addItem(self.horizontalSpacer_71) + self.horizontalLayout_98.addItem(self.horizontalSpacer_131) + + self.clusters_from_tag = QPushButton(self.frame_185) + self.clusters_from_tag.setObjectName(u"clusters_from_tag") + + self.horizontalLayout_98.addWidget(self.clusters_from_tag) + + self.horizontalSpacer_72 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + + self.horizontalLayout_98.addItem(self.horizontalSpacer_72) self.clusters_search = QPushButton(self.frame_185) self.clusters_search.setObjectName(u"clusters_search") self.horizontalLayout_98.addWidget(self.clusters_search) - self.horizontalSpacer_72 = QSpacerItem(117, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer_71 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) - self.horizontalLayout_98.addItem(self.horizontalSpacer_72) + self.horizontalLayout_98.addItem(self.horizontalSpacer_71) self.verticalLayout_74.addWidget(self.frame_185) - self.clusters_info = QListWidget(self.tab_19) + self.clusters_info = QTextBrowser(self.tab_19) self.clusters_info.setObjectName(u"clusters_info") self.verticalLayout_74.addWidget(self.clusters_info) @@ -7062,7 +7065,6 @@ def retranslateUi(self, MainWindow): self.FormActionsPreButDeleteAtom.setText(QCoreApplication.translate("MainWindow", u"Delete", None)) self.FormActionsPreButModifyAtom.setText(QCoreApplication.translate("MainWindow", u"Modify", None)) self.FormActionsPreButAddAtom.setText(QCoreApplication.translate("MainWindow", u"Add", None)) - self.groupBox_62.setTitle(QCoreApplication.translate("MainWindow", u"\u0421hange type of atoms", None)) self.toolBox_6.setItemText(self.toolBox_6.indexOf(self.page_29), QCoreApplication.translate("MainWindow", u"Add or Modify Atom", None)) self.modify_cell_cart_coord.setText(QCoreApplication.translate("MainWindow", u"Edit", None)) self.modify_cell_frac_coord.setText(QCoreApplication.translate("MainWindow", u"Edit (fractional)", None)) @@ -7317,6 +7319,7 @@ def retranslateUi(self, MainWindow): self.label_130.setText(QCoreApplication.translate("MainWindow", u"Y label", None)) self.cell_parameter_label_y.setText(QCoreApplication.translate("MainWindow", u"Energy, eV", None)) self.tabWidget_6.setTabText(self.tabWidget_6.indexOf(self.tab_17), QCoreApplication.translate("MainWindow", u"Cell parameter", None)) + self.clusters_from_tag.setText(QCoreApplication.translate("MainWindow", u"From tag", None)) self.clusters_search.setText(QCoreApplication.translate("MainWindow", u"Search", None)) self.tabWidget_6.setTabText(self.tabWidget_6.indexOf(self.tab_19), QCoreApplication.translate("MainWindow", u"Clusters", None)) self.groupBox_54.setTitle(QCoreApplication.translate("MainWindow", u"Fit with", None)) From e6d371a2445eba0685b07a76f84c680df23bce0a Mon Sep 17 00:00:00 2001 From: Sergey Sozykin Date: Sat, 27 Apr 2024 14:55:49 +0500 Subject: [PATCH 2/9] Color with cluster ID --- .github/workflows/python-app.yml | 20 +++++++++--------- README.md | 4 +++- src/core_atomistic/atom.py | 1 + src/core_atomistic/atomic_model.py | 11 +++++++--- src/core_atomistic_qt/opengl_base.py | 14 +++++++++++-- src/qtbased/gui4dftform.py | 13 ++++++++++-- src/qtbased/guiopengl.py | 18 +++------------- src/ui/form-v1.x.ui | 21 +++++++++++++------ src/ui/form.py | 31 ++++++++++++++-------------- 9 files changed, 79 insertions(+), 54 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 63bc1e2..23e3140 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -20,10 +20,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: 3.10 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -42,10 +42,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: 3.10 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -61,10 +61,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: 3.10 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -91,10 +91,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: 3.10 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -110,10 +110,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: 3.10 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/README.md b/README.md index c73604c..e1bc7f8 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ GUI4dft (Graphical User Interface for support of Density Functional Theory calculations) - first free SIESTA oriented GUI. It is a cross-platform program. ## Install -GUI4DFT program is written in Python 3 (version >= 3.9). It has some dependences. To install the necessary modules, run in the terminal (in the ): +GUI4DFT program is written in Python 3 (version 3.9 or 3.10). It has some dependences. To install the necessary modules, run in the terminal (in the ): pip3 install -r ./requirements.txt @@ -36,6 +36,8 @@ v1.3 - This version uses pyqtgraph instead of matplotlib and pyside2 instead of v1.4 - This is the next release in 1.x branch. Pyside6. Added support for exporting structural data to input files VASP, CRYSTAL, QE. +v1.5 - Python 3.12. + The master branch contains more or less stable 1.x version functions. ## Code testing diff --git a/src/core_atomistic/atom.py b/src/core_atomistic/atom.py index 88f5071..c80c781 100644 --- a/src/core_atomistic/atom.py +++ b/src/core_atomistic/atom.py @@ -12,6 +12,7 @@ def __init__(self, at_data): self.xyz = np.array([at_data[0], at_data[1], at_data[2]]) self.let = at_data[3] self.charge = int(at_data[4]) + self.cluster: int = 0 self.is_visible: bool = True self.selected: bool = False self.active: bool = False diff --git a/src/core_atomistic/atomic_model.py b/src/core_atomistic/atomic_model.py index 9d6412f..c974eb0 100644 --- a/src/core_atomistic/atomic_model.py +++ b/src/core_atomistic/atomic_model.py @@ -46,13 +46,14 @@ def __init__(self, new_atoms: list = [], mendeley: TPeriodTable = None): def __getitem__(self, i): return self.atoms[i] - def add_atom_with_data(self, xyz: np.ndarray, charge: int, tag: str = "") -> None: + def add_atom_with_data(self, xyz: np.ndarray, charge: int, let: str = "", tag: str = "") -> None: """ xyz: coords charge: atomic number tag: additional information """ - let = self.mendeley.get_let(charge) + if let == "": + let = self.mendeley.get_let(charge) atom = Atom([*xyz, let, charge]) atom.tag = tag self.atoms.append(atom) @@ -201,7 +202,7 @@ def atoms_from_xyz_structure(number_of_atoms: int, ani_file, indexes=[0, 1, 2, 3 c = reg.sub('', s[indexes[0]]) charge = mendeley.get_charge_by_letter(c) if (charge > 0) or is_allow_charge_incorrect: - new_model.add_atom_with_data(xyz, charge, tag) + new_model.add_atom_with_data(xyz, charge, let=c, tag=tag) if i1 < number_of_atoms -1: str2 = ani_file.readline() new_model.set_lat_vectors_default() @@ -561,6 +562,10 @@ def size_z(self): """The length of the molecule along the Z axis.""" return self.max_z() - self.min_z() + def set_cluster(self, cluster, k): + for atom in cluster: + self.atoms[atom].cluster = k + def sort_atoms_by_type(self): for i in range(0, self.n_atoms()): for j in range(0, self.n_atoms() - i - 1): diff --git a/src/core_atomistic_qt/opengl_base.py b/src/core_atomistic_qt/opengl_base.py index fddc5ac..6b3ecf8 100644 --- a/src/core_atomistic_qt/opengl_base.py +++ b/src/core_atomistic_qt/opengl_base.py @@ -77,6 +77,13 @@ def __init__(self, parent=None): self.orientation_model_changed: Callable = None self.selected_atom_position: Callable = None self.selected_atom_callback: Callable = None + self.selected_atom_callback: Callable = None + + self.clusters_color = [np.array((1.0, 0.0, 0.0, 1.0), dtype=float), + np.array((0.0, 0.0, 1.0, 1.0), dtype=float), + np.array((0.0, 0.0, 1.0, 1.0), dtype=float), + np.array((1.0, 1.0, 0.0, 1.0), dtype=float), + np.array((1.0, 0.0, 1.0, 1.0), dtype=float)] def is_atom_selected(self): return self.selected_atom >= 0 @@ -416,7 +423,7 @@ def add_atoms(self): max_val = 0 mean_val = 0 - if (len(prop) > 0) and (prop != "charge"): + if (len(prop) > 0) and (prop != "charge") and (prop != "cluster"): min_val = self.main_model.atoms[0].properties[prop] max_val = self.main_model.atoms[0].properties[prop] mean_val = self.main_model.atoms[0].properties[prop] @@ -438,12 +445,15 @@ def add_atoms(self): rad_scale = 0.3 if not at.is_selected(): - if (len(prop) > 0) and (prop != "charge"): + if (len(prop) > 0) and (prop != "charge") and (prop != "cluster"): val = at.properties[prop] if val > mean_val: color = (color[0], math.fabs((val-mean_val)/(max_val-mean_val)), color[2], color[3]) else: color = (color[0], color[1], math.fabs((val-mean_val)/(min_val-mean_val)), color[3]) + elif prop == "cluster": + color = self.clusters_color[at.cluster] + pass else: color = self.color_of_atoms[at.charge] if self.selected_fragment_mode and at.fragment1: diff --git a/src/qtbased/gui4dftform.py b/src/qtbased/gui4dftform.py index fcb3a4c..7cb207b 100644 --- a/src/qtbased/gui4dftform.py +++ b/src/qtbased/gui4dftform.py @@ -120,8 +120,11 @@ def setup_ui(self): # pragma: no cover self.ui.FormModelComboModels.currentIndexChanged.connect(self.model_to_screen) self.ui.FormActionsPostTreeSurface.itemSelectionChanged.connect(self.type_of_surface) + + self.ui.color_atoms_with_atom_type.clicked.connect(self.color_atoms_with_property) self.ui.PropertyForColorOfAtom.currentIndexChanged.connect(self.color_atoms_with_property) - self.ui.ColorAtomsProperty.clicked.connect(self.color_atoms_with_property) + self.ui.color_atoms_with_property.clicked.connect(self.color_atoms_with_property) + self.ui.color_atoms_with_cluster_id.clicked.connect(self.color_atoms_with_property) self.ui.font_size_3d.valueChanged.connect(self.font_size_3d_changed) self.ui.property_shift_x.valueChanged.connect(self.property_position_changed) @@ -246,6 +249,8 @@ def setup_ui(self): # pragma: no cover self.ui.FormActionsButtonPlotDOS.clicked.connect(self.plot_dos) self.ui.FormActionsButtonClearDOS.clicked.connect(self.clear_dos) + self.ui.FormActionsPostButPlotBondsHistogram.clicked.connect(self.plot_bonds_histogram) + self.ui.FormActionsPostButPlusCellParam.clicked.connect(self.add_cell_param) self.ui.FormActionsPostButAddRowCellParam.clicked.connect(self.add_cell_param_row) self.ui.cell_param_delete_row.clicked.connect(self.delete_cell_param_row) @@ -1316,11 +1321,13 @@ def cluster_data_to_form(self, clusters): for i in range(len(clusters)): cluster = clusters[i] if len(cluster) > 0: + self.models[self.active_model_id].set_cluster(cluster, i) text += "Cluster " + str(i + 1) + ": " + str(len(cluster)) + "\n" m1 = self.ui.openGLWidget.main_model.sub_model(cluster) text += "cm :" + str(m1.center_mass()) + "\n" text += "size z :" + str(round(m1.max_z() - m1.min_z(), 4)) + "\n" self.ui.clusters_info.setText(text) + self.plot_model(self.active_model_id) def fit_with(self): # pragma: no cover xf, yf, rf = self.models[self.active_model_id].fit_with_cylinder() @@ -1684,7 +1691,9 @@ def show_property_enabling(self): # pragma: no cover self.ui.PropertyForColorOfAtom.setModel(atom_prop_type) def color_atoms_with_property(self): # pragma: no cover - if self.ui.ColorAtomsProperty.isChecked(): + if self.ui.color_atoms_with_cluster_id.isChecked(): + self.ui.openGLWidget.color_atoms_with_property("cluster") + elif self.ui.color_atoms_with_property.isChecked(): prop = self.ui.PropertyForColorOfAtom.currentText() if len(prop) > 0: self.ui.openGLWidget.color_atoms_with_property(prop) diff --git a/src/qtbased/guiopengl.py b/src/qtbased/guiopengl.py index 62658e4..a2a1306 100644 --- a/src/qtbased/guiopengl.py +++ b/src/qtbased/guiopengl.py @@ -113,31 +113,19 @@ def set_atomic_structure(self, structure, atoms_colors, is_view_atoms, is_view_a is_view_bonds, bonds_color, bond_width, bonds_by_atoms, is_view_axes, axes_color, contour_width): self.clean() - self.prop = "charge" self.selected_atom = -1 self.main_model = deepcopy(structure) self.coord0 = -self.main_model.get_center_of_mass() self.main_model.move(self.coord0) - self.is_view_box = is_view_box - self.is_view_atoms = is_view_atoms - self.is_atomic_numbers_visible = is_view_atom_numbers - self.is_view_bonds = is_view_bonds - self.color_of_bonds_by_atoms = bonds_by_atoms - self.bond_width = bond_width - self.is_view_axes = is_view_axes - self.color_of_axes = axes_color self.is_view_surface = False self.is_view_contour = False self.is_view_contour_fill = False self.active = False - self.color_of_atoms = atoms_colors self.auto_zoom() - self.color_of_bonds = bonds_color - self.color_of_box = box_color self.main_model.find_bonds_fast() - self.contour_width = contour_width - self.add_all_elements() - self.update() + self.set_structure_parameters(atoms_colors, is_view_atoms, is_view_atom_numbers, is_view_box, box_color, + is_view_bonds, bonds_color, bond_width, bonds_by_atoms, is_view_axes, axes_color, + contour_width) def auto_zoom(self): super().auto_zoom() diff --git a/src/ui/form-v1.x.ui b/src/ui/form-v1.x.ui index c21d1de..8fb0641 100644 --- a/src/ui/form-v1.x.ui +++ b/src/ui/form-v1.x.ui @@ -4361,8 +4361,8 @@ 0 0 - 100 - 30 + 98 + 28 @@ -10922,7 +10922,7 @@ 0 - 50 + 0 @@ -10980,9 +10980,15 @@ 0 + + 0 + 0 + + 0 + @@ -11280,21 +11286,24 @@ - + atom type + + true + - + property - + cluster ID diff --git a/src/ui/form.py b/src/ui/form.py index 96de19d..679af51 100644 --- a/src/ui/form.py +++ b/src/ui/form.py @@ -2445,7 +2445,7 @@ def setupUi(self, MainWindow): self.toolBox_6.addItem(self.page, u"Cell") self.page_9 = QWidget() self.page_9.setObjectName(u"page_9") - self.page_9.setGeometry(QRect(0, 0, 100, 30)) + self.page_9.setGeometry(QRect(0, 0, 98, 28)) self.FormModelComboModels_2 = QComboBox(self.page_9) self.FormModelComboModels_2.setObjectName(u"FormModelComboModels_2") self.FormModelComboModels_2.setGeometry(QRect(50, 10, 61, 22)) @@ -5853,7 +5853,7 @@ def setupUi(self, MainWindow): self.frame_114 = QFrame(self.groupBox) self.frame_114.setObjectName(u"frame_114") - self.frame_114.setMinimumSize(QSize(0, 50)) + self.frame_114.setMinimumSize(QSize(0, 0)) self.frame_114.setFrameShape(QFrame.NoFrame) self.frame_114.setFrameShadow(QFrame.Raised) self.horizontalLayout_116 = QHBoxLayout(self.frame_114) @@ -5885,7 +5885,7 @@ def setupUi(self, MainWindow): self.frame_106.setFrameShadow(QFrame.Raised) self.horizontalLayout_110 = QHBoxLayout(self.frame_106) self.horizontalLayout_110.setObjectName(u"horizontalLayout_110") - self.horizontalLayout_110.setContentsMargins(0, -1, 0, -1) + self.horizontalLayout_110.setContentsMargins(0, 0, 0, 0) self.label_2 = QLabel(self.frame_106) self.label_2.setObjectName(u"label_2") @@ -6058,20 +6058,21 @@ def setupUi(self, MainWindow): self.frame_11.setFrameShadow(QFrame.Raised) self.verticalLayout_10 = QVBoxLayout(self.frame_11) self.verticalLayout_10.setObjectName(u"verticalLayout_10") - self.radioButton = QRadioButton(self.frame_11) - self.radioButton.setObjectName(u"radioButton") + self.color_atoms_with_atom_type = QRadioButton(self.frame_11) + self.color_atoms_with_atom_type.setObjectName(u"color_atoms_with_atom_type") + self.color_atoms_with_atom_type.setChecked(True) - self.verticalLayout_10.addWidget(self.radioButton) + self.verticalLayout_10.addWidget(self.color_atoms_with_atom_type) - self.ColorAtomsProperty = QRadioButton(self.frame_11) - self.ColorAtomsProperty.setObjectName(u"ColorAtomsProperty") + self.color_atoms_with_property = QRadioButton(self.frame_11) + self.color_atoms_with_property.setObjectName(u"color_atoms_with_property") - self.verticalLayout_10.addWidget(self.ColorAtomsProperty) + self.verticalLayout_10.addWidget(self.color_atoms_with_property) - self.radioButton_5 = QRadioButton(self.frame_11) - self.radioButton_5.setObjectName(u"radioButton_5") + self.color_atoms_with_cluster_id = QRadioButton(self.frame_11) + self.color_atoms_with_cluster_id.setObjectName(u"color_atoms_with_cluster_id") - self.verticalLayout_10.addWidget(self.radioButton_5) + self.verticalLayout_10.addWidget(self.color_atoms_with_cluster_id) self.horizontalLayout_97.addWidget(self.frame_11) @@ -7357,9 +7358,9 @@ def retranslateUi(self, MainWindow): self.groupBox_34.setTitle(QCoreApplication.translate("MainWindow", u"OpenGl", None)) self.OpenGL_GL_CULL_FACE.setText(QCoreApplication.translate("MainWindow", u"GL_CULL_FACE", None)) self.groupBox_60.setTitle(QCoreApplication.translate("MainWindow", u"Color atoms according to", None)) - self.radioButton.setText(QCoreApplication.translate("MainWindow", u"atom type", None)) - self.ColorAtomsProperty.setText(QCoreApplication.translate("MainWindow", u"property", None)) - self.radioButton_5.setText(QCoreApplication.translate("MainWindow", u"cluster ID", None)) + self.color_atoms_with_atom_type.setText(QCoreApplication.translate("MainWindow", u"atom type", None)) + self.color_atoms_with_property.setText(QCoreApplication.translate("MainWindow", u"property", None)) + self.color_atoms_with_cluster_id.setText(QCoreApplication.translate("MainWindow", u"cluster ID", None)) self.show_property_text.setText(QCoreApplication.translate("MainWindow", u"Show property", None)) self.tabWidget_9.setTabText(self.tabWidget_9.indexOf(self.tab_32), QCoreApplication.translate("MainWindow", u"View 3D", None)) self.groupBox_9.setTitle(QCoreApplication.translate("MainWindow", u"Colors", None)) From 28f5397e56902373e0c2a69f8d91b80068ce24a0 Mon Sep 17 00:00:00 2001 From: Sozykin Sergey Date: Mon, 13 May 2024 00:02:31 +0500 Subject: [PATCH 3/9] QE: band structure --- src/program/importer_exporter.py | 8 +- src/program/qe.py | 19 +++++ src/program/siesta.py | 53 +++++++++++-- src/qtbased/gui4dftform.py | 109 ++++++++++++++++++++------ src/ui/form-v1.x.ui | 60 ++++++++++++-- src/ui/form.py | 48 ++++++++++-- src/utils/electronic_prop_reader.py | 25 +++--- tests/gui4dft/test_electronic_prop.py | 10 +-- 8 files changed, 268 insertions(+), 64 deletions(-) diff --git a/src/program/importer_exporter.py b/src/program/importer_exporter.py index c92e137..be6d01e 100644 --- a/src/program/importer_exporter.py +++ b/src/program/importer_exporter.py @@ -135,8 +135,12 @@ def check_pdos_file(filename): @staticmethod def check_bands_file(filename): """Check PDOS file for fdf/out filename.""" - system_label = TSIESTA.system_label(filename) - file = os.path.dirname(filename) + "/" + str(system_label) + ".bands" + if helpers.check_format(filename) == "siesta_out": + system_label = TSIESTA.system_label(filename) + file = os.path.dirname(filename) + "/" + str(system_label) + ".bands" + elif helpers.check_format(filename) == "QEPWout": + file = os.path.dirname(filename) + "/Bandx.dat.gnu" + if os.path.exists(file): return file else: diff --git a/src/program/qe.py b/src/program/qe.py index bcb6c50..ead4a85 100644 --- a/src/program/qe.py +++ b/src/program/qe.py @@ -9,6 +9,19 @@ from core_atomistic.atomic_model import AtomicModel +class TQE: + + @staticmethod + def gnuplot_bands_reader(file): + data = np.loadtxt(file) + k = np.unique(data[:, 0]) + kmin, kmax = min(k), max(k) + bands = np.reshape(data[:, 1], (-1, len(k))) + emax, emin = np.max(bands), np.min(bands) + nspins = 1 + return emax, emin, kmax, kmin, nspins + + def vectors_from_pwout(f_name): """Lattice vectors from output file of PWscf. f_name: PWscf output file @@ -236,6 +249,12 @@ def get_positions(f, vectors): return model, str1 +def get_fermi_level(f): + e_fermi = helpers.from_file_property(f, "the Fermi energy is", prop_type='float') + # "the Fermi energy is 4.9640 ev" + return e_fermi + + def model_to_qe_pw(model: AtomicModel): types = model.types_of_atoms() text = "&system\n" diff --git a/src/program/siesta.py b/src/program/siesta.py index d1cfdef..a6605b8 100644 --- a/src/program/siesta.py +++ b/src/program/siesta.py @@ -74,6 +74,52 @@ def calc_pdos(file, atom_index, species, number_l, number_m, number_n, number_z) pdos += data return pdos, energy + @staticmethod + def siesta_bands_reader(file): + f = open(file) + e_fermi = float(f.readline()) + str1 = f.readline().split() + str1 = helpers.list_str_to_float(str1) + kmin, kmax = float(str1[0]), float(str1[1]) + str1 = f.readline().split() + str1 = helpers.list_str_to_float(str1) + emin = float(str1[0]) - e_fermi + emax = float(str1[1]) - e_fermi + str1 = f.readline().split() + str1 = helpers.list_str_to_int(str1) + nspins = float(str1[1]) + f.close() + return emax, emin, kmax, kmin, nspins + + @staticmethod + def read_siesta_bands_xlabels(file, k_max, k_min): + f = open(file) + f.readline() + f.readline() + f.readline() + str1 = f.readline().split() + str1 = helpers.list_str_to_int(str1) + n_bands, n_spins = int(str1[0]), int(str1[1]) + n_k_points = int(str1[2]) + for i in range(0, n_k_points): + str2 = f.readline().split() + kol = len(str2) - 1 + while kol < n_bands * n_spins: + str2 = f.readline().split() + kol += len(str2) + n_sticks = int(f.readline()) + x_ticks = [] + x_tick_labels = [] + for i in range(0, n_sticks): + str3 = f.readline().split() + value = float(str3[0]) + if (round(value, 2) >= k_min) and (round(value, 2) <= k_max): + x_ticks.append(value) + letter = helpers.utf8_letter(str3[1][1:-1]) + x_tick_labels.append(letter) + f.close() + return x_tick_labels, x_ticks + @staticmethod def get_charges_for_atoms(filename, method): if os.path.exists(filename): @@ -503,8 +549,6 @@ def atoms_from_fdf_prepare(filename): def atoms_from_fdf_text(atomic_coordinates_format, number_of_atoms, chem_spec_info, lat, lat_vectors, lines, units): lines = helpers.clear_fdf_lines(lines) all_atoms = AtomicModel() - at_list = [] - at_list1 = [] i = 0 is_block_atomic_coordinates = False is_block_z_matrix = False @@ -512,7 +556,6 @@ def atoms_from_fdf_text(atomic_coordinates_format, number_of_atoms, chem_spec_in if lines[i].find("%block Zmatrix") >= 0: is_block_z_matrix = True i += 1 - at_list = [] if lines[i].find("cartesian") >= 0: for j in range(0, number_of_atoms): i += 1 @@ -520,7 +563,7 @@ def atoms_from_fdf_text(atomic_coordinates_format, number_of_atoms, chem_spec_in xyz = np.array([float(atom_full[1]), float(atom_full[2]), float(atom_full[3])]) charge = chem_spec_info[str(atom_full[0])][0] tag = chem_spec_info[str(atom_full[0])][2] - all_atoms.add_atom_with_data(xyz, charge, tag) + all_atoms.add_atom_with_data(xyz, charge, tag=tag) if lines[i].find("%block AtomicCoordinatesAndAtomicSpecies") >= 0: is_block_atomic_coordinates = True mult = 1 @@ -532,7 +575,7 @@ def atoms_from_fdf_text(atomic_coordinates_format, number_of_atoms, chem_spec_in xyz = np.array([mult * float(atom_full[0]), mult * float(atom_full[1]), mult * float(atom_full[2])]) charge = chem_spec_info[str(atom_full[3])][0] tag = chem_spec_info[str(atom_full[3])][2] - all_atoms.add_atom_with_data(xyz, charge, tag) + all_atoms.add_atom_with_data(xyz, charge, tag=tag) i += 1 if lat_vectors is None: all_atoms.set_lat_vectors_default() diff --git a/src/qtbased/gui4dftform.py b/src/qtbased/gui4dftform.py index 7cb207b..a558d17 100644 --- a/src/qtbased/gui4dftform.py +++ b/src/qtbased/gui4dftform.py @@ -39,7 +39,7 @@ from program.xsf import XSF from qtbased.image3dexporter import Image3Dexporter from program.siesta import TSIESTA -from program.qe import model_to_qe_pw, alats_from_pwout +from program.qe import TQE, model_to_qe_pw, alats_from_pwout, get_fermi_level from program.qe import energy_tot as qe_energy_tot from program.qe import volume as qe_volume from program.wien import model_to_wien_struct @@ -53,7 +53,7 @@ from utils.calculators import Calculators as Calculator from utils.calculators import gaps from program.importer_exporter import ImporterExporter -from utils.electronic_prop_reader import read_siesta_bands, dos_from_file +from utils.electronic_prop_reader import read_siesta_bands, dos_from_file, siesta_homo_lumo from ui.about import Ui_DialogAbout as Ui_about from ui.form import Ui_MainWindow as Ui_form @@ -252,11 +252,14 @@ def setup_ui(self): # pragma: no cover self.ui.FormActionsPostButPlotBondsHistogram.clicked.connect(self.plot_bonds_histogram) self.ui.FormActionsPostButPlusCellParam.clicked.connect(self.add_cell_param) - self.ui.FormActionsPostButAddRowCellParam.clicked.connect(self.add_cell_param_row) + self.ui.add_cell_param_row.clicked.connect(self.add_cell_param_row) self.ui.cell_param_delete_row.clicked.connect(self.delete_cell_param_row) self.ui.cell_param_delete_all.clicked.connect(self.delete_cell_param_all) self.ui.cell_params_file_add.clicked.connect(self.add_data_cell_param) + self.ui.add_kpoints_row.clicked.connect(self.add_kpoints_row) + self.ui.delete_kpoints_row.clicked.connect(self.delete_k_point_row) + self.ui.FormModifyRotation.clicked.connect(self.model_rotation) self.ui.FormModifyGrowX.clicked.connect(self.model_grow_x) self.ui.FormModifyGrowY.clicked.connect(self.model_grow_y) @@ -399,6 +402,7 @@ def setup_ui(self): # pragma: no cover self.ui.FormNanotypeTypeSelector.setCurrentIndex(0) self.prepare_cell_param_table() + self.prepare_k_points_table() args_cell = QStandardItemModel() args_cell.appendRow(QStandardItem("V")) @@ -445,7 +449,16 @@ def prepare_cell_param_table(self): self.ui.FormActionsPostTableCellParam.horizontalHeader().setStyleSheet(self.table_header_stylesheet) self.ui.FormActionsPostTableCellParam.verticalHeader().setStyleSheet(self.table_header_stylesheet) - def q_standard_item_model_init(self, data: list): + def prepare_k_points_table(self): + self.ui.high_symmetry_k_points.setColumnCount(2) + self.ui.high_symmetry_k_points.setHorizontalHeaderLabels(["x", "letter"]) + self.ui.high_symmetry_k_points.setColumnWidth(0, 160) + self.ui.high_symmetry_k_points.setColumnWidth(1, 150) + self.ui.high_symmetry_k_points.horizontalHeader().setStyleSheet(self.table_header_stylesheet) + self.ui.high_symmetry_k_points.verticalHeader().setStyleSheet(self.table_header_stylesheet) + + @staticmethod + def q_standard_item_model_init(data: list): model_type = QStandardItemModel() for row in data: model_type.appendRow(QStandardItem(row)) @@ -578,6 +591,9 @@ def add_cell_param(self): def active_model(self) -> AtomicModel: return self.models[self.active_model_id] + def add_kpoints_row(self): + self.ui.high_symmetry_k_points.setRowCount(self.ui.high_symmetry_k_points.rowCount() + 1) + def add_cell_param_row(self): self.ui.FormActionsPostTableCellParam.setRowCount(self.ui.FormActionsPostTableCellParam.rowCount() + 1) @@ -842,7 +858,6 @@ def check_dos(self, f_name: str) -> None: # pragma: no cover q_tab_widg.setToolTip(dos_file) self.ui.FormActionsTabeDOSProperty.setItem(i - 1, 0, q_tab_widg) self.ui.FormActionsTabeDOSProperty.setItem(i - 1, 1, QTableWidgetItem(str(e_fermy))) - # self.ui.FormActionsTabeDOSProperty.update() def check_volumeric_data(self, file_name): files = [] @@ -987,6 +1002,9 @@ def create_model_with_electrodes(self): def colors_of_atoms(self): return self.periodic_table.get_all_colors() + def delete_k_point_row(self): + self.ui.high_symmetry_k_points.removeRow(self.ui.high_symmetry_k_points.currentRow()) + def delete_cell_param_row(self): self.ui.FormActionsPostTableCellParam.removeRow(self.ui.FormActionsPostTableCellParam.currentRow()) @@ -1113,6 +1131,16 @@ def fill_gui(self, title=""): energies = crystal.energies(file_name) self.fill_energies(energies) + if helpers.check_format(file_name) == "QEPWout": + self.check_bands(file_name) + + e_fermi = get_fermi_level(file_name) + # print(e_fermi) + if e_fermi: + i = self.ui.FormActionsTabeDOSProperty.rowCount() + 1 + self.ui.FormActionsTabeDOSProperty.setRowCount(i) + self.ui.FormActionsTabeDOSProperty.setItem(i - 1, 1, QTableWidgetItem(str(e_fermi))) + def fill_energies(self, energies: list[float]) -> None: """Plot energies for steps of output.""" if len(energies) == 0: @@ -2238,24 +2266,20 @@ def list_of_selected_items_in_combo(atom_index, combo): def parse_bands(self): file = self.ui.FormActionsLineBANDSfile.text() if os.path.exists(file): - f = open(file) - e_fermi = float(f.readline()) - str1 = f.readline().split() - str1 = helpers.list_str_to_float(str1) - kmin, kmax = float(str1[0]), float(str1[1]) - self.ui.spin_bands_xmin.setRange(kmin, kmax) - self.ui.spin_bands_xmin.setValue(kmin) - self.ui.spin_bands_xmax.setRange(kmin, kmax) - self.ui.spin_bands_xmax.setValue(kmax) + if not (file.endswith(".bands") or file.endswith(".dat.gnu")): + return + + if file.endswith(".bands"): + emax, emin, kmax, kmin, nspins = TSIESTA.siesta_bands_reader(file) + xticklabels, xticks = TSIESTA.read_siesta_bands_xlabels(file, kmax, kmin) + for tick, label in zip(xticks, xticklabels): + self.ui.high_symmetry_k_points.setRowCount(self.ui.high_symmetry_k_points.rowCount() + 1) + n = self.ui.high_symmetry_k_points.rowCount() - 1 + self.ui.high_symmetry_k_points.setItem(n, 0, QTableWidgetItem(str(tick))) + self.ui.high_symmetry_k_points.setItem(n, 1, QTableWidgetItem(label)) + elif file.endswith(".dat.gnu"): + emax, emin, kmax, kmin, nspins = TQE.gnuplot_bands_reader(file) - str1 = f.readline().split() - str1 = helpers.list_str_to_float(str1) - emin = float(str1[0]) - e_fermi - emax = float(str1[1]) - e_fermi - str1 = f.readline().split() - f.close() - str1 = helpers.list_str_to_int(str1) - nspins = float(str1[1]) if nspins == 2: self.ui.FormActionsGrBoxBANDSspin.setEnabled(True) else: @@ -2263,6 +2287,11 @@ def parse_bands(self): e_min_form = -2 if emin < -2 else emin e_max_form = 2 if emax > 2 else emax + + self.ui.spin_bands_xmin.setRange(kmin, kmax) + self.ui.spin_bands_xmin.setValue(kmin) + self.ui.spin_bands_xmax.setRange(kmin, kmax) + self.ui.spin_bands_xmax.setValue(kmax) self.ui.spin_bands_emin.setRange(emin, emax) self.ui.spin_bands_emin.setValue(e_min_form) self.ui.spin_bands_emax.setRange(emin, emax) @@ -2282,8 +2311,16 @@ def plot_bands(self): title = self.ui.bands_title.text() if os.path.exists(file): - if self.ui.bands_spin_up.isChecked() or self.ui.bands_spin_up_down.isChecked(): - bands, emaxf, eminf, homo, kmesh, lumo, xticklabels, xticks = read_siesta_bands(file, True, kmax, kmin) + format = helpers.check_format(self.filename) + updown = self.ui.bands_spin_up_down.isChecked() + + xticklabels, xticks = [], [] + for index in range(self.ui.high_symmetry_k_points.rowCount()): + xticks.append(float(self.ui.high_symmetry_k_points.item(index, 0).text())) + xticklabels.append(self.ui.high_symmetry_k_points.item(index, 1).text()) + + if (format == "siesta_out") and (self.ui.bands_spin_up.isChecked() or updown): + bands, emaxf, eminf, kmesh = read_siesta_bands(file, True, kmax, kmin) b_mins = np.min(bands, 1) b_maxs = np.max(bands, 1) inds = np.zeros(len(bands), dtype=int) @@ -2292,8 +2329,8 @@ def plot_bands(self): inds[i] = i self.ui.PyqtGraphWidget.plot([kmesh], bands[inds], [None], title, x_title, y_title, False) - if self.ui.bands_spin_down.isChecked() or self.ui.bands_spin_up_down.isChecked(): - bands, emaxf, eminf, homo, kmesh, lumo, xticklabels, xticks = read_siesta_bands(file, False, kmax, kmin) + if (format == "siesta_out") and (self.ui.bands_spin_down.isChecked() or updown): + bands, emaxf, eminf, kmesh = read_siesta_bands(file, False, kmax, kmin) b_mins = np.min(bands, 1) b_maxs = np.max(bands, 1) inds = np.zeros(len(bands), dtype=int) @@ -2306,6 +2343,16 @@ def plot_bands(self): self.ui.PyqtGraphWidget.plot([kmesh], bands[inds], [None], title, x_title, y_title, False, _style=Qt.DotLine) + if (format == "QEPWout") and (self.ui.bands_spin_up.isChecked() or updown): + bands, emaxf, eminf, kmesh = self.read_qe_bands(file) # , kmax, kmin) + b_mins = np.min(bands, 1) + b_maxs = np.max(bands, 1) + inds = np.zeros(len(bands), dtype=int) + for i in range(len(bands)): + if (b_mins[i] >= emin) and (b_mins[i] <= emax) or (b_maxs[i] >= emin) and (b_maxs[i] <= emax): + inds[i] = i + self.ui.PyqtGraphWidget.plot([kmesh], bands[inds], [None], title, x_title, y_title, False) + major_tick = [] for index in range(len(xticks)): self.ui.PyqtGraphWidget.add_line(xticks[index], 90, 2, Qt.DashLine) @@ -2315,11 +2362,21 @@ def plot_bands(self): if self.ui.FormActionsCheckBANDSfermyShow.isChecked(): self.ui.PyqtGraphWidget.add_line(0, 0, 2, Qt.SolidLine) + homo, lumo = siesta_homo_lumo(bands, emax, emin) gap, gap_ind = gaps(bands, emaxf, eminf, homo, lumo) self.ui.FormActionsLabelBANDSgap.setText( "Band gap = " + str(round(gap, 3)) + " " + "Indirect gap = " + str(round(gap_ind, 3))) + def read_qe_bands(self, file): + e_fermi = float(self.ui.FormActionsTabeDOSProperty.item(0, 1).text()) + data = np.loadtxt(file) + kmesh = np.unique(data[:, 0]) + bands = np.reshape(data[:, 1], (-1, len(kmesh))) + bands -= e_fermi + emaxf, eminf = np.max(bands), np.min(bands) + return bands, emaxf, eminf, kmesh + def plot_dos(self): self.ui.PyqtGraphWidget.set_xticks(None) self.ui.Form3Dand2DTabs.setCurrentIndex(1) diff --git a/src/ui/form-v1.x.ui b/src/ui/form-v1.x.ui index 8fb0641..5c23020 100644 --- a/src/ui/form-v1.x.ui +++ b/src/ui/form-v1.x.ui @@ -3658,8 +3658,8 @@ 0 0 - 371 - 593 + 363 + 329 @@ -4361,8 +4361,8 @@ 0 0 - 98 - 28 + 100 + 30 @@ -6116,6 +6116,56 @@ + + + + + 0 + 100 + + + + High-symmetry point + + + + + + + + + + 0 + 50 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + Add Row + + + + + + + Delete Selected + + + + + + + + + @@ -9895,7 +9945,7 @@ 0 - + Add Row diff --git a/src/ui/form.py b/src/ui/form.py index 679af51..0d38dd5 100644 --- a/src/ui/form.py +++ b/src/ui/form.py @@ -2093,7 +2093,7 @@ def setupUi(self, MainWindow): self.toolBox_6.setSizePolicy(sizePolicy2) self.page_29 = QWidget() self.page_29.setObjectName(u"page_29") - self.page_29.setGeometry(QRect(0, 0, 371, 593)) + self.page_29.setGeometry(QRect(0, 0, 363, 329)) self.verticalLayout_26 = QVBoxLayout(self.page_29) self.verticalLayout_26.setObjectName(u"verticalLayout_26") self.groupBox_63 = QGroupBox(self.page_29) @@ -2445,7 +2445,7 @@ def setupUi(self, MainWindow): self.toolBox_6.addItem(self.page, u"Cell") self.page_9 = QWidget() self.page_9.setObjectName(u"page_9") - self.page_9.setGeometry(QRect(0, 0, 98, 28)) + self.page_9.setGeometry(QRect(0, 0, 100, 30)) self.FormModelComboModels_2 = QComboBox(self.page_9) self.FormModelComboModels_2.setObjectName(u"FormModelComboModels_2") self.FormModelComboModels_2.setGeometry(QRect(50, 10, 61, 22)) @@ -3331,6 +3331,39 @@ def setupUi(self, MainWindow): self.verticalLayout_7.addWidget(self.FormActionsGrBoxBANDSspin) + self.groupBox_61 = QGroupBox(self.tab_18) + self.groupBox_61.setObjectName(u"groupBox_61") + self.groupBox_61.setMinimumSize(QSize(0, 100)) + self.verticalLayout_75 = QVBoxLayout(self.groupBox_61) + self.verticalLayout_75.setObjectName(u"verticalLayout_75") + self.high_symmetry_k_points = QTableWidget(self.groupBox_61) + self.high_symmetry_k_points.setObjectName(u"high_symmetry_k_points") + + self.verticalLayout_75.addWidget(self.high_symmetry_k_points) + + self.frame_186 = QFrame(self.groupBox_61) + self.frame_186.setObjectName(u"frame_186") + self.frame_186.setMinimumSize(QSize(0, 50)) + self.frame_186.setFrameShape(QFrame.NoFrame) + self.frame_186.setFrameShadow(QFrame.Raised) + self.horizontalLayout_99 = QHBoxLayout(self.frame_186) + self.horizontalLayout_99.setObjectName(u"horizontalLayout_99") + self.add_kpoints_row = QPushButton(self.frame_186) + self.add_kpoints_row.setObjectName(u"add_kpoints_row") + + self.horizontalLayout_99.addWidget(self.add_kpoints_row) + + self.delete_kpoints_row = QPushButton(self.frame_186) + self.delete_kpoints_row.setObjectName(u"delete_kpoints_row") + + self.horizontalLayout_99.addWidget(self.delete_kpoints_row) + + + self.verticalLayout_75.addWidget(self.frame_186) + + + self.verticalLayout_7.addWidget(self.groupBox_61) + self.groupBox_48 = QGroupBox(self.tab_18) self.groupBox_48.setObjectName(u"groupBox_48") self.groupBox_48.setMinimumSize(QSize(0, 0)) @@ -5358,10 +5391,10 @@ def setupUi(self, MainWindow): self.horizontalLayout_15 = QHBoxLayout(self.frame) self.horizontalLayout_15.setObjectName(u"horizontalLayout_15") self.horizontalLayout_15.setContentsMargins(0, 0, 0, 0) - self.FormActionsPostButAddRowCellParam = QPushButton(self.frame) - self.FormActionsPostButAddRowCellParam.setObjectName(u"FormActionsPostButAddRowCellParam") + self.add_cell_param_row = QPushButton(self.frame) + self.add_cell_param_row.setObjectName(u"add_cell_param_row") - self.horizontalLayout_15.addWidget(self.FormActionsPostButAddRowCellParam) + self.horizontalLayout_15.addWidget(self.add_cell_param_row) self.FormActionsPostButPlusCellParam = QPushButton(self.frame) self.FormActionsPostButPlusCellParam.setObjectName(u"FormActionsPostButPlusCellParam") @@ -7136,6 +7169,9 @@ def retranslateUi(self, MainWindow): self.bands_spin_up.setText(QCoreApplication.translate("MainWindow", u"Up", None)) self.bands_spin_down.setText(QCoreApplication.translate("MainWindow", u"Down", None)) self.bands_spin_up_down.setText(QCoreApplication.translate("MainWindow", u"Up+Down", None)) + self.groupBox_61.setTitle(QCoreApplication.translate("MainWindow", u"High-symmetry point", None)) + self.add_kpoints_row.setText(QCoreApplication.translate("MainWindow", u"Add Row", None)) + self.delete_kpoints_row.setText(QCoreApplication.translate("MainWindow", u"Delete Selected", None)) self.groupBox_48.setTitle(QCoreApplication.translate("MainWindow", u"Plot", None)) self.label_120.setText(QCoreApplication.translate("MainWindow", u"Title", None)) self.bands_title.setText(QCoreApplication.translate("MainWindow", u"Bands", None)) @@ -7300,7 +7336,7 @@ def retranslateUi(self, MainWindow): self.tabWidget_6.setTabText(self.tabWidget_6.indexOf(self.tab_16), QCoreApplication.translate("MainWindow", u"Bonds", None)) self.label_20.setText(QCoreApplication.translate("MainWindow", u"Energy units", None)) self.label_35.setText(QCoreApplication.translate("MainWindow", u"Volume units", None)) - self.FormActionsPostButAddRowCellParam.setText(QCoreApplication.translate("MainWindow", u"Add Row", None)) + self.add_cell_param_row.setText(QCoreApplication.translate("MainWindow", u"Add Row", None)) self.FormActionsPostButPlusCellParam.setText(QCoreApplication.translate("MainWindow", u"Add File", None)) self.cell_params_file_add.setText(QCoreApplication.translate("MainWindow", u"Import", None)) self.cell_param_delete_row.setText(QCoreApplication.translate("MainWindow", u"Delete Selected", None)) diff --git a/src/utils/electronic_prop_reader.py b/src/utils/electronic_prop_reader.py index 59c1dd9..6e4590a 100644 --- a/src/utils/electronic_prop_reader.py +++ b/src/utils/electronic_prop_reader.py @@ -70,8 +70,6 @@ def read_siesta_bands(file, is_check_bands_spin, k_max, k_min): n_bands, n_spins = int(str1[0]), int(str1[1]) n_k_points = int(str1[2]) k_mesh = np.zeros((str1[2])) - homo = e_min * np.ones(n_k_points) - lumo = e_max * np.ones(n_k_points) bands = np.zeros((n_bands * n_spins, n_k_points)) for i in range(0, str1[2]): str2 = f.readline().split() @@ -90,22 +88,19 @@ def read_siesta_bands(file, is_check_bands_spin, k_max, k_min): bands = bands[:n_bands] else: bands = bands[n_bands:] - for i in range(0, n_bands): + f.close() + return bands, e_max, e_min, k_mesh + + +def siesta_homo_lumo(bands, e_max, e_min): + # e_min, e_max = np.min(bands, 1), np.max(bands, 1) + homo = e_min * np.ones(len(bands[0])) + lumo = e_max * np.ones(len(bands[0])) + for i in range(0, len(bands)): for j in range(0, len(bands[0])): tm = float(bands[i][j]) if (tm > homo[j]) and (tm <= 0): homo[j] = tm if (tm < lumo[j]) and (tm > 0): lumo[j] = tm - n_sticks = int(f.readline()) - x_ticks = [] - x_tick_labels = [] - for i in range(0, n_sticks): - str3 = f.readline().split() - value = float(str3[0]) - if (round(value, 2) >= k_min) and (round(value, 2) <= k_max): - x_ticks.append(value) - letter = helpers.utf8_letter(str3[1][1:-1]) - x_tick_labels.append(letter) - f.close() - return bands, e_max, e_min, homo, k_mesh, lumo, x_tick_labels, x_ticks + return homo, lumo diff --git a/tests/gui4dft/test_electronic_prop.py b/tests/gui4dft/test_electronic_prop.py index 3224236..741632f 100644 --- a/tests/gui4dft/test_electronic_prop.py +++ b/tests/gui4dft/test_electronic_prop.py @@ -1,4 +1,4 @@ -from utils.electronic_prop_reader import read_siesta_bands, dos_from_file, dos_siesta_vert +from utils.electronic_prop_reader import read_siesta_bands, dos_from_file, dos_siesta_vert, siesta_homo_lumo from program.vasp import vasp_dos from program.siesta import TSIESTA @@ -44,10 +44,10 @@ def test_bands(tests_path): kmin, kmax = 0.0, 0.39 is_check_bands_spin = True - bands, emaxf, eminf, homo, kmesh, lumo, xticklabels, xticks = read_siesta_bands(f_name, is_check_bands_spin, - kmax, kmin) + bands, emaxf, eminf, kmesh = read_siesta_bands(f_name, is_check_bands_spin, kmax, kmin) assert len(kmesh) == 100 + homo, lumo = siesta_homo_lumo(bands, emaxf, eminf) gap, gap_ind = gaps(bands, emaxf, eminf, homo, lumo) assert gap == 0.6385999999999994 @@ -55,9 +55,9 @@ def test_bands(tests_path): kmin, kmax = 0.0, 0.39 is_check_bands_spin = True - bands, emaxf, eminf, homo, kmesh, lumo, xticklabels, xticks = read_siesta_bands(f_name, is_check_bands_spin, - kmax, kmin) + bands, emaxf, eminf, kmesh = read_siesta_bands(f_name, is_check_bands_spin, kmax, kmin) assert len(kmesh) == 100 + homo, lumo = siesta_homo_lumo(bands, emaxf, eminf) gap, gap_ind = gaps(bands, emaxf, eminf, homo, lumo) assert gap == 0.6385999999999994 From 64f83c03f7cd7c34945ac7998471f516eb7d2ef4 Mon Sep 17 00:00:00 2001 From: Sergey Sozykin <47054553+sozykinsa@users.noreply.github.com> Date: Tue, 14 May 2024 22:52:40 +0500 Subject: [PATCH 4/9] tests: VASP --- src/core_atomistic/helpers.py | 1 - src/program/importer_exporter.py | 40 +- src/program/vasp.py | 368 +++++++++--------- src/program/wien.py | 92 ++--- src/qtbased/gui4dftform.py | 31 +- tests/gui4dft/test_electronic_prop.py | 6 +- tests/gui4dft/test_importer.py | 4 +- tests/gui4dft/test_vasp.py | 63 ++- tests/gui4dft/test_wien.py | 7 +- tests/ref_data/vasp/{ => vasp_cartesian}/CHG | 0 .../ref_data/vasp/{ => vasp_cartesian}/CHGCAR | 0 .../vasp/{ => vasp_cartesian}/CONTCAR | 0 .../ref_data/vasp/{ => vasp_cartesian}/DOSCAR | 0 .../ref_data/vasp/{ => vasp_cartesian}/INCAR | 0 .../vasp/{ => vasp_cartesian}/KPOINTS | 0 .../ref_data/vasp/{ => vasp_cartesian}/OUTCAR | 0 .../ref_data/vasp/{ => vasp_cartesian}/POSCAR | 0 .../vasp/{ => vasp_cartesian}/vasprun.xml | 0 tests/ref_data/vasp/vasp_direct/POSCAR | 12 + 19 files changed, 340 insertions(+), 284 deletions(-) rename tests/ref_data/vasp/{ => vasp_cartesian}/CHG (100%) rename tests/ref_data/vasp/{ => vasp_cartesian}/CHGCAR (100%) rename tests/ref_data/vasp/{ => vasp_cartesian}/CONTCAR (100%) rename tests/ref_data/vasp/{ => vasp_cartesian}/DOSCAR (100%) rename tests/ref_data/vasp/{ => vasp_cartesian}/INCAR (100%) rename tests/ref_data/vasp/{ => vasp_cartesian}/KPOINTS (100%) rename tests/ref_data/vasp/{ => vasp_cartesian}/OUTCAR (100%) rename tests/ref_data/vasp/{ => vasp_cartesian}/POSCAR (100%) rename tests/ref_data/vasp/{ => vasp_cartesian}/vasprun.xml (100%) create mode 100644 tests/ref_data/vasp/vasp_direct/POSCAR diff --git a/src/core_atomistic/helpers.py b/src/core_atomistic/helpers.py index 00867c4..a4adf50 100644 --- a/src/core_atomistic/helpers.py +++ b/src/core_atomistic/helpers.py @@ -177,7 +177,6 @@ def list_of_values(filename, prop): return list_of_val - def write_text_to_file(f_name, text): # pragma: no cover if len(f_name) > 0: with open(f_name, 'w') as f: diff --git a/src/program/importer_exporter.py b/src/program/importer_exporter.py index be6d01e..c2b86de 100644 --- a/src/program/importer_exporter.py +++ b/src/program/importer_exporter.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import os +import numpy as np from core_atomistic.atomic_model import AtomicModel from core_atomistic import helpers from core_atomistic.project_file import ProjectFile @@ -12,8 +13,8 @@ from program.qe import atoms_from_pwout from program.dftb import atoms_from_gen from program.lammps import atoms_trajectory_step -from program.vasp import fermi_energy_from_doscar, atoms_from_poscar, atoms_from_outcar, model_to_vasp_poscar -from program.wien import atoms_from_struct +from program.vasp import VASP +from program.wien import WIEN from program.gaussiancube import GaussianCube from program.xsf import XSF @@ -63,10 +64,10 @@ def import_from_file(filename, fl='all', prop=False): models = AtomicModel.atoms_from_xmol_xyz(filename) elif file_format == "VASPposcar": - models = atoms_from_poscar(filename) + models = VASP.atoms_from_poscar(filename) elif file_format == "vasp_outcar": - models = atoms_from_outcar(filename) + models = VASP.atoms_from_outcar(filename) elif file_format == "CRYSTALout": models = structure_of_primitive_cell(filename) @@ -78,7 +79,8 @@ def import_from_file(filename, fl='all', prop=False): models = atoms_from_pwout(filename) elif file_format == "WIENstruct": - models = atoms_from_struct(filename) + wien = WIEN() + models = wien.atoms_from_struct(filename) elif file_format == "DFTBgen": models = atoms_from_gen(filename) @@ -98,7 +100,7 @@ def export_to_file(model, f_name): # pragma: no cover text = "" if f_name.find("POSCAR") >= 0: f_name = f_name.split(".")[0] - text = model_to_vasp_poscar(model) + text = VASP.model_to_vasp_poscar(model) if f_name.endswith(".inp"): text = atomic_model_to_firefly_inp(model) if f_name.endswith(".fdf"): @@ -109,11 +111,35 @@ def export_to_file(model, f_name): # pragma: no cover text = ProjectFile.project_file_writer(model) helpers.write_text_to_file(f_name, text) + @staticmethod + def abc_energy_volume(f_name, sourse): + volume = 0.0 + energy = 0.0 + a, b, c = 0.0, 0.0, 0.0 + if sourse == "siesta_out": + volume = TSIESTA.volume(f_name) + energy = TSIESTA.energy_tot(f_name) + models, fdf_data = ImporterExporter.import_from_file(f_name) + model = models[-1] + a = np.linalg.norm(model.lat_vector1) + b = np.linalg.norm(model.lat_vector2) + c = np.linalg.norm(model.lat_vector3) + if sourse == "vasp_outcar": + volume = VASP.volume(f_name) + energy, is_conv, iteration = VASP.energy_tot(f_name) + vasp = VASP() + a, b, c = vasp.abc_from_outcar(f_name) + if sourse == "QEPWout": + volume = qe_volume(f_name) + energy, is_conv, iteration = qe_energy_tot(f_name) + a, b, c, al, bet, gam = alats_from_pwout(f_name) + return a, b, c, energy, volume + @staticmethod def check_dos_file(filename): """Check DOS file for fdf/out filename.""" if filename.endswith("DOSCAR"): - return filename, fermi_energy_from_doscar(filename) + return filename, VASP.fermi_energy_from_doscar(filename) system_label = TSIESTA.system_label(filename) file = os.path.dirname(filename) + "/" + str(system_label) + ".DOS" diff --git a/src/program/vasp.py b/src/program/vasp.py index b4c8c4f..f1b58cc 100644 --- a/src/program/vasp.py +++ b/src/program/vasp.py @@ -10,7 +10,101 @@ from utils.electronic_prop_reader import dos_from_file -class TVASP: +class VASP: + + @staticmethod + def atoms_from_poscar(filename): + """Import structure from POSCAR file.""" + period_table = TPeriodTable() + molecules = [] + if os.path.exists(filename): + struct_file = open(filename) + helpers.spacedel(struct_file.readline()) + lat_const = float(helpers.spacedel(struct_file.readline())) + lat1 = helpers.spacedel(struct_file.readline()).split() + lat1 = np.array(helpers.list_str_to_float(lat1)) * lat_const + lat2 = helpers.spacedel(struct_file.readline()).split() + lat2 = np.array(helpers.list_str_to_float(lat2)) * lat_const + lat3 = helpers.spacedel(struct_file.readline()).split() + lat3 = np.array(helpers.list_str_to_float(lat3)) * lat_const + sorts_of_atoms = helpers.spacedel(struct_file.readline()).split() + numbers_of_atoms = helpers.spacedel(struct_file.readline()).split() + numbers_of_atoms = helpers.list_str_to_int(numbers_of_atoms) + + coord_type = helpers.spacedel(struct_file.readline()).lower() + + if (coord_type == "direct") or (coord_type == "cartesian"): + new_str = AtomicModel() + for i in range(0, len(numbers_of_atoms)): + number = numbers_of_atoms[i] + for j in range(0, number): + str1 = helpers.spacedel(struct_file.readline()) + s = str1.split(' ') + x = float(s[0]) + y = float(s[1]) + z = float(s[2]) + charge = period_table.get_charge_by_letter(sorts_of_atoms[i]) + let = sorts_of_atoms[i] + new_str.add_atom(Atom([x, y, z, let, charge])) + new_str.set_lat_vectors(lat1, lat2, lat3) + if coord_type == "direct": + new_str.convert_from_direct_to_cart() + molecules.append(new_str) + return molecules + + @staticmethod + def specieses_from_outcar(filename): + specieses = [] + ions_per_type = "" + types = [] + if os.path.exists(filename): + my_file = open(filename) + str1 = my_file.readline() + while str1 != '': + if str1.find("ions per type") >= 0: + ions_per_type = helpers.spacedel(str1.split("ions per type =")[1]).split(" ") + if str1.find("VRHFIN") >= 0: + types.append(str1.split("VRHFIN =")[1].split(":")[0]) + str1 = my_file.readline() + my_file.close() + for n, let in zip(ions_per_type, types): + for i in range(int(n)): + specieses.append(let) + return specieses + + def atoms_from_outcar(self, filename): + molecules = [] + if os.path.exists(filename): + period_table = TPeriodTable() + all_vectors = self.vectors_from_outcar(filename) + specieses = self.specieses_from_outcar(filename) + prop = "POSITION" + my_file = open(filename) + str1 = my_file.readline() + while str1 != '': + if (str1 != '') and (str1.find(prop) >= 0): + my_file.readline() + new_str = AtomicModel() + for let in specieses: + str1 = my_file.readline() + data = str1.split() + xyz = np.array([float(data[0]), float(data[1]), float(data[2])]) + new_str.add_atom_with_data(xyz, period_table.get_charge_by_letter(let)) + vectors = all_vectors[len(molecules) + 1] + new_str.set_lat_vectors(vectors[0], vectors[1], vectors[2]) + molecules.append(new_str) + str1 = my_file.readline() + my_file.close() + return molecules + + def abc_from_outcar(self, filename): + """direct lattice vectors""" + model = AtomicModel() + vecs = self.vectors_from_outcar(filename) + if len(vecs) > 0: + model.set_lat_vectors(vecs[-1][0], vecs[-1][1], vecs[-1][2]) + a, b, c, al, bet, gam = model.cell_params() + return a, b, c @staticmethod def number_of_atoms(filename): @@ -67,199 +161,103 @@ def volume(filename): my_file.close() return property + @staticmethod + def model_to_vasp_poscar(model, coord_type="Fractional"): + """Create file in VASP POSCAR format.""" + data = "" + data += "model \n" + data += ' 1.0 \n' + + data += ' ' + str(model.lat_vector1[0]) + ' ' + str(model.lat_vector1[1]) + ' ' + \ + str(model.lat_vector1[2]) + '\n' + data += ' ' + str(model.lat_vector2[0]) + ' ' + str(model.lat_vector2[1]) + ' ' + \ + str(model.lat_vector2[2]) + '\n' + data += ' ' + str(model.lat_vector3[0]) + ' ' + str(model.lat_vector3[1]) + ' ' + \ + str(model.lat_vector3[2]) + '\n' + + per_tab = TPeriodTable() + + types = model.types_of_atoms() + for i in range(0, len(types)): + data += ' ' + str(per_tab.get_let(int(types[i][0]))) + data += "\n" + + for i in range(0, len(types)): + count = 0 + for atom in model.atoms: + if atom.charge == int(types[i][0]): + count += 1 + data += ' ' + str(count) + data += "\n" + + model.sort_atoms_by_type() + # model.go_to_positive_coordinates() + model.move_atoms_to_center() + if coord_type == "Fractional": + model.convert_from_cart_to_direct() + if coord_type == "Fractional": + data += "Direct\n" + if coord_type == "Cartesian": + data += "Cartesian\n" + data += model.coords_for_export("POSCAR") + return data -def atoms_from_outcar(filename): - molecules = [] - if os.path.exists(filename): - period_table = TPeriodTable() - all_vectors = vectors_from_outcar(filename) - specieses = specieses_from_outcar(filename) - prop = "POSITION" - my_file = open(filename) - str1 = my_file.readline() - while str1 != '': - if (str1 != '') and (str1.find(prop) >= 0): - my_file.readline() - new_str = AtomicModel() - for let in specieses: - str1 = my_file.readline() - data = str1.split() - xyz = np.array([float(data[0]), float(data[1]), float(data[2])]) - new_str.add_atom_with_data(xyz, period_table.get_charge_by_letter(let)) - vectors = all_vectors[len(molecules) + 1] - new_str.set_lat_vectors(vectors[0], vectors[1], vectors[2]) - molecules.append(new_str) - str1 = my_file.readline() - my_file.close() - return molecules - - -def specieses_from_outcar(filename): - specieses = [] - ions_per_type = "" - types = [] - if os.path.exists(filename): - my_file = open(filename) - str1 = my_file.readline() - while str1 != '': - if str1.find("ions per type") >= 0: - ions_per_type = helpers.spacedel(str1.split("ions per type =")[1]).split(" ") - if str1.find("VRHFIN") >= 0: - types.append(str1.split("VRHFIN =")[1].split(":")[0]) + @staticmethod + def vectors_from_outcar(filename): + prop = "direct lattice vectors" + all_vectors = [] + vectors = np.zeros((3, 3), dtype=float) + if os.path.exists(filename): + my_file = open(filename) str1 = my_file.readline() - my_file.close() - for n, let in zip(ions_per_type, types): - for i in range(int(n)): - specieses.append(let) - return specieses - - -def abc_from_outcar(filename): - """direct lattice vectors""" - model = AtomicModel() - vecs = vectors_from_outcar(filename) - if len(vecs) > 0: - model.set_lat_vectors(vecs[-1][0], vecs[-1][1], vecs[-1][2]) - a, b, c, al, bet, gam = model.cell_params() - return a, b, c - + while str1 != '': + if (str1 != '') and (str1.find(prop) >= 0): + str1 = my_file.readline() + row1 = re.findall(r"[0-9,\.,-]+", str1) + str1 = my_file.readline() + row2 = re.findall(r"[0-9,\.,-]+", str1) + str1 = my_file.readline() + row3 = re.findall(r"[0-9,\.,-]+", str1) -def vectors_from_outcar(filename): - prop = "direct lattice vectors" - all_vectors = [] - vectors = np.zeros((3, 3), dtype=float) - if os.path.exists(filename): - my_file = open(filename) - str1 = my_file.readline() - while str1 != '': - if (str1 != '') and (str1.find(prop) >= 0): - str1 = my_file.readline() - row1 = re.findall(r"[0-9,\.,-]+", str1) + vectors[0] = np.array((row1[0], row1[1], row1[2]), dtype=float) + vectors[1] = np.array((row2[0], row2[1], row2[2]), dtype=float) + vectors[2] = np.array((row3[0], row3[1], row3[2]), dtype=float) + all_vectors.append(vectors) str1 = my_file.readline() - row2 = re.findall(r"[0-9,\.,-]+", str1) - str1 = my_file.readline() - row3 = re.findall(r"[0-9,\.,-]+", str1) - - vectors[0] = np.array((row1[0], row1[1], row1[2]), dtype=float) - vectors[1] = np.array((row2[0], row2[1], row2[2]), dtype=float) - vectors[2] = np.array((row3[0], row3[1], row3[2]), dtype=float) - all_vectors.append(vectors) - str1 = my_file.readline() - my_file.close() - return all_vectors - - -def atoms_from_poscar(filename): - """Import structure from POSCAR file.""" - period_table = TPeriodTable() - molecules = [] - if os.path.exists(filename): - struct_file = open(filename) - helpers.spacedel(struct_file.readline()) - lat_const = float(helpers.spacedel(struct_file.readline())) - lat1 = helpers.spacedel(struct_file.readline()).split() - lat1 = np.array(helpers.list_str_to_float(lat1)) * lat_const - lat2 = helpers.spacedel(struct_file.readline()).split() - lat2 = np.array(helpers.list_str_to_float(lat2)) * lat_const - lat3 = helpers.spacedel(struct_file.readline()).split() - lat3 = np.array(helpers.list_str_to_float(lat3)) * lat_const - sorts_of_atoms = helpers.spacedel(struct_file.readline()).split() - numbers_of_atoms = helpers.spacedel(struct_file.readline()).split() - numbers_of_atoms = helpers.list_str_to_int(numbers_of_atoms) - - coord_type = helpers.spacedel(struct_file.readline()).lower() - - if (coord_type == "direct") or (coord_type == "cartesian"): - new_str = AtomicModel() - for i in range(0, len(numbers_of_atoms)): - number = numbers_of_atoms[i] - for j in range(0, number): - str1 = helpers.spacedel(struct_file.readline()) - s = str1.split(' ') - x = float(s[0]) - y = float(s[1]) - z = float(s[2]) - charge = period_table.get_charge_by_letter(sorts_of_atoms[i]) - let = sorts_of_atoms[i] - new_str.add_atom(Atom([x, y, z, let, charge])) - new_str.set_lat_vectors(lat1, lat2, lat3) - if coord_type == "direct": - new_str.convert_from_direct_to_cart() - molecules.append(new_str) - return molecules - + my_file.close() + return all_vectors -def vasp_latt_const(filename): - """Lattice Constant""" - if os.path.exists(filename): - MyFile = open(filename) - MyFile.readline() - str1 = MyFile.readline() - n = float(re.findall(r"[0-9,\.,-]+", str1)[0]) - MyFile.close() - return n + @staticmethod + def vasp_latt_const(filename): + """Lattice Constant""" + if os.path.exists(filename): + f_name = open(filename) + f_name.readline() + str1 = f_name.readline() + lat_const = float(re.findall(r"[0-9,\.,-]+", str1)[0]) + f_name.close() + return lat_const + @staticmethod + def fermi_energy_from_doscar(filename): + if os.path.exists(filename): + my_file = open(filename) + str1 = my_file.readline() + for i in range(5): + str1 = my_file.readline() + my_file.close() + e_fermy = float(str1.split()[3]) + return e_fermy -def fermi_energy_from_doscar(filename): - if os.path.exists(filename): + @staticmethod + def vasp_dos(filename): + """DOS""" my_file = open(filename) str1 = my_file.readline() for i in range(5): str1 = my_file.readline() my_file.close() - e_fermy = float(str1.split()[3]) - return e_fermy - - -def vasp_dos(filename): - """DOS""" - my_file = open(filename) - str1 = my_file.readline() - for i in range(5): - str1 = my_file.readline() - my_file.close() - nlines = int(str1.split()[2]) - if os.path.exists(filename): - spin_up, spin_down, energy = dos_from_file(filename, 2, nlines) - return np.array(spin_up), np.array(spin_down), np.array(energy) - - -def model_to_vasp_poscar(model, coord_type="Fractional"): - """Create file in VASP POSCAR format.""" - data = "" - data += "model \n" - data += ' 1.0 \n' - - data += ' ' + str(model.lat_vector1[0]) + ' ' + str(model.lat_vector1[1]) + ' ' + \ - str(model.lat_vector1[2]) + '\n' - data += ' ' + str(model.lat_vector2[0]) + ' ' + str(model.lat_vector2[1]) + ' ' + \ - str(model.lat_vector2[2]) + '\n' - data += ' ' + str(model.lat_vector3[0]) + ' ' + str(model.lat_vector3[1]) + ' ' + \ - str(model.lat_vector3[2]) + '\n' - - per_tab = TPeriodTable() - - types = model.types_of_atoms() - for i in range(0, len(types)): - data += ' ' + str(per_tab.get_let(int(types[i][0]))) - data += "\n" - - for i in range(0, len(types)): - count = 0 - for atom in model.atoms: - if atom.charge == int(types[i][0]): - count += 1 - data += ' ' + str(count) - data += "\n" - - model.sort_atoms_by_type() - # model.go_to_positive_coordinates() - model.move_atoms_to_center() - if coord_type == "Fractional": - model.convert_from_cart_to_direct() - if coord_type == "Fractional": - data += "Direct\n" - if coord_type == "Cartesian": - data += "Cartesian\n" - data += model.coords_for_export("POSCAR") - return data + nlines = int(str1.split()[2]) + if os.path.exists(filename): + spin_up, spin_down, energy = dos_from_file(filename, 2, nlines) + return np.array(spin_up), np.array(spin_down), np.array(energy) diff --git a/src/program/wien.py b/src/program/wien.py index 365dbfb..5658bff 100644 --- a/src/program/wien.py +++ b/src/program/wien.py @@ -8,17 +8,53 @@ from core_atomistic import helpers -def n_atoms_from_struct(f_name): - """ - Test_file_WIEN2k9.2_Fe54 - P LATTICE,NONEQUIV. ATOMS108 - """ - f = open(f_name) - f.readline() - n = f.readline() - n = re.findall(r'\d+', n)[-1] - f.close() - return int(n) +class WIEN: + + @staticmethod + def n_atoms_from_struct(f_name): + """ + Test_file_WIEN2k9.2_Fe54 + P LATTICE,NONEQUIV. ATOMS108 + """ + f = open(f_name) + f.readline() + n = f.readline() + n = re.findall(r'\d+', n)[-1] + f.close() + return int(n) + + def atoms_from_struct(self, f_name): + model = AtomicModel() + n_atoms = self.n_atoms_from_struct(f_name) + a, b, c, alp, bet, gam = alats_from_struct(f_name) + lattice = helpers.lat_vectors_from_params(a, b, c, alp, bet, gam) + model.set_lat_vectors(lattice[0], lattice[1], lattice[2]) + f = open(f_name) + for i in range(5): + str1 = f.readline() + k = 0 + + """ + ATOM -1: X=0.00000000 Y=0.00000000 Z=0.00000000 + MULT= 1 ISPLIT= 8 + Fe NPT= 781 R0=0.00005000 RMT= 2.0000 Z: 26.0 + LOCAL ROT MATRIX: 1.0000000 0.0000000 0.0000000 + 0.0000000 1.0000000 0.0000000 + 0.0000000 0.0000000 1.0000000 + """ + while (k < n_atoms) and str1: + while (str1.find("ATOM") < 0) and str1: + str1 = f.readline() + coord = str1 # ATOM -1: X=0.00000000 Y=0.00000000 Z=0.00000000 + atom_coords = re.findall('\d+.\d+', coord) + xyz = np.array([float(atom_coords[-3]), float(atom_coords[-2]), float(atom_coords[-1])], dtype=float) + f.readline() + str1 = f.readline() # Fe NPT= 781 R0=0.00005000 RMT= 2.0000 Z: 26.0 + charge = float(str1.split("Z:")[1]) + model.add_atom_with_data(xyz, charge) + k += 1 + model.convert_from_direct_to_cart() + return [model] def alats_from_struct(f_name): @@ -47,40 +83,6 @@ def alats_from_struct(f_name): return a, b, c, alp, bet, gam -def atoms_from_struct(f_name): - model = AtomicModel() - n_atoms = n_atoms_from_struct(f_name) - a, b, c, alp, bet, gam = alats_from_struct(f_name) - lattice = helpers.lat_vectors_from_params(a, b, c, alp, bet, gam) - model.set_lat_vectors(lattice[0], lattice[1], lattice[2]) - f = open(f_name) - for i in range(5): - str1 = f.readline() - k = 0 - - """ - ATOM -1: X=0.00000000 Y=0.00000000 Z=0.00000000 - MULT= 1 ISPLIT= 8 - Fe NPT= 781 R0=0.00005000 RMT= 2.0000 Z: 26.0 - LOCAL ROT MATRIX: 1.0000000 0.0000000 0.0000000 - 0.0000000 1.0000000 0.0000000 - 0.0000000 0.0000000 1.0000000 - """ - while (k < n_atoms) and str1: - while (str1.find("ATOM") < 0) and str1: - str1 = f.readline() - coord = str1 # ATOM -1: X=0.00000000 Y=0.00000000 Z=0.00000000 - atom_coords = re.findall('\d+.\d+', coord) - xyz = np.array([float(atom_coords[-3]), float(atom_coords[-2]), float(atom_coords[-1])], dtype=float) - f.readline() - str1 = f.readline() # Fe NPT= 781 R0=0.00005000 RMT= 2.0000 Z: 26.0 - charge = float(str1.split("Z:")[1]) - model.add_atom_with_data(xyz, charge) - k += 1 - model.convert_from_direct_to_cart() - return [model] - - def model_to_wien_struct(model: AtomicModel): n_atoms = model.n_atoms() text = "Model " + model.formula() + "\n" diff --git a/src/qtbased/gui4dftform.py b/src/qtbased/gui4dftform.py index a558d17..93777ee 100644 --- a/src/qtbased/gui4dftform.py +++ b/src/qtbased/gui4dftform.py @@ -43,7 +43,7 @@ from program.qe import energy_tot as qe_energy_tot from program.qe import volume as qe_volume from program.wien import model_to_wien_struct -from program.vasp import TVASP, vasp_dos, model_to_vasp_poscar, abc_from_outcar +from program.vasp import VASP from program.dftb import model_to_dftb_d0 from program.lammps import model_to_lammps_input from program.octopus import model_to_octopus_input @@ -1141,7 +1141,7 @@ def fill_gui(self, title=""): self.ui.FormActionsTabeDOSProperty.setRowCount(i) self.ui.FormActionsTabeDOSProperty.setItem(i - 1, 1, QTableWidgetItem(str(e_fermi))) - def fill_energies(self, energies: list[float]) -> None: + def fill_energies(self, energies) -> None: """Plot energies for steps of output.""" if len(energies) == 0: return @@ -1271,28 +1271,7 @@ def fill_bonds_charges(self): return c1, c2 def fill_cell_info(self, f_name, sourse="siesta_out"): - volume = 0.0 - energy = 0.0 - a, b, c = 0.0, 0.0, 0.0 - - if sourse == "siesta_out": - volume = TSIESTA.volume(f_name) - energy = TSIESTA.energy_tot(f_name) - models, fdf_data = ImporterExporter.import_from_file(f_name) - model = models[-1] - a = np.linalg.norm(model.lat_vector1) - b = np.linalg.norm(model.lat_vector2) - c = np.linalg.norm(model.lat_vector3) - if sourse == "vasp_outcar": - volume = TVASP.volume(f_name) - energy, is_conv, iteration = TVASP.energy_tot(f_name) - a, b, c = abc_from_outcar(f_name) - - if sourse == "QEPWout": - volume = qe_volume(f_name) - energy, is_conv, iteration = qe_energy_tot(f_name) - a, b, c, al, bet, gam = alats_from_pwout(f_name) - + a, b, c, energy, volume = ImporterExporter.abc_energy_volume(f_name, sourse) self.fill_cell_info_row(energy, volume, a, b, c) self.work_dir = os.path.dirname(f_name) self.save_active_folder() @@ -2399,7 +2378,7 @@ def plot_dos(self): if os.path.exists(path): if path.endswith("DOSCAR"): - spin_up, spin_down, energy = vasp_dos(path) + spin_up, spin_down, energy = VASP.vasp_dos(path) else: spin_up, spin_down, energy = dos_from_file(path) @@ -2941,7 +2920,7 @@ def poscar_data_to_form(self): return try: model = self.ui.openGLWidget.get_model() - text = model_to_vasp_poscar(model, self.coord_type) + text = VASP.model_to_vasp_poscar(model, self.coord_type) self.ui.FormActionsPreTextFDF.setText(text) except Exception: print("There are no atoms in the model") diff --git a/tests/gui4dft/test_electronic_prop.py b/tests/gui4dft/test_electronic_prop.py index 741632f..bcc5e2e 100644 --- a/tests/gui4dft/test_electronic_prop.py +++ b/tests/gui4dft/test_electronic_prop.py @@ -1,5 +1,5 @@ from utils.electronic_prop_reader import read_siesta_bands, dos_from_file, dos_siesta_vert, siesta_homo_lumo -from program.vasp import vasp_dos +from program.vasp import VASP from program.siesta import TSIESTA from utils.calculators import gaps @@ -10,8 +10,8 @@ def test_dos_from_file(tests_path): spin_up, spin_down, energy = dos_from_file(f_name) assert len(energy) == 1000 - f_name = str(tests_path / 'ref_data' / 'vasp' / "DOSCAR") - spin_up, spin_down, energy = vasp_dos(f_name) + f_name = str(tests_path / 'ref_data' / 'vasp' / 'vasp_cartesian' / "DOSCAR") + spin_up, spin_down, energy = VASP.vasp_dos(f_name) assert len(energy) == 301 diff --git a/tests/gui4dft/test_importer.py b/tests/gui4dft/test_importer.py index d382a1d..5b57edb 100644 --- a/tests/gui4dft/test_importer.py +++ b/tests/gui4dft/test_importer.py @@ -6,7 +6,7 @@ def test_check_format(tests_path): assert helpers.check_format(str(tests_path / 'ref_data' / 'swcnt(8,0)' / "siesta1.out")) == "unknown" assert helpers.check_format(str(tests_path / 'ref_data' / 'swcnt(8,0)' / "siesta.out")) == "siesta_out" assert helpers.check_format(str(tests_path / 'ref_data' / 'siesta' / 'h2o-ang' / "siesta.fdf")) == "SIESTAfdf" - assert helpers.check_format(str(tests_path / 'ref_data' / 'vasp' / 'POSCAR')) == "VASPposcar" + assert helpers.check_format(str(tests_path / 'ref_data' / 'vasp' / 'vasp_cartesian' / 'POSCAR')) == "VASPposcar" assert helpers.check_format(str(tests_path / 'ref_data' / 'qe' / 'si-scf' / "pw.out")) == "QEPWout" file1 = str(tests_path / 'ref_data' / 'siesta' / 'h2o-ang-charges' / 'cube_and_xsf' / "siesta.BADER.cube") assert helpers.check_format(file1) == "GAUSSIAN_cube" @@ -164,7 +164,7 @@ def test_importer_check_dos_pdos_bands(tests_path): def test_importer_poscar(tests_path): - f_name = str(tests_path / 'ref_data' / 'vasp' / 'POSCAR') + f_name = str(tests_path / 'ref_data' / 'vasp' / 'vasp_cartesian' / 'POSCAR') model, fdf = ImporterExporter.import_from_file(f_name, fl='all', prop=False) assert len(model[0].atoms) == 1 diff --git a/tests/gui4dft/test_vasp.py b/tests/gui4dft/test_vasp.py index 789e844..f13214c 100644 --- a/tests/gui4dft/test_vasp.py +++ b/tests/gui4dft/test_vasp.py @@ -1,38 +1,77 @@ -from program.vasp import TVASP, model_to_vasp_poscar, fermi_energy_from_doscar, abc_from_outcar, vectors_from_outcar +from program.vasp import VASP + + +def test_atoms_from_poscar(tests_path): + f_name = str(tests_path / 'ref_data' / 'vasp' / 'vasp_direct' / "POSCAR") + model = VASP.atoms_from_poscar(f_name)[0] + assert model.n_atoms() == 4 + + f_name = str(tests_path / 'ref_data' / 'vasp' / 'vasp_cartesian'/ "POSCAR") + model = VASP.atoms_from_poscar(f_name)[0] + assert model.n_atoms() == 1 + + +def test_atoms_from_outcar(tests_path): + f_name = str(tests_path / 'ref_data' / 'vasp' / 'vasp_cartesian' / "OUTCAR") + vasp = VASP() + model = vasp.atoms_from_outcar(f_name)[0] + assert len(model.atoms) == 1 + + +def test_specieses_from_outcar(tests_path): + f_name = str(tests_path / 'ref_data' / 'vasp' / 'vasp_cartesian'/ "OUTCAR") + specieses = VASP.specieses_from_outcar(f_name) + assert len(specieses) == 1 + assert specieses[0] == 'Si' def test_model_to_vasp_poscar(h2o_model): - text = model_to_vasp_poscar(h2o_model) + text = VASP.model_to_vasp_poscar(h2o_model) assert len(text) == 216 + text = VASP.model_to_vasp_poscar(h2o_model, coord_type="Cartesian") + assert len(text) == 219 def test_fermi_energy_from_doscar(tests_path): - f_name = str(tests_path / 'ref_data' / 'vasp' / "DOSCAR") - e_f = fermi_energy_from_doscar(f_name) + f_name = str(tests_path / 'ref_data' / 'vasp' / 'vasp_cartesian' / "DOSCAR") + e_f = VASP.fermi_energy_from_doscar(f_name) assert e_f == 7.90341495 +def test_n_atoms(tests_path): + f_name = str(tests_path / 'ref_data' / 'vasp' / 'vasp_cartesian'/ "POSCAR") + n = VASP.number_of_atoms(f_name) + assert n == 1 + + +def test_vasp_latt_const(tests_path): + f_name = str(tests_path / 'ref_data' / 'vasp' / 'vasp_direct' / "POSCAR") + lat_const = VASP.vasp_latt_const(f_name) + assert lat_const == 1.0 + + def test_volume(tests_path): - f_name = str(tests_path / 'ref_data' / 'vasp' / "OUTCAR") - v = TVASP.volume(f_name) + f_name = str(tests_path / 'ref_data' / 'vasp' / 'vasp_cartesian' / "OUTCAR") + v = VASP.volume(f_name) assert v == 14.83 def test_energy_tot(tests_path): - f_name = str(tests_path / 'ref_data' / 'vasp' / "OUTCAR") - e, t, b = TVASP.energy_tot(f_name) + f_name = str(tests_path / 'ref_data' / 'vasp' / 'vasp_cartesian' / "OUTCAR") + e, t, b = VASP.energy_tot(f_name) assert e == -4.71040512 def test_vectors(tests_path): - f_name = str(tests_path / 'ref_data' / 'vasp' / "OUTCAR") - vecs = vectors_from_outcar(f_name) + f_name = str(tests_path / 'ref_data' / 'vasp' / 'vasp_cartesian' / "OUTCAR") + vecs = VASP.vectors_from_outcar(f_name) print(vecs[0][0]) a = vecs[0][0][0] assert abs(a - 1.95) < 1e-2 def test_abc(tests_path): - f_name = str(tests_path / 'ref_data' / 'vasp' / "OUTCAR") - a, b, c = abc_from_outcar(f_name) + f_name = str(tests_path / 'ref_data' / 'vasp' / 'vasp_cartesian' / "OUTCAR") + vasp = VASP() + a, b, c = vasp.abc_from_outcar(f_name) assert abs(a - 2.757716447) < 1e-7 diff --git a/tests/gui4dft/test_wien.py b/tests/gui4dft/test_wien.py index 6946dd9..ebb4a47 100644 --- a/tests/gui4dft/test_wien.py +++ b/tests/gui4dft/test_wien.py @@ -1,9 +1,9 @@ -from program.wien import n_atoms_from_struct, alats_from_struct, atoms_from_struct +from program.wien import WIEN, alats_from_struct def test_n_atoms_from_struct(tests_path): f_name = str(tests_path / 'ref_data' / 'wien2k' / 'Fe53C_Si_1.struct') - assert n_atoms_from_struct(f_name) == 55 + assert WIEN.n_atoms_from_struct(f_name) == 55 def test_alats_from_struct(tests_path): @@ -15,5 +15,6 @@ def test_alats_from_struct(tests_path): def test_atoms_from_struct(tests_path): f_name = str(tests_path / 'ref_data' / 'wien2k' / 'Fe53C_Si_1.struct') - models = atoms_from_struct(f_name) + wien = WIEN() + models = wien.atoms_from_struct(f_name) assert models[0].n_atoms() == 55 diff --git a/tests/ref_data/vasp/CHG b/tests/ref_data/vasp/vasp_cartesian/CHG similarity index 100% rename from tests/ref_data/vasp/CHG rename to tests/ref_data/vasp/vasp_cartesian/CHG diff --git a/tests/ref_data/vasp/CHGCAR b/tests/ref_data/vasp/vasp_cartesian/CHGCAR similarity index 100% rename from tests/ref_data/vasp/CHGCAR rename to tests/ref_data/vasp/vasp_cartesian/CHGCAR diff --git a/tests/ref_data/vasp/CONTCAR b/tests/ref_data/vasp/vasp_cartesian/CONTCAR similarity index 100% rename from tests/ref_data/vasp/CONTCAR rename to tests/ref_data/vasp/vasp_cartesian/CONTCAR diff --git a/tests/ref_data/vasp/DOSCAR b/tests/ref_data/vasp/vasp_cartesian/DOSCAR similarity index 100% rename from tests/ref_data/vasp/DOSCAR rename to tests/ref_data/vasp/vasp_cartesian/DOSCAR diff --git a/tests/ref_data/vasp/INCAR b/tests/ref_data/vasp/vasp_cartesian/INCAR similarity index 100% rename from tests/ref_data/vasp/INCAR rename to tests/ref_data/vasp/vasp_cartesian/INCAR diff --git a/tests/ref_data/vasp/KPOINTS b/tests/ref_data/vasp/vasp_cartesian/KPOINTS similarity index 100% rename from tests/ref_data/vasp/KPOINTS rename to tests/ref_data/vasp/vasp_cartesian/KPOINTS diff --git a/tests/ref_data/vasp/OUTCAR b/tests/ref_data/vasp/vasp_cartesian/OUTCAR similarity index 100% rename from tests/ref_data/vasp/OUTCAR rename to tests/ref_data/vasp/vasp_cartesian/OUTCAR diff --git a/tests/ref_data/vasp/POSCAR b/tests/ref_data/vasp/vasp_cartesian/POSCAR similarity index 100% rename from tests/ref_data/vasp/POSCAR rename to tests/ref_data/vasp/vasp_cartesian/POSCAR diff --git a/tests/ref_data/vasp/vasprun.xml b/tests/ref_data/vasp/vasp_cartesian/vasprun.xml similarity index 100% rename from tests/ref_data/vasp/vasprun.xml rename to tests/ref_data/vasp/vasp_cartesian/vasprun.xml diff --git a/tests/ref_data/vasp/vasp_direct/POSCAR b/tests/ref_data/vasp/vasp_direct/POSCAR new file mode 100644 index 0000000..08e2054 --- /dev/null +++ b/tests/ref_data/vasp/vasp_direct/POSCAR @@ -0,0 +1,12 @@ +model + 1.0 + 5.0 0.0 0.0 + 0.0 6.0 0.0 + 0.0 0.0 7.0 + H P + 3 1 +Direct + 0.50000000 0.70010783 0.40261596 + 0.70795820 0.39994616 0.40261596 + 0.29204180 0.39994616 0.40261596 + 0.50000000 0.50000000 0.50943225 From 4b46909a868739297847677f4932af0be7863192 Mon Sep 17 00:00:00 2001 From: Sergey Sozykin <47054553+sozykinsa@users.noreply.github.com> Date: Tue, 14 May 2024 22:56:24 +0500 Subject: [PATCH 5/9] tests: python version in tests 3.10.11 --- .github/workflows/python-app.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 23e3140..927582e 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -20,10 +20,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.10 + - name: Set up Python 3.10.11 uses: actions/setup-python@v2 with: - python-version: 3.10 + python-version: 3.10.11 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -42,10 +42,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.10 + - name: Set up Python 3.10.11 uses: actions/setup-python@v2 with: - python-version: 3.10 + python-version: 3.10.11 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -61,10 +61,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.10 + - name: Set up Python 3.10.11 uses: actions/setup-python@v2 with: - python-version: 3.10 + python-version: 3.10.11 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -91,10 +91,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.10 + - name: Set up Python 3.10.11 uses: actions/setup-python@v2 with: - python-version: 3.10 + python-version: 3.10.11 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -110,10 +110,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.10 + - name: Set up Python 3.10.11 uses: actions/setup-python@v2 with: - python-version: 3.10 + python-version: 3.10.11 - name: Install dependencies run: | python -m pip install --upgrade pip From 97476737b6408184868f33360a69a2fd33f560e4 Mon Sep 17 00:00:00 2001 From: Sergey Sozykin <47054553+sozykinsa@users.noreply.github.com> Date: Tue, 14 May 2024 23:03:23 +0500 Subject: [PATCH 6/9] fix qe cell param --- src/program/importer_exporter.py | 3 +++ src/qtbased/gui4dftform.py | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/program/importer_exporter.py b/src/program/importer_exporter.py index c2b86de..28fbbbb 100644 --- a/src/program/importer_exporter.py +++ b/src/program/importer_exporter.py @@ -11,6 +11,9 @@ from program.crystal import structure_of_primitive_cell, structure_opt_step from program.chemdraw import model_from_ct from program.qe import atoms_from_pwout +from program.qe import energy_tot as qe_energy_tot +from program.qe import volume as qe_volume +from program.qe import alats_from_pwout from program.dftb import atoms_from_gen from program.lammps import atoms_trajectory_step from program.vasp import VASP diff --git a/src/qtbased/gui4dftform.py b/src/qtbased/gui4dftform.py index 93777ee..64fa67d 100644 --- a/src/qtbased/gui4dftform.py +++ b/src/qtbased/gui4dftform.py @@ -39,9 +39,7 @@ from program.xsf import XSF from qtbased.image3dexporter import Image3Dexporter from program.siesta import TSIESTA -from program.qe import TQE, model_to_qe_pw, alats_from_pwout, get_fermi_level -from program.qe import energy_tot as qe_energy_tot -from program.qe import volume as qe_volume +from program.qe import TQE, model_to_qe_pw, get_fermi_level from program.wien import model_to_wien_struct from program.vasp import VASP from program.dftb import model_to_dftb_d0 From 0316c0a682583d0e020ed6ca3e62de9086b2b8c3 Mon Sep 17 00:00:00 2001 From: Sergey Sozykin Date: Mon, 27 May 2024 22:03:08 +0500 Subject: [PATCH 7/9] lammps: triclinic cell --- src/program/lammps.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/program/lammps.py b/src/program/lammps.py index a072d74..1955574 100644 --- a/src/program/lammps.py +++ b/src/program/lammps.py @@ -33,14 +33,18 @@ def model_to_lammps_input(model: AtomicModel, charge=False): text = "# Comment\n" n_atoms = model.n_atoms() + text += "box tilt large\n" + text += "change_box all triclinic\n" text += str(n_atoms) + " atoms\n" types = model.types_of_atoms() - text += str(len(types)) + " atom types\n\n" + text += str(len(types)) + " atom types\n" a, b, c, al, bt, gm = model.cell_params() + lx, ly, lz, xy, xz, yz = cellparams_to_lammps_cell(a, b, c, al, bt, gm) - text += "0.000000000000 " + str(a) + " xlo xhi\n" - text += "0.000000000000 " + str(b) + " ylo yhi\n" - text += "0.000000000000 " + str(c) + " zlo zhi\n\n" + text += "0.000000000000 " + str(lx) + " xlo xhi\n" + text += "0.000000000000 " + str(ly) + " ylo yhi\n" + text += "0.000000000000 " + str(lz) + " zlo zhi\n" + text += str(round(xy, 12)) + " " + str(round(xz, 12)) + " " + str(round(yz, 12)) + " xy xz yz\n\n" text += "Masses\n\n" for i in range(len(types)): @@ -57,6 +61,16 @@ def model_to_lammps_input(model: AtomicModel, charge=False): return text +def cellparams_to_lammps_cell(a, b, c, al, bt, gm): + lx = a + xy = b * np.cos(gm * np.pi / 180) + xz = c * np.cos(bt * np.pi / 180) + ly = np.sqrt(b * b - xy * xy) + yz = (b * c * np.cos(al * np.pi / 180) - xy * xz) / ly + lz = np.sqrt(c * c - xz * xz - yz * yz) + return lx, ly, lz, xy, xz, yz + + def atoms_trajectory_step(f_name): model = AtomicModel() n_atoms = 0 From d7fdf1564f0c823753663d84e022491b7eeb174e Mon Sep 17 00:00:00 2001 From: Sozykin Sergey Date: Sat, 8 Jun 2024 09:52:24 +0500 Subject: [PATCH 8/9] version 1.4 --- src/core_atomistic_qt/opengl_base.py | 18 ++++--------- src/qtbased/gui4dftform.py | 4 +++ src/ui/form-v1.x.ui | 39 +++++++++++++++++++++++++--- src/ui/form.py | 24 ++++++++++++++--- 4 files changed, 66 insertions(+), 19 deletions(-) diff --git a/src/core_atomistic_qt/opengl_base.py b/src/core_atomistic_qt/opengl_base.py index 6b3ecf8..bcf85b1 100644 --- a/src/core_atomistic_qt/opengl_base.py +++ b/src/core_atomistic_qt/opengl_base.py @@ -20,6 +20,9 @@ def __init__(self, parent=None): self.main_model = None self.perspective_angle: int = 35 self.background_color = np.array((1.0, 1.0, 1.0), dtype=float) + self.periodic_table = TPeriodTable() + self.color_of_atoms = self.periodic_table.get_all_colors() + self.color_of_bonds_by_atoms: bool = True self.is_check_atom_selection: bool = False self.quality: int = 1 # opengl lists @@ -77,13 +80,6 @@ def __init__(self, parent=None): self.orientation_model_changed: Callable = None self.selected_atom_position: Callable = None self.selected_atom_callback: Callable = None - self.selected_atom_callback: Callable = None - - self.clusters_color = [np.array((1.0, 0.0, 0.0, 1.0), dtype=float), - np.array((0.0, 0.0, 1.0, 1.0), dtype=float), - np.array((0.0, 0.0, 1.0, 1.0), dtype=float), - np.array((1.0, 1.0, 0.0, 1.0), dtype=float), - np.array((1.0, 0.0, 1.0, 1.0), dtype=float)] def is_atom_selected(self): return self.selected_atom >= 0 @@ -423,7 +419,7 @@ def add_atoms(self): max_val = 0 mean_val = 0 - if (len(prop) > 0) and (prop != "charge") and (prop != "cluster"): + if (len(prop) > 0) and (prop != "charge"): min_val = self.main_model.atoms[0].properties[prop] max_val = self.main_model.atoms[0].properties[prop] mean_val = self.main_model.atoms[0].properties[prop] @@ -445,15 +441,12 @@ def add_atoms(self): rad_scale = 0.3 if not at.is_selected(): - if (len(prop) > 0) and (prop != "charge") and (prop != "cluster"): + if (len(prop) > 0) and (prop != "charge"): val = at.properties[prop] if val > mean_val: color = (color[0], math.fabs((val-mean_val)/(max_val-mean_val)), color[2], color[3]) else: color = (color[0], color[1], math.fabs((val-mean_val)/(min_val-mean_val)), color[3]) - elif prop == "cluster": - color = self.clusters_color[at.cluster] - pass else: color = self.color_of_atoms[at.charge] if self.selected_fragment_mode and at.fragment1: @@ -874,7 +867,6 @@ def bond_len_correct(self, let1: str, let2: str, d: float): ch2 = self.main_model.mendeley.get_charge_by_letter(let2) self.main_model.mendeley.Bonds[ch1][ch2] = d self.main_model.mendeley.Bonds[ch2][ch1] = d - #self.main_model.set_mendeley(self.periodic_table) self.main_model.find_bonds_fast() self.add_all_elements() self.update() diff --git a/src/qtbased/gui4dftform.py b/src/qtbased/gui4dftform.py index 64fa67d..cc524ca 100644 --- a/src/qtbased/gui4dftform.py +++ b/src/qtbased/gui4dftform.py @@ -934,6 +934,8 @@ def clear_qtree_widget(tree): @staticmethod def color_to_ui(color_ui, state_color): + if len(state_color.split()) < 3: + state_color = '0 0 0' r = state_color.split()[0] g = state_color.split()[1] b = state_color.split()[2] @@ -1407,6 +1409,8 @@ def get_fdf_file_name(self): # pragma: no cover @staticmethod def get_color_from_setting(strcolor: str): + if len(strcolor.split()) < 3: + strcolor = '0 0 0' r = strcolor.split()[0] g = strcolor.split()[1] b = strcolor.split()[2] diff --git a/src/ui/form-v1.x.ui b/src/ui/form-v1.x.ui index 5c23020..c1b446c 100644 --- a/src/ui/form-v1.x.ui +++ b/src/ui/form-v1.x.ui @@ -11287,6 +11287,39 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Quality + + + + + + + 1 + + + 20 + + + 10 + + + @@ -11380,7 +11413,7 @@ - QFrame::StyledPanel + QFrame::NoFrame QFrame::Raised @@ -11390,7 +11423,7 @@ - QFrame::StyledPanel + QFrame::NoFrame QFrame::Raised @@ -11418,7 +11451,7 @@ - QFrame::StyledPanel + QFrame::NoFrame QFrame::Raised diff --git a/src/ui/form.py b/src/ui/form.py index 0d38dd5..c31b681 100644 --- a/src/ui/form.py +++ b/src/ui/form.py @@ -6067,6 +6067,23 @@ def setupUi(self, MainWindow): self.horizontalLayout_163.addWidget(self.OpenGL_GL_CULL_FACE) + self.horizontalSpacer_132 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + + self.horizontalLayout_163.addItem(self.horizontalSpacer_132) + + self.ogl_quality_label = QLabel(self.groupBox_34) + self.ogl_quality_label.setObjectName(u"ogl_quality_label") + + self.horizontalLayout_163.addWidget(self.ogl_quality_label) + + self.ogl_quality = QSpinBox(self.groupBox_34) + self.ogl_quality.setObjectName(u"ogl_quality") + self.ogl_quality.setMinimum(1) + self.ogl_quality.setMaximum(20) + self.ogl_quality.setValue(10) + + self.horizontalLayout_163.addWidget(self.ogl_quality) + self.verticalLayout_31.addWidget(self.groupBox_34) @@ -6119,14 +6136,14 @@ def setupUi(self, MainWindow): self.frame_83 = QFrame(self.frame_82) self.frame_83.setObjectName(u"frame_83") self.frame_83.setMinimumSize(QSize(0, 30)) - self.frame_83.setFrameShape(QFrame.StyledPanel) + self.frame_83.setFrameShape(QFrame.NoFrame) self.frame_83.setFrameShadow(QFrame.Raised) self.verticalLayout_60.addWidget(self.frame_83) self.frame_95 = QFrame(self.frame_82) self.frame_95.setObjectName(u"frame_95") - self.frame_95.setFrameShape(QFrame.StyledPanel) + self.frame_95.setFrameShape(QFrame.NoFrame) self.frame_95.setFrameShadow(QFrame.Raised) self.horizontalLayout_96 = QHBoxLayout(self.frame_95) self.horizontalLayout_96.setObjectName(u"horizontalLayout_96") @@ -6146,7 +6163,7 @@ def setupUi(self, MainWindow): self.frame_96 = QFrame(self.frame_82) self.frame_96.setObjectName(u"frame_96") self.frame_96.setMinimumSize(QSize(0, 40)) - self.frame_96.setFrameShape(QFrame.StyledPanel) + self.frame_96.setFrameShape(QFrame.NoFrame) self.frame_96.setFrameShadow(QFrame.Raised) self.verticalLayout_60.addWidget(self.frame_96) @@ -7393,6 +7410,7 @@ def retranslateUi(self, MainWindow): self.label_81.setText(QCoreApplication.translate("MainWindow", u"Figures in property", None)) self.groupBox_34.setTitle(QCoreApplication.translate("MainWindow", u"OpenGl", None)) self.OpenGL_GL_CULL_FACE.setText(QCoreApplication.translate("MainWindow", u"GL_CULL_FACE", None)) + self.ogl_quality_label.setText(QCoreApplication.translate("MainWindow", u"Quality", None)) self.groupBox_60.setTitle(QCoreApplication.translate("MainWindow", u"Color atoms according to", None)) self.color_atoms_with_atom_type.setText(QCoreApplication.translate("MainWindow", u"atom type", None)) self.color_atoms_with_property.setText(QCoreApplication.translate("MainWindow", u"property", None)) From 77d2225d3e8916a5496ec30a32c8920f6dfadeb6 Mon Sep 17 00:00:00 2001 From: Sergey Sozykin Date: Sat, 8 Jun 2024 10:09:42 +0500 Subject: [PATCH 9/9] GUI4dft v1.4 --- README.md | 4 ++-- src/qtbased/gui4dftform.py | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e1bc7f8..38025c7 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ v1.2 - Contains some additional features. For example, it allows visualizing the v1.3 - This version uses pyqtgraph instead of matplotlib and pyside2 instead of PyQt5. The colors of the atoms and covalent radii are taken from the ASE module. The project is covered with tests. -v1.4 - This is the next release in 1.x branch. Pyside6. Added support for exporting structural data to input files VASP, CRYSTAL, QE. +v1.4 - Pyside6. Added support for exporting structural data to input files VASP, CRYSTAL, QE. -v1.5 - Python 3.12. +v1.5 - This is the next release in 1.x branch. Python 3.12. The master branch contains more or less stable 1.x version functions. diff --git a/src/qtbased/gui4dftform.py b/src/qtbased/gui4dftform.py index cc524ca..7f49770 100644 --- a/src/qtbased/gui4dftform.py +++ b/src/qtbased/gui4dftform.py @@ -3024,8 +3024,15 @@ def lammps_to_form(self): model = self.models[self.active_model_id] fl = self.ui.lammps_with_charge.isChecked() text = model_to_lammps_input(model, charge=fl) - if len(text) > 0: + if (len(text) > 0) and (len(text) < 10000): self.ui.FormActionsPreTextFDF.setText(text) + if len(text) > 9999: + file_mask = "FDF files (*.fdf);;VASP POSCAR file (*.POSCAR);;Crystal d12 (*.d12)" + file_mask += ";;PWscf in (*.in);;WIEN struct (*.struct);;CIF file (*.cif);;DFTB+ (*.gen)" + file_mask += ";;Lammps input (*.lmp);;Octopus input (*.in)" + f_name = self.get_file_name_from_save_dialog(file_mask) + if f_name is not None: + helpers.write_text_to_file(f_name, text) except Exception as e: self.show_error(e)