comparison build_file.py @ 0:c70012022f0f draft

planemo upload for repository https://github.com/muon-spectroscopy-computational-project/muon-galaxy-tools/main/muspinsim_config commit d130cf2c46d933fa9d0214ddbd5ddf860f322dc4
author muon-spectroscopy-computational-project
date Thu, 25 Aug 2022 16:16:47 +0000 (2022-08-25)
parents
children 331d0776abb4
comparison
equal deleted inserted replaced
-1:000000000000 0:c70012022f0f
1 import json
2 import re
3 import sys
4
5 from muspinsim import MuSpinInput
6
7
8 def write_file(file_name, content):
9 """
10 Write muspinsim file
11 :param file_name: name of file
12 :param content: list of strings containing blocks to write
13 """
14 with open(file_name, "w") as f:
15 f.write(
16 """
17 #######################################################
18 #Muspinsim Input File
19 #Generated using Muon Galaxy Tool Muspinsim_Input
20 #######################################################\n\n"""
21 )
22 f.write("".join(content))
23
24
25 def build_block(title, vals):
26 """
27 Build keyword block
28 :param title: string - Keyword
29 :param vals: list of strings - lines containing values for keyword
30 :return: A string containing formatted keyword block
31 """
32 return "{0}\n {1}\n".format(title, "\n ".join(vals))
33
34
35 def format_entry(entry):
36 """
37 Helper function to remove whitespace between function parameters
38 and remove ',' or ';' inbetween parameters
39 :param entry: string - user entry
40 :return: string containing only valid parameters
41 """
42 stck = []
43 new_str = ""
44 for i, char in enumerate(entry):
45 if char == "(":
46 stck.append(i)
47 elif char == ")":
48 if len(stck) == 0:
49 raise ValueError(
50 "Could not parse entry {0}"
51 "brackets mismatch - unexpected ')' "
52 "found on char {1}".format(entry, i)
53 )
54 stck.pop()
55 elif char == " " and len(stck) > 0:
56 continue
57
58 # remove ',' between functions
59 elif char in [",", ";"] and len(stck) == 0:
60 new_str += " "
61 continue
62 new_str += char
63
64 if len(stck) != 0:
65 raise ValueError(
66 "Could not parse entry {0}"
67 "brackets mismatch - unclosed '(' found on char(s): {1}".format(
68 entry, stck
69 )
70 )
71 return new_str
72
73
74 def split_into_args(entry, nargs=1):
75 """
76 Helper function to split input into a list of args
77 :param entry: a string containing a user inputted line
78 :param nargs: number of expected arguments
79 :return: a list of arguments found
80 :exception: ValueError - if number of arguments
81 found does not match expected (nargs)
82 """
83
84 # remove square brackets and extra whitespace/newline
85 content = " ".join(entry.replace("[", "").replace("]", "").split())
86
87 # remove whitespace in between expressions/functions
88 # remove commas/semicolons in between expressions/functions
89 # split on whitespace to separate args
90
91 content = re.split(r"\s", format_entry(content))
92 chars = [elem.strip() for elem in content if elem != ""]
93 if len(chars) != nargs:
94 raise ValueError(
95 "Could not parse entry {0}"
96 " incorrect number of args"
97 " found {1}:\n({2})\nBut expected {3}".format(
98 entry, len(chars), chars, nargs
99 )
100 )
101 return chars
102
103
104 def parse_matrix(entry_string, size):
105 """
106 Helper function to parse and format matrix/vector
107 to be readable by Muspinsim
108 :param entry_string: a user input string for a matrix/vector
109 :param size: (x, y) integer tuple: dimensions of matrix
110 :return: a list of strings of length y, each string
111 containing x elements (space separated)
112 """
113 content = split_into_args(entry_string, nargs=size[0] * size[1])
114 return [
115 " ".join(content[x: x + size[0]])
116 for x in range(0, len(content), size[0])
117 ]
118
119
120 def parse_interactions(interaction):
121 """
122 Helper function to build keyword blocks for all
123 interaction parameters entered
124 (hyperfine, zeeman, dipolar, quadrupolar and dissipation)
125
126 :param interaction: a dictionary containing all interaction parameters
127 :return: a string containing several formatted blocks
128 """
129
130 options = interaction["interaction_options"]
131 interaction_type = options["interaction"]
132 try:
133 return {
134 "zeeman": lambda options: build_block(
135 "zeeman {0}".format(options["zeeman_index"]),
136 parse_matrix(options["zeeman_vector"], (3, 1)),
137 ),
138 "hyperfine": lambda options: build_block(
139 "hyperfine {0} {1}".format(
140 options["hfine_index"],
141 options["hfine_e_index"]
142 if options["hfine_e_index"]
143 else "",
144 ).strip(),
145 parse_matrix(options["hfine_matrix"], (3, 3)),
146 ),
147 "dipolar": lambda options: build_block(
148 "dipolar {0} {1}".format(
149 options["di_index"], options["di_index_2"]
150 ),
151 parse_matrix(options["di_vector"], (3, 1)),
152 ),
153 "quadrupolar": lambda options: build_block(
154 "quadrupolar {0}".format(options["quad_index"]),
155 parse_matrix(options["quad_matrix"], (3, 3)),
156 ),
157 "dissipation": lambda options: build_block(
158 "dissipation {0}".format(options["dis_index"]),
159 [options["dis_val"]],
160 ),
161 }.get(interaction_type)(options)
162 except ValueError as e:
163 raise ValueError("Error occurred when parsing {0}".format(e))
164
165
166 def parse_orientation(orientation):
167 """
168 Helper function to parse orientation keyword arguments
169 :param orientation: a dictionary containing one set of
170 orientation arguments
171 :return: a formatted string
172 """
173
174 options = orientation["orientation_options"]
175 preset = options["orientation_preset"]
176
177 return {
178 "zcw": lambda options: "zcw({0})".format(
179 " ".join(split_into_args(options["zcw_n"], 1))
180 ),
181 "eulrange": lambda options: "eulrange({0})".format(
182 " ".join(split_into_args(options["eul_n"], 1))
183 ),
184 "2_polar": lambda options: "{0} {1}".format(
185 " ".join(split_into_args(options["theta"], 1)),
186 " ".join(split_into_args(options["phi"], 1)),
187 ),
188 "3_euler": lambda options: "{0} {1} {2}".format(
189 " ".join(split_into_args(options["eul_1"], 1)),
190 " ".join(split_into_args(options["eul_2"], 1)),
191 " ".join(split_into_args(options["eul_3"], 1)),
192 ),
193 "4_euler": lambda options: "{0} {1} {2} {3}".format(
194 " ".join(split_into_args(options["eul_1"], 1)),
195 " ".join(split_into_args(options["eul_2"], 1)),
196 " ".join(split_into_args(options["eul_3"], 1)),
197 options["weight"],
198 ),
199 }.get(preset)(options)
200
201
202 def parse_polarization(polarization):
203 """
204 Helper function to parse polarization keyword arguments
205 :param polarization: a dictionary containing one set
206 of polarization arguments
207 :return: a formatted string
208 """
209 options = polarization["polarization_options"]
210 preset = options["polarization_preset"]
211 if preset != "custom":
212 return preset
213 else:
214 try:
215 return " ".join(split_into_args(options["polarization"], 1))
216 except ValueError:
217 return " ".join(split_into_args(options["polarization"], 3))
218
219
220 def parse_field(field):
221 """
222 Helper function to parse field keyword arguments
223 :param field: a dictionary containing one set of field arguments
224 :return: a formatted string
225 """
226 try:
227 return " ".join(split_into_args(field["field"], 1))
228 except ValueError:
229 return " ".join(split_into_args(field["field"], 3))
230
231
232 def parse_fitting_variables(fitting_variables):
233 """
234 Helper function to parse field keyword fitting_variables
235 :param fitting_variables: a dictionary containing one set of
236 arguments
237 :return: a formatted string
238 """
239 return "{0} {1} {2} {3}".format(
240 fitting_variables["var_name"].strip().replace(" ", "_"),
241 " ".join(split_into_args(fitting_variables["start_val"], 1))
242 if fitting_variables["start_val"].strip() != ""
243 else "",
244 " ".join(split_into_args(fitting_variables["min_bound"], 1))
245 if fitting_variables["min_bound"].strip() != ""
246 else "",
247 " ".join(split_into_args(fitting_variables["max_bound"], 1))
248 if fitting_variables["max_bound"].strip() != ""
249 else "",
250 ).strip()
251
252
253 def parse_spin(spin):
254 if spin["spin_preset"] != "custom":
255 return spin["spin_preset"]
256 else:
257 elem_name = spin["spin"].strip()
258 if elem_name not in ['e', 'mu']:
259 elem_name = elem_name.capitalize()
260 return "{0}{1}".format(
261 int(spin["atomic_mass"]) if spin["atomic_mass"] else "",
262 elem_name
263 ).strip()
264
265
266 parse_func_dict = {
267 "spins": lambda values: build_block(
268 "spins",
269 [
270 " ".join(
271 [
272 parse_spin(entry["spin_options"])
273 for entry in values
274 ]
275 )
276 ],
277 ),
278 # either 1x3 vector or scalar or function
279 "fields": lambda values: build_block(
280 "field", [parse_field(entry) for entry in values]
281 ),
282 # either scalar or single function
283 "times": lambda values: build_block(
284 "time",
285 [
286 " ".join(split_into_args(entry["time"], 1))
287 for entry in values
288 ],
289 ),
290 # either scalar or single function
291 "temperatures": lambda values: build_block(
292 "temperature",
293 [
294 " ".join(split_into_args(entry["temperature"], 1))
295 for entry in values
296 ],
297 ),
298 "x_axis": lambda value: build_block("x_axis", [value]),
299 "y_axis": lambda value: build_block("y_axis", [value]),
300 "average_axes": lambda values: build_block(
301 "average_axes", values
302 ),
303 "experiment_preset": lambda value: build_block(
304 "experiment", [value]
305 ),
306 "orientations": lambda values: build_block(
307 "orientation {0}".format(euler_convention),
308 [parse_orientation(entry) for entry in values],
309 ),
310 "interactions": lambda values: "".join(
311 [parse_interactions(entry) for entry in values]
312 ),
313 "polarizations": lambda values: build_block(
314 "polarization",
315 [parse_polarization(entry) for entry in values],
316 ),
317 "fitting": lambda value: build_block(
318 "fitting_data", ['load("fitting_data.dat")']
319 ),
320 "fitting_method": lambda value: build_block(
321 "fitting_method", [value]
322 ),
323 "fitting_variables": lambda values: build_block(
324 "fitting_variables",
325 [parse_fitting_variables(entry) for entry in values],
326 ),
327 "fitting_tolerance": lambda value: build_block(
328 "fitting_tolerance",
329 [str(value)],
330 ),
331 }
332 euler_convention = 'ZYZ'
333
334
335 def main():
336 input_json_path = sys.argv[1]
337 mu_params = json.load(open(input_json_path, "r"))
338
339 out_file_name = mu_params["out_file_prefix"].strip().replace(" ", "_")
340
341 # combine all sections
342 mu_params = {
343 **mu_params["spins"],
344 **mu_params["interaction_params"],
345 **mu_params["experiment_params"],
346 **mu_params["fitting_params"]["fitting_options"],
347 }
348
349 # get experiment parameters
350 experiment = mu_params["experiment"]
351 mu_params = {**mu_params, **experiment}
352
353 if experiment["experiment_preset"] == "custom":
354 del mu_params["experiment_preset"]
355 del mu_params["experiment"]
356
357 global euler_convention
358 euler_convention = mu_params["euler_convention"]
359
360 err_found = False
361 file_contents = [
362 build_block("name", [out_file_name.strip().replace(" ", "_")])
363 ]
364 for keyword, val in mu_params.items():
365 if val and val not in ["None"]:
366 try:
367 keyword_func = parse_func_dict.get(keyword)
368 if keyword_func:
369 file_contents.append(keyword_func(val))
370
371 except ValueError as e:
372 sys.stderr.write(
373 "Error occurred when parsing {0}\n{1}".format(
374 keyword, str(e)
375 )
376 )
377 err_found = True
378
379 if err_found:
380 sys.exit(1)
381
382 write_file("outfile.in", file_contents)
383
384 try:
385 MuSpinInput(open("outfile.in"))
386 except Exception as e:
387 sys.stdout.write(
388 "Warning, This created file may not work properly. "
389 "Error(s) encountered when trying to parse the file : {0}".format(
390 str(e)
391 )
392 )
393 sys.exit(1)
394
395
396 if __name__ == "__main__":
397 main()