Completed
Push — main ( 6464b6...34edd0 )
by Chaitanya
18s queued 14s
created

asgardpy.stats.utils   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 194
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 114
dl 0
loc 194
rs 10
c 0
b 0
f 0
wmc 13

6 Functions

Rating   Name   Duplication   Size   Complexity  
A get_model_config_files() 0 12 2
A tabulate_best_fit_stats() 0 28 2
A copy_target_config() 0 24 1
A get_spec_params_indices() 0 23 4
A fetch_all_analysis_fit_info() 0 53 3
A write_output_config_yaml() 0 23 1
1
"""
2
Module containing additional utility functions for selecting a preferred model.
3
"""
4
5
import numpy as np
6
import yaml
7
from astropy.table import QTable
8
9
from asgardpy.config.generator import AsgardpyConfig, all_model_templates
10
from asgardpy.stats.stats import check_model_preference_lrt
11
12
__all__ = [
13
    "fetch_all_analysis_fit_info",
14
    "get_model_config_files",
15
    "tabulate_best_fit_stats",
16
    "copy_target_config",
17
    "write_output_config_yaml",
18
]
19
20
21
def get_model_config_files(select_model_tags):
22
    """From the default model templates, select some."""
23
24
    all_tags, template_files = all_model_templates()
25
26
    spec_model_temp_files = []
27
    for tag in select_model_tags:
28
        spec_model_temp_files.append(template_files[np.where(all_tags == tag)[0][0]])
29
30
    spec_model_temp_files = np.array(spec_model_temp_files)
31
32
    return spec_model_temp_files
33
34
35
def get_spec_params_indices(aa_config):
36
    """
37
    For copying the spectral flux amplitude and flux normalization energy,
38
    from one config to another, find the correct parameter indices within a
39
    given config.
40
    """
41
    par_names = []
42
    for p in aa_config.config.target.components[0].spectral.parameters:
43
        par_names.append(p.name)
44
    par_names = np.array(par_names)
45
46
    amp_idx = None
47
    # For models without this parameter, name has not yet been included or
48
    # checked with Asgardpy
49
    if "amplitude" in par_names:
50
        amp_idx = np.where(par_names == "amplitude")[0][0]
51
52
    if "reference" in par_names:
53
        eref_idx = np.where(par_names == "reference")[0][0]
54
    else:
55
        eref_idx = np.where(par_names == "ebreak")[0][0]
56
57
    return amp_idx, eref_idx
58
59
60
def copy_target_config(aa_config_1, aa_config_2):
61
    """From aa_config_1 update information in aa_config_2."""
62
63
    amp_idx_1, eref_idx_1 = get_spec_params_indices(aa_config_1)
64
    amp_idx_2, eref_idx_2 = get_spec_params_indices(aa_config_2)
65
66
    # Have the same value of amplitude
67
    aa_config_2.config.target.components[0].spectral.parameters[amp_idx_2].value = (
68
        aa_config_1.config.target.components[0].spectral.parameters[amp_idx_1].value
69
    )
70
    # Have the same value of reference/e_break energy
71
    aa_config_2.config.target.components[0].spectral.parameters[eref_idx_2].value = (
72
        aa_config_1.config.target.components[0].spectral.parameters[eref_idx_1].value
73
    )
74
    # Have the same value of redshift value and EBL reference model
75
    aa_config_2.config.target.components[0].spectral.ebl_abs.redshift = aa_config_1.config.target.components[
76
        0
77
    ].spectral.ebl_abs.redshift
78
79
    # Make sure the source names are the same
80
    aa_config_2.config.target.source_name = aa_config_1.config.target.source_name
81
    aa_config_2.config.target.components[0].name = aa_config_1.config.target.components[0].name
82
83
    return aa_config_2
84
85
86
def fetch_all_analysis_fit_info(main_analysis_list, spec_models_list):
87
    """
88
    For a list of spectral models, with the AsgardpyAnalysis run till the fit
89
    step, get the relevant information for testing the model preference.
90
    """
91
    fit_success_list = []
92
    pref_over_pl_chi2_list = []
93
    stat_list = []
94
    dof_list = []
95
96
    for tag in spec_models_list:
97
        dict_tag = main_analysis_list[tag]["Analysis"].instrument_spectral_info
98
        dict_pl = main_analysis_list["pl"]["Analysis"].instrument_spectral_info
