Mercurial > repos > muon-spectroscopy-computational-project > larch_feff
diff larch_feff.py @ 0:edf7f8ccf4af draft
planemo upload for repository https://github.com/MaterialsGalaxy/larch-tools/tree/main/larch_feff commit 5be486890442dedfb327289d597e1c8110240735
author | muon-spectroscopy-computational-project |
---|---|
date | Tue, 14 Nov 2023 15:35:09 +0000 |
parents | |
children | 8ee2cc3374fe |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/larch_feff.py Tue Nov 14 15:35:09 2023 +0000 @@ -0,0 +1,165 @@ +import json +import re +import shutil +import sys +from pathlib import Path + +from larch.xafs.feffrunner import feff6l + +from pymatgen.io.cif import CifParser +from pymatgen.io.feff import Atoms, Header, Potential + + +def get_path_labels(paths_file): + is_meta = True + count = 0 + a_path = {} + all_paths = {} + with open(paths_file) as datfile: + dat_lines = datfile.readlines() + for a_line in dat_lines: + count += 1 + if re.match("-{15}", a_line.strip()) is not None: + is_meta = False + elif not is_meta: + if re.match(r"\s*\d*\s{4}\d*\s{3}", a_line) is not None: + if a_path: + all_paths[a_path["index"]] = a_path + line_data = a_line.split() + a_path = { + "index": line_data[0], + "nleg": line_data[1], + "degeneracy": line_data[2], + } + elif ( + re.match(r"\s{6}x\s{11}y\s{5}", a_line) is None + ): # ignore the intermediate headings + line_data = a_line.split() + if "label" not in a_path: + a_path["label"] = line_data[4].replace("'", "") + else: + a_path["label"] += "." + line_data[4].replace("'", "") + if a_path and "index" in a_path: + all_paths[a_path["index"]] = a_path + return all_paths + + +def main(structure_file: str, file_format: dict): + crystal_f = Path(structure_file) + feff_dir = "feff" + feff_inp = "feff.inp" + header_inp = "header" + atoms_inp = "atoms" + potential_inp = "potential" + path = Path(feff_dir, feff_inp) + path.parent.mkdir(parents=True, exist_ok=True) + + if file_format["format"] == "cif": + print(f"Parsing {crystal_f.name} and saving to {path}") + cif_parser = CifParser(crystal_f) + structures = cif_parser.get_structures() + length = len(structures) + if length != 1: + raise ValueError( + f"Execpted single structure in cif file but found {length}" + ) + try: + aborsbing_atom = int(file_format["absorbing_atom"]) + except ValueError: + # aborsbing_atom can be int or chemical symbol + aborsbing_atom = file_format["absorbing_atom"] + + feff_header = Header(structures[0]) + potential = Potential(structures[0], aborsbing_atom) + atoms = Atoms(structures[0], aborsbing_atom, file_format["radius"]) + # if len(atoms.struct.sites) < len(potential.): + + # print(atoms.as_dict()) + feff_header.write_file(header_inp) + potential.write_file(potential_inp) + atoms.write_file(atoms_inp) + with open(path, "w") as feff_inp_file: + with open(header_inp) as f: + header_text = f.read() + print(header_text) + feff_inp_file.write(header_text + "\n") + with open(potential_inp) as f: + potential_text = f.readlines() + print(*potential_text) + feff_inp_file.writelines(potential_text + ["\n"]) + with open(atoms_inp) as f: + atoms_text = f.readlines() + print(*atoms_text) + feff_inp_file.writelines(atoms_text + ["\n"]) + if len(atoms_text) <= len(potential_text): + print( + "WARNING: Every potential in the structure must be represented" + " by at least one atom, consider increasing the radius" + ) + else: + print(f"Copying {crystal_f.name} to {path}") + shutil.copy(crystal_f, path) + + feff6l(folder=feff_dir, feffinp=feff_inp) + + feff_files = "files.dat" + input_file = Path(feff_dir, feff_files) + # the .dat data is stored in fixed width strings + comma_positions = [13, 21, 32, 41, 48, 61] + paths_data = [] + # get the list of paths info to assign labels to paths + try: + paths_info = get_path_labels(Path(feff_dir, "paths.dat")) + except FileNotFoundError as err: + raise FileNotFoundError( + "paths.dat does not exist, which implies FEFF failed to run" + ) from err + + print("Reading from: " + str(input_file)) + with open(input_file) as datfile: + # Read until we find the line at the end of the header + line = datfile.readline() + while not re.match("-+", line.strip()): + line = datfile.readline() + + # Build headers + line = datfile.readline() + header = "" + start = 0 + for end in comma_positions: + header += line[start:end] + "," + start = end + header += f" {'label':30s}, {'select':6s}\n" + paths_data.append(header) + + # Read data + line = datfile.readline() + while line: + data = "" + start = 0 + for end in comma_positions[:-1]: + data += line[start:end] + "," + start = end + + # Last column needs padding to align + data += line[start:-1] + " ," + + # Add label and select column + path_id = int(data[5:9]) + try: + label = paths_info[str(path_id)]["label"] + f".{path_id}" + except KeyError as err: + msg = f"{path_id} not in {paths_info.keys()}" + raise KeyError(msg) from err + data += f" {label:30s}, {0:6d}\n" + paths_data.append(data) + line = datfile.readline() + + with open("out.csv", "w") as out: + out.writelines(paths_data) + + +if __name__ == "__main__": + structure_file = sys.argv[1] + input_values = json.load(open(sys.argv[2], "r", encoding="utf-8")) + main(structure_file, input_values["format"])