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 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()