figaro 1.4.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.
- figaro/__init__.py +0 -0
- figaro/_likelihood.py +159 -0
- figaro/_numba_functions.py +68 -0
- figaro/_pipelines/__init__.py +0 -0
- figaro/_pipelines/create_glade.py +135 -0
- figaro/_pipelines/hierarchical_inference.py +200 -0
- figaro/_pipelines/par_hierarchical_inference.py +315 -0
- figaro/_pipelines/par_probability_density.py +187 -0
- figaro/_pipelines/probability_density.py +154 -0
- figaro/cosmology.py +64 -0
- figaro/credible_regions.py +176 -0
- figaro/cumulative.py +39 -0
- figaro/decorators.py +38 -0
- figaro/diagnostic.py +232 -0
- figaro/exceptions.py +24 -0
- figaro/load.py +579 -0
- figaro/marginal.py +152 -0
- figaro/mixture.py +1359 -0
- figaro/montecarlo.py +66 -0
- figaro/plot.py +906 -0
- figaro/plot_settings.py +29 -0
- figaro/transform.py +72 -0
- figaro/utils.py +360 -0
- figaro-1.4.0.dist-info/LICENSE +21 -0
- figaro-1.4.0.dist-info/METADATA +84 -0
- figaro-1.4.0.dist-info/RECORD +29 -0
- figaro-1.4.0.dist-info/WHEEL +5 -0
- figaro-1.4.0.dist-info/entry_points.txt +5 -0
- figaro-1.4.0.dist-info/top_level.txt +1 -0
figaro/__init__.py
ADDED
|
File without changes
|
figaro/_likelihood.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from figaro._numba_functions import *
|
|
3
|
+
|
|
4
|
+
LOG2PI = np.log(2*np.pi)
|
|
5
|
+
|
|
6
|
+
@njit
|
|
7
|
+
def scalar_product(v, M, n):
|
|
8
|
+
"""
|
|
9
|
+
Scalar product: v*M*v^T
|
|
10
|
+
|
|
11
|
+
Arguments:
|
|
12
|
+
np.ndarray v: array
|
|
13
|
+
np.ndarray M: matrix
|
|
14
|
+
int n: len(v)
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
double: v*M*v^T
|
|
18
|
+
"""
|
|
19
|
+
res = 0.
|
|
20
|
+
for i in prange(n):
|
|
21
|
+
for j in prange(n):
|
|
22
|
+
res = res + M[i,j]*v[i]*v[j]
|
|
23
|
+
return res
|
|
24
|
+
|
|
25
|
+
@njit
|
|
26
|
+
def log_norm_1d(x, m, s):
|
|
27
|
+
"""
|
|
28
|
+
1D Normal logpdf
|
|
29
|
+
|
|
30
|
+
Arguments:
|
|
31
|
+
double x: value
|
|
32
|
+
double m: mean
|
|
33
|
+
double s: var
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Normal(m,s).logpdf(x)
|
|
37
|
+
"""
|
|
38
|
+
return -(x-m)**2/(2*s) - 0.5*np.log(2*np.pi*s)
|
|
39
|
+
|
|
40
|
+
@njit
|
|
41
|
+
def log_norm(x, mu, cov):
|
|
42
|
+
"""
|
|
43
|
+
Multivariate Normal logpdf
|
|
44
|
+
|
|
45
|
+
Arguments:
|
|
46
|
+
np.ndarray x: value
|
|
47
|
+
np.ndarray mu: mean vector
|
|
48
|
+
np.ndarray cov: covariance matrix
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
double: MultivariateNormal(m,s).logpdf(x)
|
|
52
|
+
"""
|
|
53
|
+
inv_cov = inv_jit(cov)
|
|
54
|
+
exponent = -0.5*scalar_product(x-mu, inv_cov, len(mu))
|
|
55
|
+
lognorm = 0.5*len(mu)*LOG2PI+0.5*logdet_jit(cov)
|
|
56
|
+
return -lognorm+exponent
|
|
57
|
+
|
|
58
|
+
@njit
|
|
59
|
+
def log_norm_int(x, mu, cov_1, inv_cov_1, cov_2):
|
|
60
|
+
"""
|
|
61
|
+
Multivariate Normal logpdf (tailored to this problem!)
|
|
62
|
+
See https://arxiv.org/pdf/1811.04751v1.pdf
|
|
63
|
+
|
|
64
|
+
Arguments:
|
|
65
|
+
np.ndarray x: value
|
|
66
|
+
np.ndarray mu: mean vector
|
|
67
|
+
np.ndarray cov_1: 1st covariance matrix
|
|
68
|
+
np.ndarray inv_cov_1: inverse of 1st covariance matrix
|
|
69
|
+
np.ndarray cov_2: 2nd covariance matrix
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
double: MultivariateNormal(m,s).logpdf(x)
|
|
73
|
+
"""
|
|
74
|
+
inv_cov_2 = inv_jit(cov_2)
|
|
75
|
+
inv_cov = inv_cov_1@inv_jit(inv_cov_1+inv_cov_2)@inv_cov_2
|
|
76
|
+
exponent = -0.5*scalar_product(x-mu, inv_cov, len(mu))
|
|
77
|
+
lognorm = 0.5*len(mu)*LOG2PI+0.5*(logdet_jit(cov_1) + logdet_jit(cov_2) + logdet_jit(inv_cov_1+inv_cov_2))
|
|
78
|
+
return -lognorm+exponent
|
|
79
|
+
|
|
80
|
+
#------------#
|
|
81
|
+
# 1D methods #
|
|
82
|
+
#------------#
|
|
83
|
+
|
|
84
|
+
@njit
|
|
85
|
+
def eval_mix_1d(mu, sigma, means, covs):
|
|
86
|
+
"""
|
|
87
|
+
Computes N(mu_k| mu, (sigma_k^2+sigma^2) for all the components of a mixture (for predictive likelihood, 1D).
|
|
88
|
+
|
|
89
|
+
Arguments:
|
|
90
|
+
np.ndarray mu: temptative mean of the parent mixture component
|
|
91
|
+
np.ndarray sigma: temptative variance of the parent mixture component
|
|
92
|
+
np.ndarray means: means of the event mixture components
|
|
93
|
+
np.ndarray vars: variances of the event mixture components
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
np.ndarray: probability for each event mixture components
|
|
97
|
+
"""
|
|
98
|
+
return np.array([log_norm_1d(means[i,0], mu, sigma+covs[i,0,0]) for i in prange(len(means))])
|
|
99
|
+
|
|
100
|
+
@njit
|
|
101
|
+
def evaluate_mixture_MC_draws_1d(mu, sigma, means, vars, w):
|
|
102
|
+
"""
|
|
103
|
+
Computes N(mu_k| mu, (sigma_k^2+sigma^2) for a set of MC draws for mu and sigma.
|
|
104
|
+
|
|
105
|
+
Arguments:
|
|
106
|
+
np.ndarray mu: MC draws for the mean of the parent mixture component
|
|
107
|
+
np.ndarray sigma: MC draws for the variance of the parent mixture component
|
|
108
|
+
np.ndarray means: means of the event mixture components
|
|
109
|
+
np.ndarray vars: variances of the event mixture components
|
|
110
|
+
np.ndarray w: component weights
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
np.ndarray: probability for each MC draw
|
|
114
|
+
"""
|
|
115
|
+
logP = np.zeros(len(mu), dtype = np.float64)
|
|
116
|
+
for i in prange(len(mu)):
|
|
117
|
+
logP[i] = logsumexp_jit(eval_mix_1d(mu[i], sigma[i], means, vars), b = w)
|
|
118
|
+
return logP
|
|
119
|
+
|
|
120
|
+
#------------#
|
|
121
|
+
# ND methods #
|
|
122
|
+
#------------#
|
|
123
|
+
|
|
124
|
+
@njit
|
|
125
|
+
def eval_mix(mu, sigma, means, covs):
|
|
126
|
+
"""
|
|
127
|
+
Computes N(mu_k| mu, (sigma_k^2+sigma^2) for all the components of a mixture (for predictive likelihood, ND).
|
|
128
|
+
|
|
129
|
+
Arguments:
|
|
130
|
+
np.ndarray mu: temptative mean of the parent mixture component
|
|
131
|
+
np.ndarray sigma: temptative covariance matrix of the parent mixture component
|
|
132
|
+
np.ndarray means: means of the event mixture components
|
|
133
|
+
np.ndarray covs: covariance matrices of the event mixture components
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
np.ndarray: probability for each event mixture components
|
|
137
|
+
"""
|
|
138
|
+
inv_sigma = inv_jit(sigma)
|
|
139
|
+
return np.array([log_norm_int(means[i], mu, sigma, inv_sigma, covs[i]) for i in prange(len(means))])
|
|
140
|
+
|
|
141
|
+
@njit
|
|
142
|
+
def evaluate_mixture_MC_draws(mu, sigma, means, covs, w):
|
|
143
|
+
"""
|
|
144
|
+
Computes N(mu_k| mu, (sigma_k^2+sigma^2) for a set of MC draws for mu and sigma.
|
|
145
|
+
|
|
146
|
+
Arguments:
|
|
147
|
+
np.ndarray mu: MC draws for the mean vector of the parent mixture component
|
|
148
|
+
np.ndarray sigma: MC draws for the covariance matrix of the parent mixture component
|
|
149
|
+
np.ndarray means: means of the event mixture components
|
|
150
|
+
np.ndarray covs: covariance matrices of the event mixture components
|
|
151
|
+
np.ndarray w: component weights
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
np.ndarray: probability for each MC draw
|
|
155
|
+
"""
|
|
156
|
+
logP = np.zeros(len(mu), dtype = np.float64)
|
|
157
|
+
for i in prange(len(mu)):
|
|
158
|
+
logP[i] = logsumexp_jit(eval_mix(mu[i], sigma[i], means, covs), b = w)
|
|
159
|
+
return logP
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numba import njit, prange
|
|
3
|
+
from numba.extending import get_cython_function_address
|
|
4
|
+
import ctypes
|
|
5
|
+
|
|
6
|
+
@njit
|
|
7
|
+
def inv_jit(M):
|
|
8
|
+
return np.linalg.inv(M)
|
|
9
|
+
|
|
10
|
+
@njit
|
|
11
|
+
def logdet_jit(M):
|
|
12
|
+
return np.log(np.linalg.det(M))
|
|
13
|
+
|
|
14
|
+
@njit
|
|
15
|
+
def logsumexp_jit(a, b):
|
|
16
|
+
a_max = np.max(a)
|
|
17
|
+
tmp = b * np.exp(a - a_max)
|
|
18
|
+
return np.log(np.sum(tmp)) + a_max
|
|
19
|
+
|
|
20
|
+
@njit
|
|
21
|
+
def log_add(x, y):
|
|
22
|
+
if x >= y:
|
|
23
|
+
return x+_log1p_jit(np.exp(y-x))
|
|
24
|
+
else:
|
|
25
|
+
return y+_log1p_jit(np.exp(x-y))
|
|
26
|
+
|
|
27
|
+
@njit
|
|
28
|
+
def _outer_jit(x, y):
|
|
29
|
+
return np.outer(x, y)
|
|
30
|
+
|
|
31
|
+
@njit
|
|
32
|
+
def _divide_jit(x, y):
|
|
33
|
+
return np.divide(x, y)
|
|
34
|
+
|
|
35
|
+
@njit
|
|
36
|
+
def _diag_jit(m):
|
|
37
|
+
return np.diag(m)
|
|
38
|
+
|
|
39
|
+
@njit
|
|
40
|
+
def _rescale_matrix(S, n):
|
|
41
|
+
std = np.sqrt(_diag_jit(S))
|
|
42
|
+
rho = _divide_jit(S, _outer_jit(std,std))
|
|
43
|
+
return rho * _outer_jit(std/np.sqrt(n), std/np.sqrt(n))
|
|
44
|
+
|
|
45
|
+
@njit
|
|
46
|
+
def _eigh_jit(m):
|
|
47
|
+
return np.linalg.eigh(m)
|
|
48
|
+
|
|
49
|
+
@njit
|
|
50
|
+
def _log1p_jit(x):
|
|
51
|
+
return np.log1p(x)
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
See https://stackoverflow.com/a/54855769
|
|
55
|
+
Wrapper (based on https://github.com/numba/numba/issues/3086) for scipy's cython implementation of gammaln.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
_PTR = ctypes.POINTER
|
|
59
|
+
_dble = ctypes.c_double
|
|
60
|
+
_ptr_dble = _PTR(_dble)
|
|
61
|
+
|
|
62
|
+
addr = get_cython_function_address("scipy.special.cython_special", "gammaln")
|
|
63
|
+
functype = ctypes.CFUNCTYPE(_dble, _dble)
|
|
64
|
+
gammaln_float64 = functype(addr)
|
|
65
|
+
|
|
66
|
+
@njit
|
|
67
|
+
def _gammaln_jit(x):
|
|
68
|
+
return gammaln_float64(x)
|
|
File without changes
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#from astropy.table import Table
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
import numpy as np
|
|
4
|
+
#from tqdm import tqdm
|
|
5
|
+
#import healpy as hp
|
|
6
|
+
import h5py
|
|
7
|
+
import pandas as pd
|
|
8
|
+
#from tqdm import tqdm
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import optparse as op
|
|
11
|
+
|
|
12
|
+
'''
|
|
13
|
+
GLADE+ columns (29/04/2022)
|
|
14
|
+
|
|
15
|
+
0 GLADE no GLADE+ catalog number
|
|
16
|
+
1 PGC no Principal Galaxies Catalogue number
|
|
17
|
+
2 GWGC name Name in the GWGC catalog
|
|
18
|
+
3 HyperLEDA name Name in the HyperLEDA catalog
|
|
19
|
+
4 2MASS name Name in the 2MASS XSC catalog
|
|
20
|
+
5 WISExSCOS name Name in the WISExSuperCOSMOS catalog (wiseX)
|
|
21
|
+
6 SDSS-DR16Q name Name in the SDSS-DR16Q catalog
|
|
22
|
+
7 Object type flag Q: the source is from the SDSS-DR16Q catalog, G:the source is from another catalog and has not been identified as a quasar
|
|
23
|
+
8 RA Right ascension in degrees
|
|
24
|
+
9 Dec Declination in degrees
|
|
25
|
+
10 B Apparent B magnitude
|
|
26
|
+
11 B_err Absolute error of apparent B magnitude
|
|
27
|
+
12 B flag 0: the B magnitude is measured, 1: the B magnitude is calculated from the B_J magnitude
|
|
28
|
+
13 B_Abs Absolute B magnitude
|
|
29
|
+
14 J Apparent J magnitude
|
|
30
|
+
15 J_err Absolute error of apparent J magnitude
|
|
31
|
+
16 H Apparent H magnitude
|
|
32
|
+
17 H_err Absolute error of apparent H magnitude
|
|
33
|
+
18 K Apparent K_s magnitude
|
|
34
|
+
19 K_err Absolute error of apparent K_s magnitude
|
|
35
|
+
20 W1 Apparent W1 magnitude
|
|
36
|
+
21 W1_err Absolute error of apparent W1 magnitude
|
|
37
|
+
22 W2 Apparent W2 magnitude
|
|
38
|
+
23 W2_err Absolute error of apparent W2 magnitude
|
|
39
|
+
24 W1 flag 0: the W1 magnitude is measured, 1: the W1 magnitude is calculated from the K_s magnitude
|
|
40
|
+
25 B_J Apparent B_J magnitude
|
|
41
|
+
26 B_J err Absolute error of apparent B_J magnitude
|
|
42
|
+
27 z_helio Redshift in the heliocentric frame
|
|
43
|
+
28 z_cmb Redshift converted to the Cosmic Microwave Background (CMB) frame
|
|
44
|
+
29 z flag 0: the CMB frame redshift and luminosity distance values given in columns 25 and 28 are not corrected for the peculiar velocity, 1: they are corrected values
|
|
45
|
+
30 v_err Error of redshift from the peculiar velocity estimation
|
|
46
|
+
31 z_err Measurement error of heliocentric redshift
|
|
47
|
+
32 d_L Luminosity distance in Mpc units
|
|
48
|
+
33 d_L err Error of luminosity distance in Mpc units
|
|
49
|
+
34 dist flag 0: the galaxy has no measured redshift or distance value, 1: it has a measured photometric redshift from which we have calculated its luminosity distance, 2: it has a measured luminosity distance value from which we have calculated its redshift, 3: it has a measured spectroscopic redshift from which we have calculated its luminosity distance
|
|
50
|
+
35 M* Stellar mass in 10^10 M_Sun units
|
|
51
|
+
36 M*_err Absolute error of stellar mass in 10^10 M_Sun units
|
|
52
|
+
37 M* flag 0: if the stellar mass was calculated assuming no active star formation, 1: if the stellar mass was calculated assuming active star formation
|
|
53
|
+
38 Merger rate Base-10 logarithm of estimated BNS merger rate in the galaxy in Gyr^-1 units
|
|
54
|
+
39 Merger rate error Absolute error of estimated BNS merger rate in the galaxy
|
|
55
|
+
'''
|
|
56
|
+
|
|
57
|
+
def main():
|
|
58
|
+
"""
|
|
59
|
+
Preprocess the .txt file downloaded from the GLADE+ website - http://glade.elte.hu - selecting only some columns.
|
|
60
|
+
The output file is a lighter .hdf5 file, saved in the same folder as the .txt file, that has the structure required by figaro.threeDvolume.VolumeReconstruction.load_glade
|
|
61
|
+
"""
|
|
62
|
+
parser = op.OptionParser()
|
|
63
|
+
parser.add_option("-i", type = "string", dest = "glade_file", help = "GLADE+ txt file")
|
|
64
|
+
|
|
65
|
+
(options, args) = parser.parse_args()
|
|
66
|
+
glade_file = Path(options.glade_file).resolve()
|
|
67
|
+
glade_folder = glade_file.parent
|
|
68
|
+
|
|
69
|
+
dict_out={}
|
|
70
|
+
|
|
71
|
+
# Load all the columns of interest, please note that usecols must be in creasing order
|
|
72
|
+
chunk = pd.read_csv(glade_file,
|
|
73
|
+
usecols=(7, 8, 9, 10, 12, 18, 20, 25, 27, 28, 29, 30, 31, 34),
|
|
74
|
+
header=None,
|
|
75
|
+
names=['objtype', 'ra', 'dec', 'B', 'Bflag', 'K', 'W1', 'bJ', 'zhelio', 'zcmb', 'pecflag', 'pecerr', 'zhelioerr', 'redflag'],
|
|
76
|
+
delim_whitespace=True,
|
|
77
|
+
na_values="null",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Check the galaxies
|
|
81
|
+
COND_GALAXY = (chunk['objtype']=='G')
|
|
82
|
+
|
|
83
|
+
# Check if the redshift is measured and not obtained from dl
|
|
84
|
+
COND_RED_ORIGIN = True#(chunk['redflag']==1) | (chunk['redflag']==3)
|
|
85
|
+
|
|
86
|
+
# Check if peculiar corrections are applied. If not, saves the galaxy only if it is above redshift 0.5
|
|
87
|
+
COND_PEC = (chunk['zcmb']>0.05) | (chunk['pecflag']==1)
|
|
88
|
+
|
|
89
|
+
# Check if cmb redshift is positive
|
|
90
|
+
COND_NEGATIVE_RED = chunk['zcmb']>0
|
|
91
|
+
|
|
92
|
+
# Combine the above conditions, must be all true
|
|
93
|
+
idx=np.where(COND_GALAXY & COND_RED_ORIGIN & COND_PEC & COND_NEGATIVE_RED)[0]
|
|
94
|
+
|
|
95
|
+
# Saves the columns in a disctionary as numpy arrays
|
|
96
|
+
for kk in ['objtype',"ra", "dec", "B", "Bflag", "bJ",'K','W1','zhelio','zcmb','pecflag','pecerr','zhelioerr','redflag']:
|
|
97
|
+
dict_out[kk]=chunk[kk][idx].to_numpy()
|
|
98
|
+
|
|
99
|
+
# Save GLADE catalog
|
|
100
|
+
bands=['B', 'K','W1','bJ']
|
|
101
|
+
nGal = len(dict_out['zcmb'])
|
|
102
|
+
|
|
103
|
+
# Converts to radians
|
|
104
|
+
dict_out['ra']*=np.pi/180
|
|
105
|
+
dict_out['dec']*=np.pi/180
|
|
106
|
+
|
|
107
|
+
# # Finds the numpy index, just as general info
|
|
108
|
+
# nside = 1024
|
|
109
|
+
# ind = hp.pixelfunc.ang2pix(nside,dict_out['dec']+np.pi/2,dict_out['ra'])
|
|
110
|
+
|
|
111
|
+
# Limits for an all sky catalogs
|
|
112
|
+
# ra_dec_lim = 0
|
|
113
|
+
# ra_min = 0.0
|
|
114
|
+
# ra_max = np.pi*2.0
|
|
115
|
+
# dec_min = -np.pi/2.0
|
|
116
|
+
# dec_max = np.pi/2.0
|
|
117
|
+
|
|
118
|
+
# Just a test print. I wanted to check how many galaxies had B from bj. I got around 10%
|
|
119
|
+
# idx0=np.where(dict_out['Bflag']==0)[0]
|
|
120
|
+
# idx1=np.where(dict_out['Bflag']==1)[0]
|
|
121
|
+
# print('bJflag ratio 0/1', len(idx0)/len(idx1))
|
|
122
|
+
|
|
123
|
+
# Saves the catalog in hdf5 format
|
|
124
|
+
with h5py.File(Path(glade_folder, "glade+.hdf5"), "w") as f:
|
|
125
|
+
f.create_dataset("ra", data=dict_out['ra'])
|
|
126
|
+
f.create_dataset("dec", data=dict_out['dec'])
|
|
127
|
+
f.create_dataset("z", data=dict_out['zcmb'])
|
|
128
|
+
# f.create_dataset("sigmaz", data=(dict_out['zhelioerr']/dict_out['zhelio'])*dict_out['zcmb'])
|
|
129
|
+
# f.create_dataset("skymap_indices", data=ind)
|
|
130
|
+
# f.create_dataset("radec_lim", data=np.array([ra_dec_lim,ra_min,ra_max,dec_min,dec_max]))
|
|
131
|
+
for j in range(len(bands)):
|
|
132
|
+
f.create_dataset("m_{0}".format(bands[j]), data=dict_out[bands[j]])
|
|
133
|
+
|
|
134
|
+
if __name__ == '__main__':
|
|
135
|
+
main()
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
import optparse as op
|
|
4
|
+
import importlib
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from tqdm import tqdm
|
|
8
|
+
|
|
9
|
+
from figaro.mixture import DPGMM, HDPGMM
|
|
10
|
+
from figaro.transform import transform_to_probit
|
|
11
|
+
from figaro.utils import save_options, load_options, get_priors
|
|
12
|
+
from figaro.plot import plot_median_cr, plot_multidim
|
|
13
|
+
from figaro.load import load_data, load_single_event, save_density, load_density
|
|
14
|
+
|
|
15
|
+
def main():
|
|
16
|
+
|
|
17
|
+
parser = op.OptionParser()
|
|
18
|
+
# Input/output
|
|
19
|
+
parser.add_option("-i", "--input", type = "string", dest = "samples_folder", help = "Folder with single-event samples files", default = None)
|
|
20
|
+
parser.add_option("-b", "--bounds", type = "string", dest = "bounds", help = "Density bounds. Must be a string formatted as '[[xmin, xmax], [ymin, ymax],...]'. For 1D distributions use '[xmin, xmax]'. Quotation marks are required and scientific notation is accepted", default = None)
|
|
21
|
+
parser.add_option("-o", "--output", type = "string", dest = "output", help = "Output folder. Default: same directory as samples folder", default = None)
|
|
22
|
+
parser.add_option("-j", dest = "json", action = 'store_true', help = "Save mixtures in json file", default = False)
|
|
23
|
+
parser.add_option("--inj_density", type = "string", dest = "inj_density_file", help = "Python module with injected density - please name the method 'density'", default = None)
|
|
24
|
+
parser.add_option("--selfunc", type = "string", dest = "selfunc_file", help = "Python module with selection function - please name the method 'selection_function'", default = None)
|
|
25
|
+
parser.add_option("--parameter", type = "string", dest = "par", help = "GW parameter(s) to be read from files", default = None)
|
|
26
|
+
parser.add_option("--waveform", type = "string", dest = "wf", help = "Waveform to load from samples file. To be used in combination with --parameter. Accepted values: 'combined', 'imr', 'seob'", default = 'combined')
|
|
27
|
+
# Plot
|
|
28
|
+
parser.add_option("--name", type = "string", dest = "h_name", help = "Name to be given to hierarchical inference files. Default: same name as samples folder parent directory", default = None)
|
|
29
|
+
parser.add_option("-p", "--postprocess", dest = "postprocess", action = 'store_true', help = "Postprocessing", default = False)
|
|
30
|
+
parser.add_option("-s", "--save_se", dest = "save_single_event", action = 'store_true', help = "Save single event plots", default = False)
|
|
31
|
+
parser.add_option("--symbol", type = "string", dest = "symbol", help = "LaTeX-style quantity symbol, for plotting purposes", default = None)
|
|
32
|
+
parser.add_option("--unit", type = "string", dest = "unit", help = "LaTeX-style quantity unit, for plotting purposes", default = None)
|
|
33
|
+
parser.add_option("--hier_samples", type = "string", dest = "true_vals", help = "Samples from hierarchical distribution (true single-event values, for simulations only)", default = None)
|
|
34
|
+
# Settings
|
|
35
|
+
parser.add_option("--draws", type = "int", dest = "n_draws", help = "Number of draws for hierarchical distribution", default = 100)
|
|
36
|
+
parser.add_option("--se_draws", type = "int", dest = "n_se_draws", help = "Number of draws for single-event distribution. Default: same as hierarchical distribution", default = None)
|
|
37
|
+
parser.add_option("--n_samples_dsp", type = "int", dest = "n_samples_dsp", help = "Number of samples to analyse (downsampling). Default: all", default = -1)
|
|
38
|
+
parser.add_option("--exclude_points", dest = "exclude_points", action = 'store_true', help = "Exclude points outside bounds from analysis", default = False)
|
|
39
|
+
parser.add_option("--cosmology", type = "string", dest = "cosmology", help = "Cosmological parameters (h, om, ol). Default values from Planck (2021)", default = '0.674,0.315,0.685')
|
|
40
|
+
parser.add_option("-e", "--events", dest = "run_events", action = 'store_false', help = "Skip single-event analysis", default = True)
|
|
41
|
+
parser.add_option("--se_sigma_prior", dest = "se_sigma_prior", type = "string", help = "Expected standard deviation (prior) for single-event inference - single value or n-dim values. If None, it is estimated from samples", default = None)
|
|
42
|
+
parser.add_option("--sigma_prior", dest = "sigma_prior", type = "string", help = "Expected standard deviation (prior) for hierarchical inference - single value or n-dim values. If None, it is estimated from samples", default = None)
|
|
43
|
+
parser.add_option("--fraction", dest = "scale", type = "float", help = "Fraction of samples standard deviation for sigma prior. Overrided by sigma_prior.", default = None)
|
|
44
|
+
parser.add_option("--mc_draws", dest = "mc_draws", type = "int", help = "Number of draws for assignment MC integral", default = None)
|
|
45
|
+
parser.add_option("--snr_threshold", dest = "snr_threshold", type = "float", help = "SNR threshold for simulated GW datasets", default = None)
|
|
46
|
+
parser.add_option("--far_threshold", dest = "far_threshold", type = "float", help = "FAR threshold for simulated GW datasets", default = None)
|
|
47
|
+
parser.add_option("--no_probit", dest = "probit", action = 'store_false', help = "Disable probit transformation", default = True)
|
|
48
|
+
parser.add_option("--config", dest = "config", type = "string", help = "Config file. Warning: command line options are ignored if provided", default = None)
|
|
49
|
+
|
|
50
|
+
(options, args) = parser.parse_args()
|
|
51
|
+
|
|
52
|
+
# Paths
|
|
53
|
+
if options.samples_folder is not None:
|
|
54
|
+
options.samples_folder = Path(options.samples_folder).resolve()
|
|
55
|
+
elif options.config is not None:
|
|
56
|
+
options.samples_folder = Path('.').resolve()
|
|
57
|
+
else:
|
|
58
|
+
raise Exception("Please provide path to samples.")
|
|
59
|
+
if options.output is not None:
|
|
60
|
+
options.output = Path(options.output).resolve()
|
|
61
|
+
if not options.output.exists():
|
|
62
|
+
options.output.mkdir(parents=True)
|
|
63
|
+
else:
|
|
64
|
+
options.output = options.samples_folder.parent
|
|
65
|
+
if options.config is not None:
|
|
66
|
+
options.config = Path(options.config).resolve()
|
|
67
|
+
output_plots = Path(options.output, 'plots')
|
|
68
|
+
if not output_plots.exists():
|
|
69
|
+
output_plots.mkdir()
|
|
70
|
+
output_draws = Path(options.output, 'draws')
|
|
71
|
+
if not output_draws.exists():
|
|
72
|
+
output_draws.mkdir()
|
|
73
|
+
# Read hierarchical name
|
|
74
|
+
if options.h_name is None:
|
|
75
|
+
options.h_name = options.samples_folder.parent.parts[-1]
|
|
76
|
+
# File extension
|
|
77
|
+
if options.json:
|
|
78
|
+
options.ext = 'json'
|
|
79
|
+
else:
|
|
80
|
+
options.ext = 'pkl'
|
|
81
|
+
|
|
82
|
+
if options.config is not None:
|
|
83
|
+
options = load_options(options, options.config)
|
|
84
|
+
save_options(options, options.output, name = options.h_name)
|
|
85
|
+
|
|
86
|
+
# Read bounds
|
|
87
|
+
if options.bounds is not None:
|
|
88
|
+
options.bounds = np.array(np.atleast_2d(eval(options.bounds)), dtype = np.float64)
|
|
89
|
+
elif options.bounds is None and not options.postprocess:
|
|
90
|
+
raise Exception("Please provide bounds for the inference (use -b '[[xmin,xmax],[ymin,ymax],...]')")
|
|
91
|
+
# Read cosmology
|
|
92
|
+
options.h, options.om, options.ol = (float(x) for x in options.cosmology.split(','))
|
|
93
|
+
# Read parameter(s)
|
|
94
|
+
if options.par is not None:
|
|
95
|
+
options.par = options.par.split(',')
|
|
96
|
+
# Read number of single-event draws
|
|
97
|
+
if options.n_se_draws is None:
|
|
98
|
+
options.n_se_draws = options.n_draws
|
|
99
|
+
if options.se_sigma_prior is not None:
|
|
100
|
+
options.se_sigma_prior = np.array([float(s) for s in options.se_sigma_prior.split(',')])
|
|
101
|
+
if options.sigma_prior is not None:
|
|
102
|
+
options.sigma_prior = np.array([float(s) for s in options.sigma_prior.split(',')])
|
|
103
|
+
|
|
104
|
+
# If provided, load injected density
|
|
105
|
+
inj_density = None
|
|
106
|
+
if options.inj_density_file is not None:
|
|
107
|
+
inj_file_name = Path(options.inj_density_file).parts[-1].split('.')[0]
|
|
108
|
+
spec = importlib.util.spec_from_file_location(inj_file_name, options.inj_density_file)
|
|
109
|
+
inj_module = importlib.util.module_from_spec(spec)
|
|
110
|
+
spec.loader.exec_module(inj_module)
|
|
111
|
+
inj_density = inj_module.density
|
|
112
|
+
#If provided, load selecton function
|
|
113
|
+
selfunc = None
|
|
114
|
+
if options.selfunc_file is not None:
|
|
115
|
+
selfunc_file_name = Path(options.selfunc_file).parts[-1].split('.')[0]
|
|
116
|
+
spec = importlib.util.spec_from_file_location(selfunc_file_name, options.selfunc_file)
|
|
117
|
+
selfunc_module = importlib.util.module_from_spec(spec)
|
|
118
|
+
spec.loader.exec_module(selfunc_module)
|
|
119
|
+
selfunc = selfunc_module.selection_function
|
|
120
|
+
# If provided, load true values
|
|
121
|
+
true_vals = None
|
|
122
|
+
if options.true_vals is not None:
|
|
123
|
+
options.true_vals = Path(options.true_vals).resolve()
|
|
124
|
+
true_vals, true_name = load_single_event(options.true_vals, par = options.par, h = options.h, om = options.om, ol = options.ol, waveform = options.wf)
|
|
125
|
+
if np.shape(true_vals)[-1] == 1:
|
|
126
|
+
true_vals = true_vals.flatten()
|
|
127
|
+
|
|
128
|
+
# Load samples
|
|
129
|
+
events, names = load_data(options.samples_folder, par = options.par, n_samples = options.n_samples_dsp, h = options.h, om = options.om, ol = options.ol, waveform = options.wf, snr_threshold = options.snr_threshold, far_threshold = options.far_threshold)
|
|
130
|
+
try:
|
|
131
|
+
dim = np.shape(events[0][0])[-1]
|
|
132
|
+
except IndexError:
|
|
133
|
+
dim = 1
|
|
134
|
+
if options.exclude_points:
|
|
135
|
+
print("Ignoring points outside bounds.")
|
|
136
|
+
for i, ev in enumerate(events):
|
|
137
|
+
events[i] = ev[np.where((np.prod(options.bounds[:,0] < ev, axis = 1) & np.prod(ev < options.bounds[:,1], axis = 1)))]
|
|
138
|
+
else:
|
|
139
|
+
# Check if all samples are within bounds
|
|
140
|
+
all_samples = np.atleast_2d(np.concatenate(events))
|
|
141
|
+
if options.probit:
|
|
142
|
+
if not np.all([(all_samples[:,i] > options.bounds[i,0]).all() and (all_samples[:,i] < options.bounds[i,1]).all() for i in range(dim)]):
|
|
143
|
+
raise ValueError("One or more samples are outside the given bounds.")
|
|
144
|
+
|
|
145
|
+
# Plot labels
|
|
146
|
+
if dim > 1:
|
|
147
|
+
if options.symbol is not None:
|
|
148
|
+
symbols = options.symbol.split(',')
|
|
149
|
+
else:
|
|
150
|
+
symbols = options.symbol
|
|
151
|
+
if options.unit is not None:
|
|
152
|
+
units = options.unit.split(',')
|
|
153
|
+
else:
|
|
154
|
+
units = options.unit
|
|
155
|
+
|
|
156
|
+
# Reconstruction
|
|
157
|
+
if not options.postprocess:
|
|
158
|
+
if options.run_events:
|
|
159
|
+
mix = DPGMM(options.bounds, probit = options.probit)
|
|
160
|
+
posteriors = []
|
|
161
|
+
# Run each single-event analysis
|
|
162
|
+
for i in tqdm(range(len(events)), desc = 'Events'):
|
|
163
|
+
ev = events[i]
|
|
164
|
+
name = names[i]
|
|
165
|
+
prior_pars = get_priors(mix.bounds, samples = ev, probit = options.probit, std = options.se_sigma_prior, scale = options.scale, hierarchical = False)
|
|
166
|
+
mix.initialise(prior_pars = prior_pars)
|
|
167
|
+
# Draw samples
|
|
168
|
+
draws = [mix.density_from_samples(ev) for _ in range(options.n_se_draws)]
|
|
169
|
+
posteriors.append(draws)
|
|
170
|
+
# Make plots
|
|
171
|
+
if options.save_single_event:
|
|
172
|
+
plt_bounds = np.atleast_2d([ev.min(axis = 0), ev.max(axis = 0)]).T
|
|
173
|
+
if dim == 1:
|
|
174
|
+
plot_median_cr(draws, samples = ev, bounds = plt_bounds[0], out_folder = output_plots, name = name, label = options.symbol, unit = options.unit, subfolder = True)
|
|
175
|
+
else:
|
|
176
|
+
plot_multidim(draws, samples = ev, bounds = plt_bounds, out_folder = output_plots, name = name, labels = symbols, units = units, subfolder = True)
|
|
177
|
+
# Save single-event draws
|
|
178
|
+
save_density(draws, folder = output_draws, name = 'draws_'+name, ext = options.ext)
|
|
179
|
+
# Save all single-event draws together
|
|
180
|
+
posteriors = np.array(posteriors)
|
|
181
|
+
save_density(posteriors, folder = output_draws, name = 'posteriors_single_event', ext = options.ext)
|
|
182
|
+
else:
|
|
183
|
+
# Load pre-computed posteriors
|
|
184
|
+
posteriors = load_density(Path(output_draws, 'posteriors_single_event.'+options.ext))
|
|
185
|
+
# Run hierarchical analysis
|
|
186
|
+
prior_pars = get_priors(options.bounds, samples = events, std = options.sigma_prior, scale = options.scale, probit = options.probit, hierarchical = True)
|
|
187
|
+
mix = HDPGMM(options.bounds, prior_pars = prior_pars, MC_draws = options.mc_draws, probit = options.probit)
|
|
188
|
+
draws = np.array([mix.density_from_samples(posteriors) for _ in tqdm(range(options.n_draws), desc = 'Hierarchical')])
|
|
189
|
+
# Save draws
|
|
190
|
+
save_density(draws, folder = output_draws, name = 'draws_'+options.h_name, ext = options.ext)
|
|
191
|
+
else:
|
|
192
|
+
draws = load_density(Path(output_draws, 'draws_'+options.h_name+'.'+options.ext))
|
|
193
|
+
# Plot
|
|
194
|
+
if dim == 1:
|
|
195
|
+
plot_median_cr(draws, injected = inj_density, selfunc = selfunc, samples = true_vals, out_folder = output_plots, name = options.h_name, label = options.symbol, unit = options.unit, hierarchical = True)
|
|
196
|
+
else:
|
|
197
|
+
plot_multidim(draws, samples = true_vals, out_folder = output_plots, name = options.h_name, labels = symbols, units = units, hierarchical = True)
|
|
198
|
+
|
|
199
|
+
if __name__ == '__main__':
|
|
200
|
+
main()
|