99
100
        # Collect parameters for AIC check
101
        stat = dict_tag["best_fit_stat"]
102
        dof = dict_tag["DoF"]
103
104
        fit_success = main_analysis_list[tag]["Analysis"].fit_result.success
105
106
        fit_success_list.append(fit_success)
107
        stat_list.append(stat)
108
        dof_list.append(dof)
109
110
        # Checking the preference of a "nested" spectral model (observed),
111
        # over Power Law.
112
        if tag == "pl":
113
            main_analysis_list[tag]["Pref_over_pl_chi2"] = 0
114
            main_analysis_list[tag]["Pref_over_pl_pval"] = 0
115
            main_analysis_list[tag]["DoF_over_pl"] = 0
116
            pref_over_pl_chi2_list.append(0)
117
            continue
118
119
        p_pl_x, g_pl_x, ndof_pl_x = check_model_preference_lrt(
120
            dict_pl["best_fit_stat"],
121
            dict_tag["best_fit_stat"],
122
            dict_pl["DoF"],
123
            dict_tag["DoF"],
124
        )
125
126
        main_analysis_list[tag]["Pref_over_pl_chi2"] = g_pl_x
127
        pref_over_pl_chi2_list.append(g_pl_x)
128
        main_analysis_list[tag]["Pref_over_pl_pval"] = p_pl_x
129
        main_analysis_list[tag]["DoF_over_pl"] = ndof_pl_x
130
131
    fit_success_list = np.array(fit_success_list)
132
133
    # Only select fit results that were successful for comparisons
134
    stat_list = np.array(stat_list)[fit_success_list]
135
    dof_list = np.array(dof_list)[fit_success_list]
136
    pref_over_pl_chi2_list = np.array(pref_over_pl_chi2_list)[fit_success_list]
137
138
    return fit_success_list, stat_list, dof_list, pref_over_pl_chi2_list
139
140
141
def tabulate_best_fit_stats(spec_models_list, fit_success_list, main_analysis_list, list_rel_p):
142
    """For a list of spectral models, tabulate the best fit information."""
143
144
    fit_stats_table = []
145
146
    for i, tag in enumerate(spec_models_list[fit_success_list]):
147
        info_ = main_analysis_list[tag]["Analysis"].instrument_spectral_info
148
149
        t = main_analysis_list[tag]
150
151
        ts_gof = round(info_["best_fit_stat"] - info_["max_fit_stat"], 3)
152
        t_fits = {
153
            "Spectral Model": tag.upper(),
154
            "TS of Best Fit": round(info_["best_fit_stat"], 3),
155
            "TS of Max Fit": round(info_["max_fit_stat"], 3),
156
            "TS of Goodness of Fit": ts_gof,
157
            "DoF of Fit": info_["DoF"],
158
            r"Significance ($\sigma$) of Goodness of Fit": round(info_["fit_chi2_sig"], 3),
159
            "p-value of Goodness of Fit": float(f"{info_['fit_pval']:.4g}"),
160
            "Pref over PL (chi2)": round(t["Pref_over_pl_chi2"], 3),
161
            "Pref over PL (p-value)": float(f"{t['Pref_over_pl_pval']:.4g}"),
162
            "Pref over PL (DoF)": t["DoF_over_pl"],
163
            "Relative p-value (AIC)": float(f"{list_rel_p[i]:.4g}"),
164
        }
165
        fit_stats_table.append(t_fits)
166
    stats_table = QTable(fit_stats_table)
167
168
    return stats_table
169
170
171
def write_output_config_yaml(model_):
172
    """With the selected spectral model, update a default config in yaml."""
173
174
    spec_model = model_.spectral_model.model1.to_dict()
175
176
    temp_config = AsgardpyConfig()
177
    temp_config.target.components[0] = spec_model
178
179
    # Update with the spectral model info
180
    temp_ = temp_config.dict(exclude_defaults=True)
181
182
    # Remove some of the unnecessary keys
183
    temp_["target"].pop("models_file", None)
184
    temp_["target"]["components"][0]["spectral"].pop("ebl_abs", None)
185
186
    yaml_ = yaml.dump(
187
        temp_,
188
        sort_keys=False,
189
        indent=4,
190
        width=80,
191
        default_flow_style=None,
192
    )
193
    return yaml_
194