Source code for htesp.structure_group
#!/usr/bin/env python
#"""Written by Niraj K. Nepal, Ph.D."""
"""Module to group structures from different database"""
# coding: utf-8
import glob
import os
import shutil
import warnings
from pymatgen.core import Structure
from pymatgen.io.pwscf import PWInput
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
from pymatgen.analysis.structure_matcher import StructureMatcher
[docs]
def read_structure_file(filepath):
"""
Read structure from a file.
Args:
filepath (str): Path to the structure file.
Returns:
Structure: Pymatgen Structure object.
"""
# FIX(35): read a Quantum ESPRESSO input directly rather than letting
# pymatgen guess from the extension. Its handler registry ends with a
# deliberately generic ``("fleur-inpgen", "*.in*")`` catch-all, so every
# scf-<mpid>.in matches Fleur and Structure.from_file() raises
# ``ModuleNotFoundError: No module named 'pymatgen.io.fleur'`` -- which is
# an ImportError, not one of the exceptions caught below, so the QE
# fallback never ran and `mainprogram data-combine` died.
if str(filepath).endswith(".in"):
try:
return PWInput.from_file(filepath).structure
except (OSError, ValueError, IndexError, KeyError):
pass # not a QE input after all; let the generic path try
try:
return Structure.from_file(filepath)
except (OSError, ValueError, ImportError):
# FIX(17): PWInput's constructor takes a Structure, not a path -- the
# old call built a PWInput whose ``structure`` attribute was the file
# name string, and the ``.structure`` access below then returned that
# string instead of a Structure.
# FIX(35): ImportError is caught too -- see above.
return PWInput.from_file(filepath).structure
[docs]
def create_structure_list(input_file):
"""
Create a list of structures from the input file.
Args:
input_file (str): Path to the input file.
Returns:
list: List of Pymatgen Structure objects.
"""
struc_list = []
with open(input_file, "r") as read_input:
lines = read_input.readlines()
for line in lines:
fields = line.split()
if len(fields) < 3:
warnings.warn("ignoring malformed line {!r} in {}".format(
line.strip(), input_file), RuntimeWarning)
continue
mpid = fields[1]
compound = fields[2].strip()
struc_path = f"R{mpid}-{compound}/relax/POSCAR"
try:
struc = read_structure_file(struc_path)
except (OSError, ValueError):
# FIX(17): the fallback used to be guarded by a bare ``except:``,
# which also swallowed KeyboardInterrupt.
struc_path = f"scf_dir/scf-{mpid}.in"
struc = read_structure_file(struc_path)
struc = SpacegroupAnalyzer(structure=struc, symprec=0.1).get_primitive_standard_structure()
site_prop = [mpid] * len(struc.sites)
struc.add_site_property("id", site_prop)
struc_list.append(struc)
return struc_list
[docs]
def write_unique_structures(unique_structures, output_file):
"""
Write unique structures to an output file and copy files to a directory.
Args:
unique_structures (list): List of unique Pymatgen Structure objects.
output_file (str): Path to the output file.
"""
os.makedirs("filtered_inputs", exist_ok=True)
with open(output_file, "w") as write_mpid:
for i, group in enumerate(unique_structures):
extract_struc = group[0]
mpid = extract_struc.site_properties['id'][0]
comp = str(extract_struc.composition).replace(" ", "")
write_mpid.write(f"v{i+1} {mpid} {comp}\n")
# ``cp -r R{mpid}*`` went through a shell with the id interpolated
# into it, and its ``except FileNotFoundError`` could never fire
# because os.system reports failure through a return code.
matches = sorted(glob.glob("R{}*".format(mpid)))
if matches:
for source in matches:
dest = os.path.join("filtered_inputs", os.path.basename(source))
if os.path.isdir(source):
shutil.copytree(source, dest, dirs_exist_ok=True)
else:
shutil.copy(source, dest)
continue
scf_input = f"scf_dir/scf-{mpid}.in"
if os.path.isfile(scf_input):
shutil.copy(scf_input, "filtered_inputs/")
else:
warnings.warn(
"nothing to copy for {}: neither R{}* nor {}".format(
mpid, mpid, scf_input), RuntimeWarning)
[docs]
def main():
"""
Main function to process structures from an input file, group them into unique structures,
and write the results to an output file.
Reads structure information from the 'mpid.in' file, creates a list of structures,
groups them into unique structures using StructureMatcher, and writes the unique structures
along with associated metadata to the 'mpid-new.in' file.
Returns:
None
"""
input_file = "mpid.in"
struc_list = create_structure_list(input_file)
unique_structures = StructureMatcher().group_structures(struc_list)
output_file = "mpid-new.in"
write_unique_structures(unique_structures, output_file)
print("Check filtered_inputs folder and mpid-new.in file\n")
if __name__ == "__main__":
main()