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"]) |