BASTA 1.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- basta/__about__.py +1 -0
- basta/__init__.py +0 -0
- basta/bastamain.py +611 -0
- basta/constants.py +571 -0
- basta/distances.py +442 -0
- basta/downloader.py +214 -0
- basta/downloader_examples.py +153 -0
- basta/fileio.py +1539 -0
- basta/freq_fit.py +1151 -0
- basta/glitch_fit.py +264 -0
- basta/glitch_fq.f95 +876 -0
- basta/glitch_sd.f95 +794 -0
- basta/icov_sd.f95 +242 -0
- basta/interpolation_across.py +695 -0
- basta/interpolation_along.py +464 -0
- basta/interpolation_combined.py +524 -0
- basta/interpolation_driver.py +554 -0
- basta/interpolation_helpers.py +902 -0
- basta/plot_corner.py +695 -0
- basta/plot_driver.py +411 -0
- basta/plot_interp.py +410 -0
- basta/plot_kiel.py +555 -0
- basta/plot_seismic.py +1261 -0
- basta/plots.mplstyle +74 -0
- basta/priors.py +142 -0
- basta/process_output.py +696 -0
- basta/run.py +172 -0
- basta/sd.f95 +73 -0
- basta/stats.py +749 -0
- basta/utils_distances.py +176 -0
- basta/utils_examples.py +409 -0
- basta/utils_general.py +688 -0
- basta/utils_seismic.py +637 -0
- basta/utils_xml.py +224 -0
- basta/xml_create.py +415 -0
- basta/xml_run.py +955 -0
- basta-1.5.0.dist-info/METADATA +153 -0
- basta-1.5.0.dist-info/RECORD +41 -0
- basta-1.5.0.dist-info/WHEEL +4 -0
- basta-1.5.0.dist-info/entry_points.txt +5 -0
- basta-1.5.0.dist-info/licenses/LICENSE +21 -0
basta/__about__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.5.0"
|
basta/__init__.py
ADDED
|
File without changes
|
basta/bastamain.py
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main module for running BASTA analysis
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import gc
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from copy import deepcopy
|
|
10
|
+
|
|
11
|
+
import h5py
|
|
12
|
+
import numpy as np
|
|
13
|
+
from tqdm import tqdm
|
|
14
|
+
|
|
15
|
+
from basta import freq_fit, stats, process_output, priors, distances, plot_driver
|
|
16
|
+
from basta import utils_seismic as su
|
|
17
|
+
from basta import utils_general as util
|
|
18
|
+
from basta.__about__ import __version__
|
|
19
|
+
from basta import fileio as fio
|
|
20
|
+
from basta.constants import freqtypes
|
|
21
|
+
|
|
22
|
+
# Import matplotlib after other plotting modules for proper setup
|
|
23
|
+
# --> Here in main it is only used for clean-up
|
|
24
|
+
import matplotlib.pyplot as plt
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Custom exception
|
|
28
|
+
class LibraryError(Exception):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def BASTA(
|
|
33
|
+
starid: str,
|
|
34
|
+
gridfile: str,
|
|
35
|
+
inputparams: dict,
|
|
36
|
+
gridid: bool | tuple = False,
|
|
37
|
+
usebayw: bool = True,
|
|
38
|
+
usepriors: tuple = (None,),
|
|
39
|
+
optionaloutputs: bool = False,
|
|
40
|
+
seed: int | None = None,
|
|
41
|
+
debug: bool = False,
|
|
42
|
+
verbose: bool = False,
|
|
43
|
+
developermode: bool = False,
|
|
44
|
+
validationmode: bool = False,
|
|
45
|
+
):
|
|
46
|
+
"""
|
|
47
|
+
The BAyesian STellar Algorithm (BASTA).
|
|
48
|
+
(c) 2024, The BASTA Team
|
|
49
|
+
|
|
50
|
+
For a description of how to use BASTA, please explore the documentation (https://github.com/BASTAcode/BASTA).
|
|
51
|
+
This function is typically called by :func:'xmltools.run_xml()'
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
starid : str
|
|
56
|
+
Unique identifier for this target.
|
|
57
|
+
gridfile : str
|
|
58
|
+
Path and name of the hdf5 file containing the isochrones or tracks
|
|
59
|
+
used in the fitting
|
|
60
|
+
inputparams : dict
|
|
61
|
+
Dictionary containing most information needed, e.g. controls, fitparameters,
|
|
62
|
+
output options.
|
|
63
|
+
gridid : bool or tuple
|
|
64
|
+
For isochrones, a tuple containing (overshooting [f],
|
|
65
|
+
diffusion [0 or 1], mass loss [eta], alpha enhancement [0.0 ... 0.4])
|
|
66
|
+
used for selecting a science case / path in the library.
|
|
67
|
+
usebayw : bool or tuple
|
|
68
|
+
If True, bayesian weights are applied in the computation of the
|
|
69
|
+
likelihood. See :func:`interpolation_helpers.bay_weights()` for details.
|
|
70
|
+
usepriors : tuple
|
|
71
|
+
Tuple of strings containing name of priors (e.g., an IMF).
|
|
72
|
+
See :func:`priors` for details.
|
|
73
|
+
optionaloutputs : bool, optional
|
|
74
|
+
If True, saves a 'json' file for each star with the global results and the PDF.
|
|
75
|
+
seed : int, optional
|
|
76
|
+
The seed of randomness
|
|
77
|
+
debug : bool, optional
|
|
78
|
+
Activate additional output for debugging (for developers)
|
|
79
|
+
verbose : bool, optional
|
|
80
|
+
Activate a lot (!) of additional output (for developers)
|
|
81
|
+
developermode : bool, optional
|
|
82
|
+
Activate experimental features (for developers)
|
|
83
|
+
validationmode : bool, optional
|
|
84
|
+
Activate validation mode features (for validation purposes only)
|
|
85
|
+
"""
|
|
86
|
+
# Enable legacy printing of NumPy data types
|
|
87
|
+
# --> E.g., print 104.14836386995329 instead of np.float64(104.14836386995329)
|
|
88
|
+
# and 'Teff' instead of np.str_('Teff') to the .log file
|
|
89
|
+
np.set_printoptions(legacy="1.25")
|
|
90
|
+
|
|
91
|
+
# Set output directory and filenames
|
|
92
|
+
t0 = time.localtime()
|
|
93
|
+
outputdir = inputparams.get("output")
|
|
94
|
+
outfilename = os.path.join(outputdir, starid)
|
|
95
|
+
|
|
96
|
+
# Start the log
|
|
97
|
+
stdout = sys.stdout
|
|
98
|
+
sys.stdout = util.Logger(outfilename)
|
|
99
|
+
|
|
100
|
+
# Pretty printing a header
|
|
101
|
+
util.print_bastaheader(t0=t0, seed=seed, developermode=developermode)
|
|
102
|
+
|
|
103
|
+
# Load the desired grid and obtain information from the header
|
|
104
|
+
Grid = h5py.File(gridfile, "r")
|
|
105
|
+
gridtype, gridver, gridtime, grid_is_intpol = util.read_grid_header(Grid)
|
|
106
|
+
|
|
107
|
+
# Verbose information on the grid file
|
|
108
|
+
print(f"\nFitting star id: {starid} .")
|
|
109
|
+
|
|
110
|
+
print(f"* Using the grid '{gridfile}' of type '{gridtype}'.")
|
|
111
|
+
print(f" - Grid built with BASTA version {gridver}, timestamp: {gridtime}.")
|
|
112
|
+
|
|
113
|
+
entryname, defaultpath, difsolarmodel = util.check_gridtype(gridtype, gridid=gridid)
|
|
114
|
+
|
|
115
|
+
# Read available weights if not provided by the user
|
|
116
|
+
bayweights, dweight = (
|
|
117
|
+
util.read_grid_bayweights(Grid, gridtype) if usebayw else (None, None)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Get list of parameters
|
|
121
|
+
cornerplots = inputparams["cornerplots"]
|
|
122
|
+
outparams = inputparams["asciiparams"]
|
|
123
|
+
allparams = list(np.unique(cornerplots + outparams))
|
|
124
|
+
|
|
125
|
+
inputparams, allparams = util.prepare_distancefitting(
|
|
126
|
+
inputparams=inputparams,
|
|
127
|
+
debug=debug,
|
|
128
|
+
debug_dirpath=outfilename,
|
|
129
|
+
allparams=allparams,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Create list of all available input parameters
|
|
133
|
+
fitparams = inputparams.get("fitparams")
|
|
134
|
+
fitfreqs = inputparams["fitfreqs"]
|
|
135
|
+
distparams = inputparams.get("distanceparams", False)
|
|
136
|
+
limits = inputparams.get("limits")
|
|
137
|
+
|
|
138
|
+
# Scale dnu and numax using a solar model or default solar values
|
|
139
|
+
inputparams = su.solar_scaling(Grid, inputparams, diffusion=difsolarmodel)
|
|
140
|
+
|
|
141
|
+
# Prepare asteroseismic quantities if required
|
|
142
|
+
if fitfreqs["active"]:
|
|
143
|
+
if not all(x in freqtypes.alltypes for x in fitfreqs["fittypes"]):
|
|
144
|
+
print(fitfreqs["fittypes"])
|
|
145
|
+
raise ValueError("Unrecognized frequency fitting parameters!")
|
|
146
|
+
|
|
147
|
+
# Obtain/calculate all frequency related quantities
|
|
148
|
+
(
|
|
149
|
+
obskey,
|
|
150
|
+
obs,
|
|
151
|
+
obsfreqdata,
|
|
152
|
+
obsfreqmeta,
|
|
153
|
+
obsintervals,
|
|
154
|
+
) = su.prepare_obs(inputparams, verbose=verbose, debug=debug)
|
|
155
|
+
# Apply prior on dnufit to mimick the range defined by dnufrac
|
|
156
|
+
if fitfreqs["dnuprior"] and ("dnufit" not in limits):
|
|
157
|
+
dnufit_frac = fitfreqs["dnufrac"] * fitfreqs["dnufit"]
|
|
158
|
+
dnuerr = max(3 * fitfreqs["dnufit_err"], dnufit_frac)
|
|
159
|
+
limits["dnufit"] = [
|
|
160
|
+
fitfreqs["dnufit"] - dnuerr,
|
|
161
|
+
fitfreqs["dnufit"] + dnuerr,
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
# Check if any specified limit in prior is in header, and can be used to
|
|
165
|
+
# skip computation of models, in order to speed up computation
|
|
166
|
+
tracks_headerpath = "header/"
|
|
167
|
+
if "tracks" in gridtype.lower():
|
|
168
|
+
headerpath: str | bool = tracks_headerpath
|
|
169
|
+
elif "isochrones" in gridtype.lower():
|
|
170
|
+
headerpath = tracks_headerpath + defaultpath
|
|
171
|
+
if "FeHini" in limits:
|
|
172
|
+
del limits["FeHini"]
|
|
173
|
+
print("Warning: Dropping prior in FeHini, redundant for isochrones!")
|
|
174
|
+
else:
|
|
175
|
+
headerpath = False
|
|
176
|
+
|
|
177
|
+
# Gridcut dictionary containing cutting parameters
|
|
178
|
+
gridcut = {}
|
|
179
|
+
if headerpath:
|
|
180
|
+
keys = Grid[headerpath].keys()
|
|
181
|
+
# Compare keys in header and limits
|
|
182
|
+
for key in keys:
|
|
183
|
+
if key in limits:
|
|
184
|
+
gridcut[key] = limits[key]
|
|
185
|
+
# Remove key from limits, to avoid redundant second check
|
|
186
|
+
del limits[key]
|
|
187
|
+
|
|
188
|
+
# Apply the cut on header parameters with a special treatment of diffusion
|
|
189
|
+
if headerpath and gridcut:
|
|
190
|
+
print("\nCutting in grid based on sampling parameters ('gridcut'):")
|
|
191
|
+
noofskips = [0, 0]
|
|
192
|
+
for cpar in gridcut:
|
|
193
|
+
if cpar != "dif":
|
|
194
|
+
print(f"* {cpar}: {gridcut[cpar]}")
|
|
195
|
+
|
|
196
|
+
# Diffusion switch printed in a more readable format
|
|
197
|
+
if "dif" in gridcut:
|
|
198
|
+
# As gridcut['dif'] is always either [-inf, 0.5] or [0.5, inf]
|
|
199
|
+
# The location of 0.5 can be used as the switch
|
|
200
|
+
switch = np.where(np.array(gridcut["dif"]) == 0.5)[0][0]
|
|
201
|
+
print(
|
|
202
|
+
"* Only considering tracks with diffusion turned",
|
|
203
|
+
"{:s}!".format(["on", "off"][switch]),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
util.print_fitparams(fitparams=fitparams)
|
|
207
|
+
if fitfreqs["active"]:
|
|
208
|
+
util.print_seismic(fitfreqs=fitfreqs, obskey=obskey, obs=obs)
|
|
209
|
+
util.print_distances(distparams, inputparams["asciiparams"])
|
|
210
|
+
util.print_additional(inputparams)
|
|
211
|
+
util.print_weights(bayweights, gridtype)
|
|
212
|
+
util.print_priors(limits, usepriors)
|
|
213
|
+
|
|
214
|
+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
215
|
+
# Start likelihood computation
|
|
216
|
+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
217
|
+
|
|
218
|
+
# Two loop cases for the outer "metal" loop:
|
|
219
|
+
# - For Garstec and MESA grids, the top level contains only one element ("tracks").
|
|
220
|
+
# Here the outer loop will run only once.
|
|
221
|
+
# - For BaSTI, the top level is a list of metallicities and the outer loop will run
|
|
222
|
+
# multiple times.
|
|
223
|
+
metal = util.list_metallicities(Grid, defaultpath, inputparams, limits)
|
|
224
|
+
|
|
225
|
+
# We assume Garstec grid structure. The path will be updated in the loop for BaSTI
|
|
226
|
+
group_name = defaultpath + "tracks/"
|
|
227
|
+
|
|
228
|
+
# Before running the actual loop, all tracks/isochrones are counted to better
|
|
229
|
+
# estimate the progress.
|
|
230
|
+
trackcounter = 0
|
|
231
|
+
for FeH in metal:
|
|
232
|
+
if "grid" not in defaultpath:
|
|
233
|
+
group_name = f"{defaultpath}FeH={FeH:.4f}/"
|
|
234
|
+
assert group_name == defaultpath + "FeH=" + format(FeH, ".4f") + "/"
|
|
235
|
+
|
|
236
|
+
group = Grid[group_name]
|
|
237
|
+
trackcounter += len(group.items())
|
|
238
|
+
|
|
239
|
+
# Prepare the main loop
|
|
240
|
+
shapewarn = 0
|
|
241
|
+
warn = True
|
|
242
|
+
selectedmodels = {}
|
|
243
|
+
noofind = 0
|
|
244
|
+
noofposind = 0
|
|
245
|
+
# In some cases we need to store quantities computed at runtime
|
|
246
|
+
if fitfreqs["active"] and fitfreqs["dnufit_in_ratios"]:
|
|
247
|
+
dnusurfmodels = {}
|
|
248
|
+
if fitfreqs["active"] and fitfreqs["glitchfit"]:
|
|
249
|
+
glitchmodels = {}
|
|
250
|
+
|
|
251
|
+
print(
|
|
252
|
+
f"\n\nComputing likelihood of models in the grid ({trackcounter} {entryname}) ..."
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Use a progress bar (with the package tqdm; will write to stderr)
|
|
256
|
+
pbar = tqdm(total=trackcounter, desc="--> Progress", ascii=True)
|
|
257
|
+
for FeH in metal:
|
|
258
|
+
if "grid" not in defaultpath:
|
|
259
|
+
group_name = f"{defaultpath}FeH={FeH:.4f}/"
|
|
260
|
+
|
|
261
|
+
group = Grid[group_name]
|
|
262
|
+
for noingrid, (name, libitem) in enumerate(group.items()):
|
|
263
|
+
# Update progress bar in the start of the loop to count skipped tracks
|
|
264
|
+
pbar.update(1)
|
|
265
|
+
|
|
266
|
+
# For grid with interpolated tracks, skip tracks flagged as empty
|
|
267
|
+
if grid_is_intpol:
|
|
268
|
+
if libitem["IntStatus"][()] < 0:
|
|
269
|
+
continue
|
|
270
|
+
|
|
271
|
+
# Check for diffusion
|
|
272
|
+
if "dif" in inputparams:
|
|
273
|
+
if int(round(libitem["dif"][0])) != int(
|
|
274
|
+
round(float(inputparams["dif"]))
|
|
275
|
+
):
|
|
276
|
+
continue
|
|
277
|
+
|
|
278
|
+
# Check if mass or age is in limits to efficiently skip
|
|
279
|
+
if "grid" not in defaultpath:
|
|
280
|
+
param, val = name.split("=")
|
|
281
|
+
if param == "mass":
|
|
282
|
+
param += "ini"
|
|
283
|
+
if param in limits:
|
|
284
|
+
# if age or massini is outside limits, skip this iteration
|
|
285
|
+
if float(val) < limits[param][0] or float(val) > limits[param][1]:
|
|
286
|
+
continue
|
|
287
|
+
|
|
288
|
+
# Check if track should be skipped from cut in initial parameters
|
|
289
|
+
if gridcut:
|
|
290
|
+
noofskips[1] += 1
|
|
291
|
+
docut = False
|
|
292
|
+
for param in gridcut:
|
|
293
|
+
if "tracks" in gridtype.lower():
|
|
294
|
+
value = Grid[tracks_headerpath][param][noingrid]
|
|
295
|
+
elif "isochrones" in gridtype.lower():
|
|
296
|
+
# For isochrones, metallicity is already cut from the
|
|
297
|
+
# metal list and lookup of age is simplest and fastest
|
|
298
|
+
if param == "age":
|
|
299
|
+
value = float(name[4:])
|
|
300
|
+
# If value is outside cut limits, skip looking at the rest
|
|
301
|
+
if not (value >= gridcut[param][0] and value <= gridcut[param][1]):
|
|
302
|
+
docut = True
|
|
303
|
+
continue
|
|
304
|
+
# Actually skip this iteration
|
|
305
|
+
if docut:
|
|
306
|
+
noofskips[0] += 1
|
|
307
|
+
continue
|
|
308
|
+
|
|
309
|
+
# Check which models have parameters within limits
|
|
310
|
+
index = np.ones(len(libitem["age"][:]), dtype=bool)
|
|
311
|
+
for param in limits:
|
|
312
|
+
index &= libitem[param][:] >= limits[param][0]
|
|
313
|
+
index &= libitem[param][:] <= limits[param][1]
|
|
314
|
+
|
|
315
|
+
# Check which models have phases as specified
|
|
316
|
+
if "phase" in inputparams:
|
|
317
|
+
# Mapping of verbose input phases to internal numbers
|
|
318
|
+
pmap = {
|
|
319
|
+
"pre-ms": 1,
|
|
320
|
+
"solar": 2,
|
|
321
|
+
"rgb": 3,
|
|
322
|
+
"flash": 4,
|
|
323
|
+
"clump": 5,
|
|
324
|
+
"agb": 6,
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
# Fitting multiple phases or just one
|
|
328
|
+
if isinstance(inputparams["phase"], tuple):
|
|
329
|
+
iphases = [pmap[ip] for ip in inputparams["phase"]]
|
|
330
|
+
|
|
331
|
+
phaseindex = libitem["phase"][:] == iphases[0]
|
|
332
|
+
for j in range(1, len(iphases)):
|
|
333
|
+
phaseindex |= libitem["phase"][:] == iphases[j]
|
|
334
|
+
index &= phaseindex
|
|
335
|
+
else:
|
|
336
|
+
iphase = pmap[inputparams["phase"]]
|
|
337
|
+
index &= libitem["phase"][:] == iphase
|
|
338
|
+
|
|
339
|
+
# Check which models have l=0, lowest n within tolerance
|
|
340
|
+
if fitfreqs["active"]:
|
|
341
|
+
indexf = np.zeros(len(index), dtype=bool)
|
|
342
|
+
for ind in np.where(index)[0]:
|
|
343
|
+
rawmod = libitem["osc"][ind]
|
|
344
|
+
rawmodkey = libitem["osckey"][ind]
|
|
345
|
+
mod = su.transform_obj_array(rawmod)
|
|
346
|
+
modkey = su.transform_obj_array(rawmodkey)
|
|
347
|
+
modkeyl0, modl0 = su.get_givenl(l=0, osc=mod, osckey=modkey)
|
|
348
|
+
# As mod is ordered (stacked in increasing n and l),
|
|
349
|
+
# then [0, 0] is the lowest l=0 mode
|
|
350
|
+
same_n = modkeyl0[1, :] == obskey[1, 0]
|
|
351
|
+
cl0 = modl0[0, same_n]
|
|
352
|
+
if len(cl0) > 1:
|
|
353
|
+
cl0 = cl0[0]
|
|
354
|
+
|
|
355
|
+
# Note to self: This code is pretty hard to read...
|
|
356
|
+
if (
|
|
357
|
+
cl0
|
|
358
|
+
>= (
|
|
359
|
+
obs[0, 0]
|
|
360
|
+
- min(
|
|
361
|
+
(fitfreqs["dnufrac"] / 2 * fitfreqs["dnufit"]),
|
|
362
|
+
(3 * obs[1, 0]),
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
) and (cl0 - obs[0, 0]) <= (
|
|
366
|
+
fitfreqs["dnufrac"] * fitfreqs["dnufit"]
|
|
367
|
+
):
|
|
368
|
+
indexf[ind] = True
|
|
369
|
+
index &= indexf
|
|
370
|
+
|
|
371
|
+
# If any models are within tolerances, calculate statistics
|
|
372
|
+
if np.any(index):
|
|
373
|
+
chi2 = np.zeros(index.sum())
|
|
374
|
+
paramvalues = {}
|
|
375
|
+
for param in fitparams:
|
|
376
|
+
paramvals = libitem[param][index]
|
|
377
|
+
chi2 += (
|
|
378
|
+
(paramvals - fitparams[param][0]) / fitparams[param][1]
|
|
379
|
+
) ** 2.0
|
|
380
|
+
if param in allparams:
|
|
381
|
+
paramvalues[param] = paramvals
|
|
382
|
+
|
|
383
|
+
# Add parameters not in fitparams
|
|
384
|
+
for param in allparams:
|
|
385
|
+
if param not in fitparams:
|
|
386
|
+
paramvalues[param] = libitem[param][index]
|
|
387
|
+
|
|
388
|
+
# Frequency (and/or ratio and/or glitch) fitting
|
|
389
|
+
if fitfreqs["active"]:
|
|
390
|
+
if fitfreqs["dnufit_in_ratios"]:
|
|
391
|
+
dnusurf = np.zeros(index.sum())
|
|
392
|
+
if fitfreqs["glitchfit"]:
|
|
393
|
+
glitchpar = np.zeros((index.sum(), 3))
|
|
394
|
+
for indd, ind in enumerate(np.where(index)[0]):
|
|
395
|
+
chi2_freq, warn, shapewarn, addpars = stats.chi2_astero(
|
|
396
|
+
obskey,
|
|
397
|
+
obs,
|
|
398
|
+
obsfreqmeta,
|
|
399
|
+
obsfreqdata,
|
|
400
|
+
obsintervals,
|
|
401
|
+
libitem,
|
|
402
|
+
ind,
|
|
403
|
+
fitfreqs,
|
|
404
|
+
warnings=warn,
|
|
405
|
+
shapewarn=shapewarn,
|
|
406
|
+
debug=debug,
|
|
407
|
+
verbose=verbose,
|
|
408
|
+
)
|
|
409
|
+
chi2[indd] += chi2_freq
|
|
410
|
+
|
|
411
|
+
if fitfreqs["dnufit_in_ratios"]:
|
|
412
|
+
dnusurf[indd] = addpars["dnusurf"]
|
|
413
|
+
if fitfreqs["glitchfit"]:
|
|
414
|
+
glitchpar[indd] = addpars["glitchparams"]
|
|
415
|
+
|
|
416
|
+
# Bayesian weights (across tracks/isochrones)
|
|
417
|
+
logPDF = 0.0
|
|
418
|
+
if debug:
|
|
419
|
+
bayw = 0.0
|
|
420
|
+
magw = 0.0
|
|
421
|
+
IMFw = 0.0
|
|
422
|
+
if bayweights is not None:
|
|
423
|
+
for weight in bayweights:
|
|
424
|
+
logPDF += util.inflog(libitem[weight][()])
|
|
425
|
+
if debug:
|
|
426
|
+
bayw += util.inflog(libitem[weight][()])
|
|
427
|
+
|
|
428
|
+
# Within a given track/isochrone; these are called dweights
|
|
429
|
+
assert dweight is not None
|
|
430
|
+
logPDF += util.inflog(libitem[dweight][index])
|
|
431
|
+
if debug:
|
|
432
|
+
bayw += util.inflog(libitem[dweight][index])
|
|
433
|
+
|
|
434
|
+
# Multiply by absolute magnitudes, if present
|
|
435
|
+
for f in inputparams["magnitudes"]:
|
|
436
|
+
mags = inputparams["magnitudes"][f]["prior"]
|
|
437
|
+
absmags = libitem[f][index]
|
|
438
|
+
interp_mags = mags(absmags)
|
|
439
|
+
|
|
440
|
+
logPDF += util.inflog(interp_mags)
|
|
441
|
+
if debug:
|
|
442
|
+
magw += util.inflog(interp_mags)
|
|
443
|
+
|
|
444
|
+
# Multiply priors into the weight
|
|
445
|
+
for prior in usepriors:
|
|
446
|
+
logPDF += util.inflog(getattr(priors, prior)(libitem, index))
|
|
447
|
+
if debug:
|
|
448
|
+
IMFw += util.inflog(getattr(priors, prior)(libitem, index))
|
|
449
|
+
|
|
450
|
+
# Calculate likelihood from weights, priors and chi2
|
|
451
|
+
# PDF = weights * np.exp(-0.5 * chi2)
|
|
452
|
+
logPDF -= 0.5 * chi2
|
|
453
|
+
if debug and verbose:
|
|
454
|
+
print(
|
|
455
|
+
"DEBUG: Mass with nonzero likelihood:",
|
|
456
|
+
libitem["massini"][index][~np.isinf(logPDF)],
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
# Sum the number indexes and nonzero indexes
|
|
460
|
+
noofind += len(logPDF)
|
|
461
|
+
noofposind += np.count_nonzero(~np.isinf(logPDF))
|
|
462
|
+
if debug and verbose:
|
|
463
|
+
print(
|
|
464
|
+
f"DEBUG: Index found: {group_name + name}, {~np.isinf(logPDF)}"
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Store statistical info
|
|
468
|
+
if debug:
|
|
469
|
+
selectedmodels[group_name + name] = stats.priorlogPDF(
|
|
470
|
+
index, logPDF, chi2, bayw, magw, IMFw
|
|
471
|
+
)
|
|
472
|
+
else:
|
|
473
|
+
selectedmodels[group_name + name] = stats.Trackstats(
|
|
474
|
+
index, logPDF, chi2
|
|
475
|
+
)
|
|
476
|
+
if fitfreqs["active"] and fitfreqs["dnufit_in_ratios"]:
|
|
477
|
+
dnusurfmodels[group_name + name] = stats.Trackdnusurf(dnusurf)
|
|
478
|
+
if fitfreqs["active"] and fitfreqs["glitchfit"]:
|
|
479
|
+
glitchmodels[group_name + name] = stats.Trackglitchpar(
|
|
480
|
+
glitchpar[:, 0],
|
|
481
|
+
glitchpar[:, 1],
|
|
482
|
+
glitchpar[:, 2],
|
|
483
|
+
)
|
|
484
|
+
else:
|
|
485
|
+
if debug and verbose:
|
|
486
|
+
print(
|
|
487
|
+
f"DEBUG: Index not found: {group_name + name}, {~np.isinf(logPDF)}"
|
|
488
|
+
)
|
|
489
|
+
# End loop over isochrones/tracks
|
|
490
|
+
#######################################################################
|
|
491
|
+
# End loop over metals
|
|
492
|
+
###########################################################################
|
|
493
|
+
pbar.close()
|
|
494
|
+
print(
|
|
495
|
+
f"Done! Computed the likelihood of {str(noofind)} models,",
|
|
496
|
+
f"found {str(noofposind)} models with non-zero likelihood!\n",
|
|
497
|
+
)
|
|
498
|
+
if gridcut:
|
|
499
|
+
print(
|
|
500
|
+
f"(Note: The use of 'gridcut' skipped {noofskips[0]} out of {noofskips[1]} {gridtype})\n"
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
# Raise possible warnings
|
|
504
|
+
if shapewarn == 1:
|
|
505
|
+
print(
|
|
506
|
+
"Warning: Found models with fewer frequencies than observed!",
|
|
507
|
+
"These were set to zero likelihood!",
|
|
508
|
+
)
|
|
509
|
+
if "intpol" in gridfile:
|
|
510
|
+
print(
|
|
511
|
+
"This is probably due to the interpolation scheme. Lookup",
|
|
512
|
+
"`interpolate_frequencies` for more details.",
|
|
513
|
+
)
|
|
514
|
+
if shapewarn == 2:
|
|
515
|
+
print(
|
|
516
|
+
"Warning: Models without frequencies overlapping with observed",
|
|
517
|
+
"ignored due to interpolation of ratios being impossible.",
|
|
518
|
+
)
|
|
519
|
+
if shapewarn == 3:
|
|
520
|
+
print(
|
|
521
|
+
"Warning: Models ignored due to phase shift differences being",
|
|
522
|
+
"unapplicable to models with mixed modes.",
|
|
523
|
+
)
|
|
524
|
+
if noofposind == 0:
|
|
525
|
+
fio.no_models(starid, inputparams, "No models found")
|
|
526
|
+
return
|
|
527
|
+
|
|
528
|
+
# Print a header to signal the start of the output section in the log
|
|
529
|
+
print("\n*****************************************")
|
|
530
|
+
print("** **")
|
|
531
|
+
print("** Output and results from the fit **")
|
|
532
|
+
print("** **")
|
|
533
|
+
print("*****************************************\n")
|
|
534
|
+
|
|
535
|
+
# Find and print highest likelihood model info
|
|
536
|
+
maxPDF_path, maxPDF_ind = stats.get_highest_likelihood(
|
|
537
|
+
Grid, selectedmodels, inputparams
|
|
538
|
+
)
|
|
539
|
+
stats.get_lowest_chi2(Grid, selectedmodels, inputparams)
|
|
540
|
+
|
|
541
|
+
# Generate posteriors of ascii- and plotparams
|
|
542
|
+
# --> Print posteriors to console and log
|
|
543
|
+
# --> Generate corner plots
|
|
544
|
+
# --> Generate Kiel diagrams
|
|
545
|
+
print("\n\nComputing posterior distributions for the requested output parameters!")
|
|
546
|
+
print("==> Summary statistics printed below ...\n")
|
|
547
|
+
process_output.compute_posterior(
|
|
548
|
+
starid=starid,
|
|
549
|
+
selectedmodels=selectedmodels,
|
|
550
|
+
Grid=Grid,
|
|
551
|
+
inputparams=inputparams,
|
|
552
|
+
outfilename=outfilename,
|
|
553
|
+
gridtype=gridtype,
|
|
554
|
+
debug=debug,
|
|
555
|
+
developermode=developermode,
|
|
556
|
+
validationmode=validationmode,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
# Collect additional output for plotting and saving
|
|
560
|
+
addstats = {}
|
|
561
|
+
if fitfreqs["active"] and fitfreqs["dnufit_in_ratios"]:
|
|
562
|
+
addstats["dnusurf"] = dnusurfmodels
|
|
563
|
+
if fitfreqs["active"] and fitfreqs["glitchfit"]:
|
|
564
|
+
addstats["glitchparams"] = glitchmodels
|
|
565
|
+
|
|
566
|
+
# Make frequency-related plots
|
|
567
|
+
freqplots = inputparams.get("freqplots")
|
|
568
|
+
if fitfreqs["active"] and len(freqplots):
|
|
569
|
+
plot_driver.plot_all_seismic(
|
|
570
|
+
freqplots,
|
|
571
|
+
Grid=Grid,
|
|
572
|
+
fitfreqs=fitfreqs,
|
|
573
|
+
obsfreqmeta=obsfreqmeta,
|
|
574
|
+
obsfreqdata=obsfreqdata,
|
|
575
|
+
obskey=obskey,
|
|
576
|
+
obs=obs,
|
|
577
|
+
obsintervals=obsintervals,
|
|
578
|
+
selectedmodels=selectedmodels,
|
|
579
|
+
path=maxPDF_path,
|
|
580
|
+
ind=maxPDF_ind,
|
|
581
|
+
plotfname=outfilename + "_{0}." + inputparams["plotfmt"],
|
|
582
|
+
nameinplot=inputparams["nameinplot"],
|
|
583
|
+
**addstats,
|
|
584
|
+
debug=debug,
|
|
585
|
+
)
|
|
586
|
+
else:
|
|
587
|
+
print(
|
|
588
|
+
"Did not get any frequency file input, skipping ratios and echelle plots."
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
# Save dictionary with full statistics
|
|
592
|
+
if optionaloutputs:
|
|
593
|
+
pfname = outfilename + ".json"
|
|
594
|
+
fio.save_selectedmodels(pfname, selectedmodels)
|
|
595
|
+
print(f"Saved dictionary to {pfname}")
|
|
596
|
+
|
|
597
|
+
# Print time of completion
|
|
598
|
+
t1 = time.localtime()
|
|
599
|
+
print(
|
|
600
|
+
f"\nFinished on {time.strftime('%Y-%m-%d %H:%M:%S', t1)}",
|
|
601
|
+
f"(runtime {time.mktime(t1) - time.mktime(t0)} s).\n",
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
# Save log and recover standard output
|
|
605
|
+
sys.stdout = stdout
|
|
606
|
+
print(f"Saved log to {outfilename}.log")
|
|
607
|
+
|
|
608
|
+
# Close grid, close open plots, and try to free memory between multiple runs
|
|
609
|
+
Grid.close()
|
|
610
|
+
plt.close("all")
|
|
611
|
+
gc.collect()
|