From 824f39a0772fa2d4032a1e48e492c9abf99522dc Mon Sep 17 00:00:00 2001 From: Sahil Jhawar Date: Sat, 21 Sep 2024 16:52:24 +0200 Subject: [PATCH 1/3] filter dependent and time dependent systematic errors --- nmma/em/analysis.py | 7 + nmma/em/likelihood.py | 105 +++++++- nmma/em/prior.py | 25 ++ nmma/em/systematics.py | 179 +++++++++++++ nmma/tests/analysis.py | 21 +- nmma/tests/data/systematics_with_time.yaml | 14 ++ nmma/tests/data/systematics_without_time.yaml | 14 ++ nmma/tests/systematics.py | 237 ++++++++++++++++++ priors/systematics.yaml | 19 ++ 9 files changed, 606 insertions(+), 15 deletions(-) create mode 100644 nmma/em/systematics.py create mode 100644 nmma/tests/data/systematics_with_time.yaml create mode 100644 nmma/tests/data/systematics_without_time.yaml create mode 100644 nmma/tests/systematics.py create mode 100644 priors/systematics.yaml diff --git a/nmma/em/analysis.py b/nmma/em/analysis.py index 2fde1856..713cc663 100644 --- a/nmma/em/analysis.py +++ b/nmma/em/analysis.py @@ -420,6 +420,12 @@ def get_parser(**kwargs): action="store_true", default=False, ) + parser.add_argument( + "--systematics-file", + metavar="PATH", + help="Path to systematics configuration file", + default=None, + ) return parser @@ -771,6 +777,7 @@ def analysis(args): error_budget=error_budget, verbose=args.verbose, detection_limit=args.detection_limit, + systematics_file=args.systematics_file ) likelihood = OpticalLightCurve(**likelihood_kwargs) diff --git a/nmma/em/likelihood.py b/nmma/em/likelihood.py index cc62ee8a..2d768d44 100644 --- a/nmma/em/likelihood.py +++ b/nmma/em/likelihood.py @@ -4,10 +4,12 @@ import scipy.stats from scipy.interpolate import interp1d from scipy.stats import truncnorm +from pathlib import Path +import yaml -from bilby.core.likelihood import Likelihood -from . import utils +from bilby.core.likelihood import Likelihood +from . import utils, systematics def truncated_gaussian(m_det, m_err, m_est, lim): @@ -55,6 +57,7 @@ def __init__( filters, light_curve_data, trigger_time, + systematics_file, detection_limit=None, error_budget=1.0, tmin=0.0, @@ -81,6 +84,7 @@ def __init__( light_curve_data, self.filters, self.trigger_time, self.tmin, self.tmax ) self.light_curve_data = processedData + self.systematics_file = systematics_file self.detection_limit = {} if detection_limit: @@ -144,6 +148,40 @@ def log_likelihood(self): bounds_error=False, ) + if self.systematics_file is not None: + yaml_dict = yaml.safe_load(Path(self.systematics_file).read_text()) + systematics.validate_only_one_true(yaml_dict) + + if yaml_dict["config"]["withTime"]["value"]: + n = yaml_dict["config"]["withTime"]["time_nodes"] + time_array = np.round(np.linspace(self.tmin, self.tmax, n), 2) + yaml_filters = yaml_dict["config"]["withTime"]["filters"] + systematics.validate_filters(yaml_filters) + + for filter_group in yaml_filters: + if isinstance(filter_group, list): + filt = "___".join(filter_group) + elif filter_group is None: + filt = "all" + else: + filt = filter_group + + globals()[f"sys_err_{filt}_array"] = np.array([]) + + for i in range(1, n + 1): + value = self.parameters.get(f"sys_err_{filt}{i}") + globals()[f"sys_err_{filt}_array"] = np.append(globals()[f"sys_err_{filt}_array"], value) + + for filter_group in yaml_dict["config"]["withTime"]["filters"]: + if isinstance(filter_group, list): + filt = "___".join(filter_group) + elif filter_group is None: + filt = "all" + else: + filt = filter_group + globals()[f"sys_err_{filt}_interped"] = interp1d(time_array, globals()[f"sys_err_{filt}_array"], fill_value="extrapolate", bounds_error=False) + + # compare the estimated light curve and the measured data minus_chisquare_total = 0.0 gaussprob_total = 0.0 @@ -153,9 +191,31 @@ def log_likelihood(self): data_mag = self.light_curve_data[filt][:, 1] data_sigma = self.light_curve_data[filt][:, 2] + if self.systematics_file is not None: + if yaml_dict["config"]["withTime"]["value"]: + yaml_filters = yaml_dict["config"]["withTime"]["filters"] + + filter_group_finite_idx_match = False + + if yaml_filters is not None and yaml_filters != []: + for yaml_filt in yaml_filters: + if yaml_filt is not None and filt in yaml_filt: + if len(yaml_filt) == 1: + filters_to_use = yaml_filt[0] + else: + filters_to_use = "___".join(yaml_filt) + filter_group_finite_idx_match = True + break + if not filter_group_finite_idx_match: + filters_to_use = "all" + data_sigma = np.sqrt(data_sigma**2 + (globals()[f"sys_err_{filters_to_use}_interped"](data_time)) ** 2) + + elif yaml_dict["config"]["withoutTime"]["value"]: + data_sigma = np.sqrt(data_sigma**2 + self.parameters["sys_err"] ** 2) + # include the error budget into calculation - if 'em_syserr' in self.parameters: - data_sigma = np.sqrt(data_sigma**2 + self.parameters['em_syserr']**2) + elif 'sys_err' in self.parameters: + data_sigma = np.sqrt(data_sigma**2 + self.parameters['sys_err']**2) else: data_sigma = np.sqrt(data_sigma**2 + self.error_budget[filt]**2) @@ -186,15 +246,36 @@ def log_likelihood(self): # evaluate the data with infinite error if len(infIdx) > 0: - if 'em_syserr' in self.parameters: - upperlim_sigma = self.parameters['em_syserr'] - gausslogsf = scipy.stats.norm.logsf( - data_mag[infIdx], mag_est[infIdx], upperlim_sigma - ) + if self.systematics_file is not None: + if yaml_dict["config"]["withTime"]["value"]: + yaml_filters = yaml_dict["config"]["withTime"]["filters"] + + filter_group_infinite_idx_match = False + + if yaml_filters is not None and yaml_filters != []: + for yaml_filt in yaml_filters: + if yaml_filt is not None and filt in yaml_filt: + if len(yaml_filt) == 1: + upperlim_sigma = globals()[f"sys_err_{yaml_filt}_interped"](data_time)[infIdx] + else: + filters_to_use = "___".join(yaml_filt) + upperlim_sigma = globals()[f"sys_err_{filters_to_use}_interped"](data_time)[infIdx] + filter_group_infinite_idx_match = True + break + if not filter_group_infinite_idx_match: + filters_to_use = "all" + upperlim_sigma = globals()[f"sys_err_{filters_to_use}_interped"](data_time)[infIdx] + + gausslogsf = scipy.stats.norm.logsf(data_mag[infIdx], mag_est[infIdx], upperlim_sigma) + + elif yaml_dict["config"]["withoutTime"]["value"]: + upperlim_sigma = self.parameters["sys_err"] + gausslogsf = scipy.stats.norm.logsf(data_mag[infIdx], mag_est[infIdx], upperlim_sigma) + elif 'sys_err' in self.parameters: + upperlim_sigma = self.parameters['sys_err'] + gausslogsf = scipy.stats.norm.logsf(data_mag[infIdx], mag_est[infIdx], upperlim_sigma) else: - gausslogsf = scipy.stats.norm.logsf( - data_mag[infIdx], mag_est[infIdx], self.error_budget[filt] - ) + gausslogsf = scipy.stats.norm.logsf(data_mag[infIdx], mag_est[infIdx], self.error_budget[filt]) gaussprob_total += np.sum(gausslogsf) log_prob = minus_chisquare_total + gaussprob_total diff --git a/nmma/em/prior.py b/nmma/em/prior.py index e98dd187..cb622e98 100644 --- a/nmma/em/prior.py +++ b/nmma/em/prior.py @@ -1,9 +1,31 @@ import bilby import bilby.core from bilby.core.prior import Prior +from bilby.core.prior import PriorDict as _PriorDict from bilby.core.prior.interpolated import Interped from bilby.core.prior.conditional import ConditionalTruncatedGaussian +from . import systematics + +def from_list(self, systematics): + """ + Similar to `from_file` but instead of file buffer, takes a list of Prior strings + See `from_file` for more details + """ + + comments = ["#", "\n"] + prior = dict() + for line in systematics: + if line[0] in comments: + continue + line.replace(" ", "") + elements = line.split("=") + key = elements[0].replace(" ", "") + val = "=".join(elements[1:]).strip() + prior[key] = val + self.from_dictionary(prior) + +setattr(_PriorDict, "from_list", from_list) class ConditionalGaussianIotaGivenThetaCore(ConditionalTruncatedGaussian): """ @@ -264,4 +286,7 @@ def convert_mtot_mni(parameters): print(f"The prior on Ebv is set to fixed value of {priors['Ebv']}") + if args.systematics_file is not None: + systematics_priors = systematics.main(args.systematics_file) + priors.from_list(systematics_priors) return priors diff --git a/nmma/em/systematics.py b/nmma/em/systematics.py new file mode 100644 index 00000000..d0863caf --- /dev/null +++ b/nmma/em/systematics.py @@ -0,0 +1,179 @@ +import yaml +from pathlib import Path +import warnings + +warnings.simplefilter("module", DeprecationWarning) + + +class ValidationError(ValueError): + def __init__(self, key, message): + super().__init__(f"Validation error for '{key}': {message}") + + +ALLOWED_FILTERS = [ + "2massh", + "2massj", + "2massks", + "atlasc", + "atlaso", + "bessellb", + "besselli", + "bessellr", + "bessellux", + "bessellv", + "ps1__g", + "ps1__i", + "ps1__r", + "ps1__y", + "ps1__z", + "sdssu", + "uvot__b", + "uvot__u", + "uvot__uvm2", + "uvot__uvw1", + "uvot__uvw2", + "uvot__v", + "uvot__white", + "ztfg", + "ztfi", + "ztfr", +] + + +def load_yaml(file_path): + return yaml.safe_load(Path(file_path).read_text()) + + +def validate_only_one_true(yaml_dict): + for key, values in yaml_dict["config"].items(): + if "value" not in values or type(values["value"]) is not bool: + raise ValidationError( + key, "'value' key must be present and be a boolean" + ) + true_count = sum(value["value"] for value in yaml_dict["config"].values()) + if true_count > 1: + raise ValidationError( + "config", "Only one configuration key can be set to True at a time" + ) + elif true_count == 0: + raise ValidationError( + "config", "At least one configuration key must be set to True" + ) + + +def validate_filters(filter_groups): + used_filters = set() + for filter_group in filter_groups: + if isinstance(filter_group, list): + filters_in_group = set() # Keep track of filters within this group + for filt in filter_group: + if filt not in ALLOWED_FILTERS: + raise ValidationError( + "filters", + f"Invalid filter value '{filt}'. Allowed values are {', '.join([str(f) for f in ALLOWED_FILTERS])}", + ) + if filt in filters_in_group: + raise ValidationError( + "filters", + f"Duplicate filter value '{filt}' within the same group.", + ) + if filt in used_filters: + raise ValidationError( + "filters", + f"Duplicate filter value '{filt}'. A filter can only be used in one group.", + ) + used_filters.add(filt) + filters_in_group.add(filt) + # Add the filter to the set of used filters within this group + elif filter_group is not None and filter_group not in ALLOWED_FILTERS: + raise ValidationError( + "filters", + f"Invalid filter value '{filter_group}'. Allowed values are {', '.join([str(f) for f in ALLOWED_FILTERS])}", + ) + elif filter_group in used_filters: + raise ValidationError( + "filters", + f"Duplicate filter value '{filter_group}'. A filter can only be used in one group.", + ) + else: + used_filters.add(filter_group) + + +def validate_distribution(distribution): + if distribution != "Uniform": + raise ValidationError( + "type", + f"Invalid distribution '{distribution}'. Only 'Uniform' distribution is supported", + ) + + +def validate_fields(key, values, required_fields): + missing_fields = [ + field for field in required_fields if values.get(field) is None + ] + if missing_fields: + raise ValidationError( + key, f"Missing fields: {', '.join(missing_fields)}" + ) + for field, expected_type in required_fields.items(): + if not isinstance(values[field], expected_type): + raise ValidationError( + key, f"'{field}' must be of type {expected_type}" + ) + + +def handle_withTime(key, values): + required_fields = { + "type": str, + "min": (float, int), + "max": (float, int), + "time_nodes": int, + "filters": list, + } + + + validate_fields(key, values, required_fields) + filter_groups = values.get("filters", []) + validate_filters(filter_groups) + distribution = values.get("type") + validate_distribution(distribution) + result = [] + + for filter_group in filter_groups: + if isinstance(filter_group, list): + filter_name = "___".join(filter_group) + else: + filter_name = filter_group if filter_group is not None else "all" + + for n in range(1, values["time_nodes"] + 1): + result.append( + f'sys_err_{filter_name}{n} = {values["type"]}(minimum={values["min"]},maximum={values["max"]},name="sys_err_{filter_name}{n}")' + ) + + return result + + +def handle_withoutTime(key, values): + required_fields = {"type": str, "min": (float, int), "max": (float, int)} + validate_fields(key, values, required_fields) + distribution = values.get("type") + validate_distribution(distribution) + return [ + f'sys_err = {values["type"]}(minimum={values["min"]},maximum={values["max"]},name="sys_err")' + ] + + +config_handlers = { + "withTime": handle_withTime, + "withoutTime": handle_withoutTime, +} + + +def main(yaml_file_path): + yaml_dict = load_yaml(yaml_file_path) + validate_only_one_true(yaml_dict) + results = [] + for key, values in yaml_dict["config"].items(): + if values["value"] and key in config_handlers: + results.extend(config_handlers[key](key, values)) + return results \ No newline at end of file diff --git a/nmma/tests/analysis.py b/nmma/tests/analysis.py index 5219b363..f10a6c71 100644 --- a/nmma/tests/analysis.py +++ b/nmma/tests/analysis.py @@ -10,6 +10,7 @@ WORKING_DIR = os.path.dirname(__file__) DATA_DIR = os.path.join(WORKING_DIR, "data") + @pytest.fixture(scope="module") def args(): args = Namespace( @@ -46,7 +47,7 @@ def args(): injection_outfile="outdir/lc.csv", injection_model=None, ignore_timeshift=False, - remove_nondetections=True, + remove_nondetections=True, detection_limit=None, with_grb_injection=False, prompt_collapse=False, @@ -77,12 +78,24 @@ def args(): cosiota_node_num=10, ra=None, dec=None, - fetch_Ebv_from_dustmap=False + fetch_Ebv_from_dustmap=False, ) return args +def test_analysis_systematics_with_time(args): + + args.systematics_file = f"{DATA_DIR}/systematics_with_time.yaml" + analysis.main(args) + + +def test_analysis_systematics_without_time(args): + + args.systematics_file = f"{DATA_DIR}/systematics_without_time.yaml" + analysis.main(args) + + def test_analysis_tensorflow(args): analysis.main(args) @@ -93,6 +106,7 @@ def test_analysis_sklearn_gp(args): args.interpolation_type = "sklearn_gp" analysis.main(args) + def test_nn_analysis(args): args.model = "Ka2017" @@ -101,9 +115,10 @@ def test_nn_analysis(args): args.dt = 0.25 args.filters = "ztfg,ztfr,ztfi" args.local_only = False - args.injection=f"{DATA_DIR}/Ka2017_injection.json" + args.injection = f"{DATA_DIR}/Ka2017_injection.json" analysis.main(args) + def test_analysis_slurm(args): args_slurm = Namespace( diff --git a/nmma/tests/data/systematics_with_time.yaml b/nmma/tests/data/systematics_with_time.yaml new file mode 100644 index 00000000..a3247164 --- /dev/null +++ b/nmma/tests/data/systematics_with_time.yaml @@ -0,0 +1,14 @@ +config: + withTime: + value: true + filters: + - null # you can keep it or remove it, it will still be parsed as None (filter independent) + time_nodes: 4 + type: "Uniform" + min: 0 + max: 2 + withoutTime: + value: false + type: "Uniform" + min: 0 + max: 2 diff --git a/nmma/tests/data/systematics_without_time.yaml b/nmma/tests/data/systematics_without_time.yaml new file mode 100644 index 00000000..376ba335 --- /dev/null +++ b/nmma/tests/data/systematics_without_time.yaml @@ -0,0 +1,14 @@ +config: + withTime: + value: false + filters: + - null # you can keep it or remove it, it will still be parsed as None (filter independent) + time_nodes: 4 + type: "Uniform" + min: 0 + max: 2 + withoutTime: + value: true + type: "Uniform" + min: 0 + max: 2 diff --git a/nmma/tests/systematics.py b/nmma/tests/systematics.py new file mode 100644 index 00000000..5cf33881 --- /dev/null +++ b/nmma/tests/systematics.py @@ -0,0 +1,237 @@ +from pathlib import Path +import pytest + +import yaml +from ..em.systematics import ( + ValidationError, + validate_only_one_true, + validate_filters, + validate_distribution, + validate_fields, + handle_withTime, + handle_withoutTime, + main, + ALLOWED_FILTERS, +) + + +@pytest.fixture +def sample_yaml_content(): + return """ +config: + withTime: + value: true + type: Uniform + min: 0.0 + max: 1.0 + time_nodes: 2 + filters: + - [bessellb, bessellv] + - ztfr + withoutTime: + value: false + type: Uniform + min: 0.0 + max: 1.0 +""" + + +@pytest.fixture +def sample_yaml_file(tmp_path, sample_yaml_content): + yaml_file = tmp_path / "test_config.yaml" + yaml_file.write_text(sample_yaml_content) + return yaml_file + + +def test_validate_only_one_true_valid(sample_yaml_file): + yaml_dict = yaml.safe_load(Path(sample_yaml_file).read_text()) + validate_only_one_true(yaml_dict) # Should not raise an exception + + +def test_validate_only_one_true_invalid(): + invalid_yaml = {"config": {"withTime": {"value": True}, "withoutTime": {"value": True}}} + with pytest.raises(ValidationError, match="Only one configuration key can be set to True at a time"): + validate_only_one_true(invalid_yaml) + + +def test_validate_filters_valid(): + valid_filters = [["bessellb", "bessellv"], "ztfr"] + validate_filters(valid_filters) # Should not raise an exception + + +def test_validate_filters_invalid(): + invalid_filters = [["bessellb", "invalid_filter"], "ztfr"] + with pytest.raises(ValidationError, match="Invalid filter value 'invalid_filter'"): + validate_filters(invalid_filters) + + +def test_validate_distribution_valid(): + validate_distribution("Uniform") # Should not raise an exception + + +def test_validate_distribution_invalid(): + with pytest.raises(ValidationError, match="Invalid distribution 'Normal'"): + validate_distribution("Normal") + + +def test_validate_fields_valid(): + valid_values = {"type": "Uniform", "min": 0.0, "max": 1.0} + required_fields = {"type": str, "min": (float, int), "max": (float, int)} + validate_fields("test", valid_values, required_fields) # Should not raise an exception + + +def test_validate_fields_invalid(): + invalid_values = {"type": "Uniform", "min": "0.0", "max": 1.0} + required_fields = {"type": str, "min": (float, int), "max": (float, int)} + with pytest.raises(ValidationError, match="'min' must be of type"): + validate_fields("test", invalid_values, required_fields) + + +def test_handle_withTime(): + values = {"type": "Uniform", "min": 0.0, "max": 1.0, "time_nodes": 2, "filters": [["bessellb", "bessellv"], "ztfr"]} + result = handle_withTime("withTime", values) + assert "sys_err_bessellb___bessellv1" in result[0] + assert "sys_err_ztfr2" in result[3] + + +def test_handle_withoutTime(): + values = {"type": "Uniform", "min": 0.0, "max": 1.0} + result = handle_withoutTime("withoutTime", values) + assert len(result) == 1 + assert 'sys_err = Uniform(minimum=0.0,maximum=1.0,name="sys_err")' in result[0] + + +def test_main(sample_yaml_file): + result = main(sample_yaml_file) + assert all("sys_err" in line for line in result) + + +def test_main_invalid_yaml(tmp_path): + invalid_yaml = tmp_path / "invalid_config.yaml" + invalid_yaml.write_text("invalid: yaml: content") + with pytest.raises(yaml.YAMLError): + main(invalid_yaml) + + +def test_validate_only_one_true_all_false(): + invalid_yaml = {"config": {"withTime": {"value": False}, "withoutTime": {"value": False}}} + with pytest.raises(ValidationError, match="At least one configuration key must be set to True"): + validate_only_one_true(invalid_yaml) + + +def test_validate_only_one_true_missing_value(): + invalid_yaml = {"config": {"withTime": {}, "withoutTime": {"value": False}}} + with pytest.raises(ValidationError, match="'value' key must be present and be a boolean"): + validate_only_one_true(invalid_yaml) + + +def test_validate_filters_duplicate_in_group(): + invalid_filters = [["bessellb", "bessellb"], "ztfr"] + with pytest.raises(ValidationError, match="Duplicate filter value 'bessellb' within the same group"): + validate_filters(invalid_filters) + + +def test_validate_filters_duplicate_across_groups(): + invalid_filters = [["bessellb", "bessellv"], "bessellb"] + with pytest.raises(ValidationError, match="Duplicate filter value 'bessellb'. A filter can only be used in one group"): + validate_filters(invalid_filters) + + +def test_validate_filters_none_value(): + valid_filters = [["bessellb", "bessellv"], None] + validate_filters(valid_filters) # Should not raise an exception + + +def test_validate_filters_empty_list(): + validate_filters([]) # Should not raise an exception + + +def test_validate_distribution_case_sensitive(): + with pytest.raises(ValidationError, match="Invalid distribution 'uniform'"): + validate_distribution("uniform") # Should be "Uniform" + + +@pytest.mark.parametrize("invalid_type", [123, True, [], {}]) +def test_validate_fields_invalid_types(invalid_type): + invalid_values = {"type": invalid_type, "min": 0.0, "max": 1.0} + required_fields = {"type": str, "min": (float, int), "max": (float, int)} + with pytest.raises(ValidationError, match="'type' must be of type"): + validate_fields("test", invalid_values, required_fields) + + +def test_handle_withTime_single_filter(): + values = {"type": "Uniform", "min": 0.0, "max": 1.0, "time_nodes": 2, "filters": ["ztfr"]} + result = handle_withTime("withTime", values) + assert len(result) == 2 + assert all("sys_err_ztfr" in line for line in result) + + +def test_handle_withTime_all_filters(): + values = {"type": "Uniform", "min": 0.0, "max": 1.0, "time_nodes": 1, "filters": [None]} + result = handle_withTime("withTime", values) + assert len(result) == 1 + assert "sys_err_all1" in result[0] + + +def test_handle_withTime_integer_bounds(): + values = {"type": "Uniform", "min": 0, "max": 10, "time_nodes": 1, "filters": ["ztfr"]} + result = handle_withTime("withTime", values) + assert "minimum=0" in result[0] and "maximum=10" in result[0] + + +def test_handle_withoutTime_integer_bounds(): + values = {"type": "Uniform", "min": 0, "max": 10} + result = handle_withoutTime("withoutTime", values) + assert "minimum=0" in result[0] and "maximum=10" in result[0] + + +def test_main_withoutTime(tmp_path): + yaml_content = """ +config: + withTime: + value: false + withoutTime: + value: true + type: Uniform + min: 0.0 + max: 1.0 +""" + yaml_file = tmp_path / "withoutTime_config.yaml" + yaml_file.write_text(yaml_content) + result = main(yaml_file) + assert len(result) == 1 + assert "sys_err = Uniform" in result[0] + + +def test_main_empty_config(tmp_path): + yaml_content = """ +config: + withTime: + value: false + withoutTime: + value: false +""" + yaml_file = tmp_path / "empty_config.yaml" + yaml_file.write_text(yaml_content) + with pytest.raises(ValidationError, match="At least one configuration key must be set to True"): + main(yaml_file) + + +@pytest.mark.parametrize("filter_name", ALLOWED_FILTERS) +def test_all_allowed_filters(filter_name): + values = {"type": "Uniform", "min": 0.0, "max": 1.0, "time_nodes": 1, "filters": [filter_name]} + result = handle_withTime("withTime", values) + assert len(result) == 1 + assert f"sys_err_{filter_name}1" in result[0] + + +def test_load_yaml_file_not_found(): + with pytest.raises(FileNotFoundError): + main("non_existent_file.yaml") + + +def test_load_yaml_invalid_format(tmp_path): + invalid_yaml = tmp_path / "invalid_format.yaml" + invalid_yaml.write_text("{ invalid: yaml: content") + with pytest.raises(yaml.YAMLError): + main(invalid_yaml) diff --git a/priors/systematics.yaml b/priors/systematics.yaml new file mode 100644 index 00000000..95f0d0da --- /dev/null +++ b/priors/systematics.yaml @@ -0,0 +1,19 @@ +#boilerplate for systematics prior + +config: + withTime: + value: true + filters: + - sdssu + - 2massks + - [2massj, 2massh] + - null # you can keep it or remove it, it will still be parsed as None (filter independent) + time_nodes: 2 + type: "Uniform" + min: 0 + max: 2 + withoutTime: + value: false + type: "Uniform" + min: 0 + max: 2 From abb78ebbec27d1c8ff30d14faf1d2857619d0880 Mon Sep 17 00:00:00 2001 From: Sahil Jhawar Date: Sat, 21 Sep 2024 17:19:49 +0200 Subject: [PATCH 2/3] fix tests, autodelete outdir --- nmma/tests/analysis.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nmma/tests/analysis.py b/nmma/tests/analysis.py index f10a6c71..11c1d667 100644 --- a/nmma/tests/analysis.py +++ b/nmma/tests/analysis.py @@ -1,6 +1,7 @@ from argparse import Namespace import os import pytest +import shutil from ..em import analysis @@ -11,6 +12,13 @@ DATA_DIR = os.path.join(WORKING_DIR, "data") +@pytest.fixture(autouse=True) +def cleanup_outdir(args): + yield + if os.path.exists(args.outdir): + shutil.rmtree(args.outdir) + + @pytest.fixture(scope="module") def args(): args = Namespace( From bdebc091196a324592e16e266b6e6f431e8f49d7 Mon Sep 17 00:00:00 2001 From: Sahil Jhawar Date: Sat, 21 Sep 2024 17:36:49 +0200 Subject: [PATCH 3/3] add systematics_file flag to lbol --- nmma/em/analysis_lbol.py | 7 +++++++ nmma/tests/analysis_lbol.py | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/nmma/em/analysis_lbol.py b/nmma/em/analysis_lbol.py index 3037f0ad..bb93f7a5 100644 --- a/nmma/em/analysis_lbol.py +++ b/nmma/em/analysis_lbol.py @@ -222,6 +222,13 @@ def get_parser(**kwargs): action="store_true", default=False, ) + parser.add_argument( #no use in this script + "--systematics-file", + metavar="PATH", + help="Path to systematics configuration file", + default=None, + ) + return parser diff --git a/nmma/tests/analysis_lbol.py b/nmma/tests/analysis_lbol.py index 3765a521..e56642a1 100644 --- a/nmma/tests/analysis_lbol.py +++ b/nmma/tests/analysis_lbol.py @@ -44,7 +44,8 @@ def args(): cosiota_node_num=10, ra=None, dec=None, - fetch_Ebv_from_dustmap=False + fetch_Ebv_from_dustmap=False, + systematics_file=None ) return args