Mercurial > repos > muon-spectroscopy-computational-project > larch_feff
comparison 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 |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:edf7f8ccf4af |
|---|---|
| 1 import json | |
| 2 import re | |
| 3 import shutil | |
| 4 import sys | |
| 5 from pathlib import Path | |
| 6 | |
| 7 from larch.xafs.feffrunner import feff6l | |
| 8 | |
| 9 from pymatgen.io.cif import CifParser | |
| 10 from pymatgen.io.feff import Atoms, Header, Potential | |
| 11 | |
| 12 | |
| 13 def get_path_labels(paths_file): | |
| 14 is_meta = True | |
| 15 count = 0 | |
| 16 a_path = {} | |
| 17 all_paths = {} | |
| 18 with open(paths_file) as datfile: | |
| 19 dat_lines = datfile.readlines() | |
| 20 for a_line in dat_lines: | |
| 21 count += 1 | |
| 22 if re.match("-{15}", a_line.strip()) is not None: | |
| 23 is_meta = False | |
| 24 elif not is_meta: | |
| 25 if re.match(r"\s*\d*\s{4}\d*\s{3}", a_line) is not None: | |
| 26 if a_path: | |
| 27 all_paths[a_path["index"]] = a_path | |
| 28 line_data = a_line.split() | |
| 29 a_path = { | |
| 30 "index": line_data[0], | |
| 31 "nleg": line_data[1], | |
| 32 "degeneracy": line_data[2], | |
| 33 } | |
| 34 elif ( | |
| 35 re.match(r"\s{6}x\s{11}y\s{5}", a_line) is None | |
| 36 ): # ignore the intermediate headings | |
| 37 line_data = a_line.split() | |
| 38 if "label" not in a_path: | |
| 39 a_path["label"] = line_data[4].replace("'", "") | |
| 40 else: | |
| 41 a_path["label"] += "." + line_data[4].replace("'", "") | |
| 42 if a_path and "index" in a_path: | |
| 43 all_paths[a_path["index"]] = a_path | |
| 44 return all_paths | |
| 45 | |
| 46 | |
| 47 def main(structure_file: str, file_format: dict): | |
| 48 crystal_f = Path(structure_file) | |
| 49 feff_dir = "feff" | |
| 50 feff_inp = "feff.inp" | |
| 51 header_inp = "header" | |
| 52 atoms_inp = "atoms" | |
| 53 potential_inp = "potential" | |
| 54 path = Path(feff_dir, feff_inp) | |
| 55 path.parent.mkdir(parents=True, exist_ok=True) | |
| 56 | |
| 57 if file_format["format"] == "cif": | |
| 58 print(f"Parsing {crystal_f.name} and saving to {path}") | |
| 59 cif_parser = CifParser(crystal_f) | |
| 60 structures = cif_parser.get_structures() | |
| 61 length = len(structures) | |
| 62 if length != 1: | |
| 63 raise ValueError( | |
| 64 f"Execpted single structure in cif file but found {length}" | |
| 65 ) | |
| 66 try: | |
| 67 aborsbing_atom = int(file_format["absorbing_atom"]) | |
| 68 except ValueError: | |
| 69 # aborsbing_atom can be int or chemical symbol | |
| 70 aborsbing_atom = file_format["absorbing_atom"] | |
| 71 | |
| 72 feff_header = Header(structures[0]) | |
| 73 potential = Potential(structures[0], aborsbing_atom) | |
| 74 atoms = Atoms(structures[0], aborsbing_atom, file_format["radius"]) | |
| 75 # if len(atoms.struct.sites) < len(potential.): | |
| 76 | |
| 77 # print(atoms.as_dict()) | |
| 78 feff_header.write_file(header_inp) | |
| 79 potential.write_file(potential_inp) | |
| 80 atoms.write_file(atoms_inp) | |
| 81 with open(path, "w") as feff_inp_file: | |
| 82 with open(header_inp) as f: | |
| 83 header_text = f.read() | |
| 84 print(header_text) | |
| 85 feff_inp_file.write(header_text + "\n") | |
| 86 with open(potential_inp) as f: | |
| 87 potential_text = f.readlines() | |
| 88 print(*potential_text) | |
| 89 feff_inp_file.writelines(potential_text + ["\n"]) | |
| 90 with open(atoms_inp) as f: | |
| 91 atoms_text = f.readlines() | |
| 92 print(*atoms_text) | |
| 93 feff_inp_file.writelines(atoms_text + ["\n"]) | |
| 94 if len(atoms_text) <= len(potential_text): | |
| 95 print( | |
| 96 "WARNING: Every potential in the structure must be represented" | |
| 97 " by at least one atom, consider increasing the radius" | |
| 98 ) | |
| 99 else: | |
| 100 print(f"Copying {crystal_f.name} to {path}") | |
| 101 shutil.copy(crystal_f, path) | |
| 102 | |
| 103 feff6l(folder=feff_dir, feffinp=feff_inp) | |
| 104 | |
| 105 feff_files = "files.dat" | |
| 106 input_file = Path(feff_dir, feff_files) | |
| 107 # the .dat data is stored in fixed width strings | |
| 108 comma_positions = [13, 21, 32, 41, 48, 61] | |
| 109 paths_data = [] | |
| 110 # get the list of paths info to assign labels to paths | |
| 111 try: | |
| 112 paths_info = get_path_labels(Path(feff_dir, "paths.dat")) | |
| 113 except FileNotFoundError as err: | |
| 114 raise FileNotFoundError( | |
| 115 "paths.dat does not exist, which implies FEFF failed to run" | |
| 116 ) from err | |
| 117 | |
| 118 print("Reading from: " + str(input_file)) | |
| 119 with open(input_file) as datfile: | |
| 120 # Read until we find the line at the end of the header | |
| 121 line = datfile.readline() | |
| 122 while not re.match("-+", line.strip()): | |
| 123 line = datfile.readline() | |
| 124 | |
| 125 # Build headers | |
| 126 line = datfile.readline() | |
| 127 header = "" | |
| 128 start = 0 | |
| 129 for end in comma_positions: | |
| 130 header += line[start:end] + "," | |
| 131 start = end | |
| 132 header += f" {'label':30s}, {'select':6s}\n" | |
| 133 paths_data.append(header) | |
| 134 | |
| 135 # Read data | |
| 136 line = datfile.readline() | |
| 137 while line: | |
| 138 data = "" | |
| 139 start = 0 | |
| 140 for end in comma_positions[:-1]: | |
| 141 data += line[start:end] + "," | |
| 142 start = end | |
| 143 | |
| 144 # Last column needs padding to align | |
| 145 data += line[start:-1] + " ," | |
| 146 | |
| 147 # Add label and select column | |
| 148 path_id = int(data[5:9]) | |
| 149 try: | |
| 150 label = paths_info[str(path_id)]["label"] + f".{path_id}" | |
| 151 except KeyError as err: | |
| 152 msg = f"{path_id} not in {paths_info.keys()}" | |
| 153 raise KeyError(msg) from err | |
| 154 data += f" {label:30s}, {0:6d}\n" | |
| 155 paths_data.append(data) | |
| 156 line = datfile.readline() | |
| 157 | |
| 158 with open("out.csv", "w") as out: | |
| 159 out.writelines(paths_data) | |
| 160 | |
| 161 | |
| 162 if __name__ == "__main__": | |
| 163 structure_file = sys.argv[1] | |
| 164 input_values = json.load(open(sys.argv[2], "r", encoding="utf-8")) | |
| 165 main(structure_file, input_values["format"]) |
