Skip to content

Commit b959aa4

Browse files
author
David Turner
committed
Cleaned up some of the XSPEC execute_cmd function, and made sure that the new line in the XSPEC script that errors with very poor spectra is not raised as an error unless it is actually called.
1 parent 96bc5f3 commit b959aa4

File tree

1 file changed

+21
-20
lines changed

1 file changed

+21
-20
lines changed

xga/xspec/run.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# This code is a part of X-ray: Generate and Analyse (XGA), a module designed for the XMM Cluster Survey (XCS).
2-
# Last modified by David J Turner (turne540@msu.edu) 07/08/2025, 16:40. Copyright (c) The Contributors
2+
# Last modified by David J Turner (turne540@msu.edu) 09/12/2025, 17:17. Copyright (c) The Contributors
33

44
import os
5-
import warnings
65
from functools import wraps
76
# from multiprocessing.dummy import Pool
87
from multiprocessing import Pool
@@ -18,10 +17,12 @@
1817
from tqdm import tqdm
1918

2019
from .. import XSPEC_VERSION
21-
from ..exceptions import XSPECFitError, MultipleMatchError, NoMatchFoundError, XSPECNotFoundError, XGADeveloperError
20+
from ..exceptions import MultipleMatchError, NoMatchFoundError, XSPECNotFoundError, XGADeveloperError
2221
from ..samples.base import BaseSample
2322
from ..sources import BaseSource
2423

24+
XGA_XSPEC_ERRS = ["No acceptable spectra are left after the minimum channel step",
25+
"No acceptable spectra are left after the cleaning step"]
2526

2627
def execute_cmd(x_script: str, out_file: str, src: str, run_type: str, timeout: float) \
2728
-> Tuple[Union[FITS, str], str, bool, list, list, str]:
@@ -45,7 +46,7 @@ def execute_cmd(x_script: str, out_file: str, src: str, run_type: str, timeout:
4546
usable = True
4647

4748
# We're going to make a temporary pfiles directory which is a) local to the XGA directory, and thus sure to
48-
# be on the same filesystem (can be a performance issue for HPCs I think), and b) is unique to a particular
49+
# be on the same filesystem (can be a performance issue for HPCs I think); and b) is unique to a particular
4950
# fit process, so there shouldn't be any clashes. The temporary file name is randomly generated
5051
tmp_ident = str(randint(0, int(100_000_000)))
5152
tmp_hea_dir = os.path.join(os.path.dirname(out_file), tmp_ident, 'pfiles/')
@@ -65,7 +66,7 @@ def execute_cmd(x_script: str, out_file: str, src: str, run_type: str, timeout:
6566
out, err = xspec_proc.communicate()
6667
# Need to infer the name of the source to supply it in the warning
6768
source_name = x_script.split('/')[-1].split("_")[0]
68-
warnings.warn("An XSPEC fit for {} has timed out".format(source_name), stacklevel=2)
69+
warn("An XSPEC fit for {} has timed out".format(source_name), stacklevel=2)
6970
usable = False
7071

7172
out = out.decode("UTF-8").split("\n")
@@ -77,8 +78,8 @@ def execute_cmd(x_script: str, out_file: str, src: str, run_type: str, timeout:
7778
# We ignore that particular string in the errors identified from stdout because if we don't just it being
7879
# present in the if statement in the executed script is enough to make XGA think that the fit failed, even if
7980
# that error message was never printed at all
80-
err_out_lines = [line.split("***Error: ")[-1] for line in out if "***Error" in line
81-
if "No acceptable spectra are left after the cleaning step" not in line]
81+
err_out_lines = [line.split("***Error: ")[-1] for line in out
82+
if "***Error" in line and all([xxe not in line for xxe in XGA_XSPEC_ERRS])]
8283
err_out_line_init = [line for line in out if "You do not have an up-to-date version of Xspec.init" in line]
8384
warn_out_lines = [line.split("***Warning: ")[-1] for line in out if "***Warning" in line]
8485
err_err_lines = [line.split("***Error: ")[-1] for line in err if "***Error" in line]
@@ -89,8 +90,8 @@ def execute_cmd(x_script: str, out_file: str, src: str, run_type: str, timeout:
8990
else:
9091
usable = False
9192

92-
error = err_out_lines + err_err_lines + err_out_line_init
93-
warn = warn_out_lines + warn_err_lines
93+
cur_error = err_out_lines + err_err_lines + err_out_line_init
94+
cur_warn = warn_out_lines + warn_err_lines
9495

9596
if os.path.exists(out_file + "_info.csv") and run_type == "fit" and usable:
9697
# The original version of the xga_output.tcl script output everything as one nice neat fits file
@@ -132,28 +133,29 @@ def execute_cmd(x_script: str, out_file: str, src: str, run_type: str, timeout:
132133
elif not os.path.exists(out_file):
133134
usable = False
134135
res_tables = None
135-
error.append('Results table not written.')
136+
cur_error.append('Results table not written.')
136137
else:
137138
res_tables = None
138139
usable = False
139140

140-
# This uses outfile name (which has a structure set by XGA, so this is reliable) to figure out which telescope
141-
# this script was run for - this isn't necessarily how I would have designed it from the beginning, but is a
142-
# good solution to get the telescope which doesn't require adding stuff to all the user-facing XSPEC functions
141+
# This uses outfile name (has structure set by XGA, so this is reliable) to
142+
# figure out which telescope this script was run for. This isn't necessarily
143+
# how I would have designed it from the beginning, but is a good solution to get
144+
# the telescope which doesn't require adding stuff to all the user-facing
145+
# XSPEC functions
143146
if run_type == "conv_factors":
144147
tel = out_file.split('_')[-3]
145148
else:
146149
tel = out_file.split('_')[-1].split('.')[0]
147150

148-
return res_tables, src, usable, error, warn, tel
151+
return res_tables, src, usable, cur_error, cur_warn, tel
149152

150153

151154
def xspec_call(xspec_func):
152155
"""
153156
This is used as a decorator for functions that produce XSPEC scripts. Depending on the
154157
system that XGA is running on (and whether the user requests parallel execution), the method of
155158
executing the XSPEC commands will change. This supports multi-threading.
156-
:return:
157159
"""
158160

159161
@wraps(xspec_func)
@@ -200,14 +202,14 @@ def wrapper(*args, **kwargs):
200202
with tqdm(total=len(script_list), desc=desc) as fit, Pool(cores) as pool:
201203
def callback(results_in):
202204
"""
203-
Callback function for the apply_async pool method, gets called when a task finishes
204-
and something is returned.
205+
Callback function for the apply_async pool method which gets
206+
called when a task finishes and something is returned.
205207
"""
206208
nonlocal fit # The progress bar will need updating
207209
nonlocal results # The dictionary the command call results are added to
208210

209-
res_fits, rel_src, successful, err_list, warn_list, tel = results_in
210-
results[rel_src].append([res_fits, successful, err_list, warn_list, tel])
211+
res_fits, rel_src, successful, cur_err_list, cur_warn_list, cur_tel = results_in
212+
results[rel_src].append([res_fits, successful, cur_err_list, cur_warn_list, cur_tel])
211213
fit.update(1)
212214

213215
for s_ind, s in enumerate(script_list):
@@ -238,7 +240,6 @@ def callback(results_in):
238240
for res_set in results[src_repr]:
239241
# res_set[0] = res_table, if it is None then the xspec fit has failed
240242
if res_set[0] is None:
241-
print(res_set)
242243
for err in res_set[2]:
243244
errors_all_sources.append(err + " - {s}".format(s=s.name))
244245
# Extract the telescope from the information passed back by the running of the fit

0 commit comments

Comments
 (0)