comparison Model_spectrum.py @ 0:c9fc89ee996e draft default tip

planemo upload for repository https://github.com/esg-epfl-apc/tools-astro/tree/main/tools commit d5be3628a0bdad9747451669fa195f1d2acaf3f2
author astroteam
date Wed, 17 Apr 2024 10:29:23 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:c9fc89ee996e
1 #!/usr/bin/env python
2 # coding: utf-8
3
4 # flake8: noqa
5
6 import json
7 import os
8 import shutil
9
10 from oda_api.json import CustomJSONEncoder
11
12 get_ipython().run_cell_magic( # noqa: F821
13 "bash",
14 "",
15 "wget https://gitlab.renkulab.io/astronomy/mmoda/sgwb/-/raw/master/EPTA.csv\nwget https://gitlab.renkulab.io/astronomy/mmoda/sgwb/-/raw/master/NANOGrav23.csv\nwget https://gitlab.renkulab.io/astronomy/mmoda/sgwb/-/raw/master/PPTA.csv\n",
16 )
17
18 import matplotlib.pyplot as plt
19
20 # Loading necessary packages
21 import numpy as np
22
23 get_ipython().run_line_magic("matplotlib", "inline") # noqa: F821
24 import astropy.constants as const
25 import astropy.units as u
26 from astropy.constants import c
27 from numpy import log, pi, sqrt
28 from oda_api.data_products import ODAAstropyTable, PictureProduct
29
30 # Parameters of the phase transition
31 # Temperature in GeV
32 T_star = 0.178 # http://odahub.io/ontology#Energy_GeV
33
34 # Numbers of relativistic degrees of freedom
35 g_star = 20 # http://odahub.io/ontology#Integer
36
37 # ratio of the energy density deposited in the bubbles to the radiation energy density
38 alpha = 1.0 # http://odahub.io/ontology#Float
39
40 # beta/H : rate of the phase transition compared to Hubble rate
41 beta_H = 3.3
42
43 # fraction of turbulent energy that goes to gw N.B. arXiv:1004.4187 claims that epsilon_turb=0.05, but checks below show that it is rather 0.01
44 epsilon_turb = 1 # http://odahub.io/ontology#Float
45
46 # terminal velocity of bubbles
47 v_w = 0.999 # http://odahub.io/ontology#Float
48 h = 0.7 # http://odahub.io/ontology#Float
49
50 # Parameterisation='RoperPol2023' # http://odahub.io/ontology#String ; oda:allowed_value "Lewicki2022","RoperPol2023"
51
52 _galaxy_wd = os.getcwd()
53
54 with open("inputs.json", "r") as fd:
55 inp_dic = json.load(fd)
56 if "_data_product" in inp_dic.keys():
57 inp_pdic = inp_dic["_data_product"]
58 else:
59 inp_pdic = inp_dic
60
61 for vn, vv in inp_pdic.items():
62 if vn != "_selector":
63 globals()[vn] = type(globals()[vn])(vv)
64
65 c_s = 3 ** (-0.5) # speed of sound
66 F0_GW = 1.64e-5 / h**2 * (100 / g_star) ** (1 / 3.0)
67
68 # Eq.20 of arXiv:1512.06239, corrected according to Appendix A of arXiv:1004.4187
69
70 def ka_v(alpha_v, v):
71 zeta = ((2.0 / 3.0 * alpha_v + alpha_v**2) ** 0.5 + (1 / 3.0) ** 0.5) / (
72 1 + alpha_v
73 )
74 kappa_a = (
75 6.9
76 * v ** (6.0 / 5.0)
77 * alpha_v
78 / (1.36 - 0.037 * alpha_v**0.5 + alpha_v)
79 )
80 kappa_b = alpha_v ** (2.0 / 5.0) / (
81 0.017 + (0.997 + alpha_v) ** (2.0 / 5.0)
82 )
83 kappa_c = alpha_v**0.5 / (0.135 + (0.98 + alpha_v) ** 0.5)
84 kappa_d = alpha_v / (0.73 + 0.083 * alpha_v**0.6 + alpha_v)
85 deltak = -0.9 * log(alpha_v**0.5 / (1 + alpha_v**0.5))
86 if v < c_s:
87 return (
88 c_s ** (11.0 / 5.0)
89 * kappa_a
90 * kappa_b
91 / (
92 (c_s ** (11.0 / 5.0) - v ** (11.0 / 5.0)) * kappa_b
93 + v * c_s ** (6.0 / 5.0) * kappa_a
94 )
95 )
96 elif v > zeta:
97 return (
98 (zeta - 1) ** 3
99 * (zeta / v) ** (5.0 / 2)
100 * kappa_c
101 * kappa_d
102 / (
103 ((zeta - 1) ** 3 - (v - 1) ** 3)
104 * zeta ** (5.0 / 2.0)
105 * kappa_c
106 + (v - 1) ** 3 * kappa_d
107 )
108 )
109 else:
110 return (
111 kappa_b
112 + (v - c_s) * deltak
113 + (v - c_s) ** 3
114 / (zeta - c_s) ** 3
115 * (kappa_c - kappa_b - (zeta - c_s) * deltak)
116 )
117
118 kappa_v = ka_v(alpha, v_w)
119 kappa_therm = 1 - kappa_v
120 print(
121 "Fraction of released energy in bulk motion:",
122 kappa_v,
123 ", Thermal energy fraction:",
124 kappa_therm,
125 )
126 Omega_star = (kappa_v) * alpha / (1 + alpha)
127 print("Omega in bulk motion:", Omega_star)
128
129 # Comoving Hubble rate at the phase transition Eq. 11 of arXiv:1512.06239
130 def H_star(T_star):
131 return (
132 16.5e-6
133 * (T_star / (100.0 * u.GeV))
134 * (g_star / 100) ** (1.0 / 6.0)
135 * u.Hz
136 )
137
138 Hstar = H_star(T_star * u.GeV)
139 logHstar = np.log10(Hstar / u.Hz)
140
141 fmin = logHstar.value - 5
142 fmax = logHstar.value + 5
143 ff = np.logspace(fmin, fmax, 101)
144
145 # HL model formula
146 def GW_sound(f, T_star, alpha, beta_H, v_w):
147 kappa_v = ka_v(alpha, v_w)
148 K = kappa_v * alpha / (1 + alpha)
149 lambda_star = (8 * pi) ** (1 / 3) * max([v_w, c_s]) / (beta_H * Hstar) * c
150 Delta_w = sqrt((v_w - c_s) ** 2) / v_w
151 s2 = Delta_w * lambda_star * f / c
152 print((c / lambda_star).cgs, (c / (Delta_w * lambda_star)).cgs)
153 s1 = lambda_star * f / c
154 M = (
155 16
156 * (1 + Delta_w ** (-3)) ** (2 / 3.0)
157 * (Delta_w * s1) ** 3
158 / ((1 + s1**3) ** (2.0 / 3.0) * (3 + s2**2) ** 2)
159 )
160 factor = (K * lambda_star * Hstar) ** 2 / (
161 sqrt(K) + lambda_star * Hstar / c
162 )
163 mu = 4.78 - 6.27 * Delta_w + 3.34 * Delta_w**2
164 ff = f / u.Hz
165 dlnf = (ff[1] - ff[0]) / ff[0]
166 mu = sum(M) * dlnf
167 B = 1e-2 / mu
168 return (3 * B * factor / c**2 * F0_GW * M).cgs
169
170 # Eq 1 of the new Overleaf, from Alberto
171
172 def GW_turb_Theo(f, T_star, alpha, beta_H, v_w, epsilon_turb):
173 (const.c / H_star(T_star)).to(u.Mpc)
174 kappa_v = ka_v(alpha, v_w)
175 Omega_star = (kappa_v) * alpha / (1 + alpha)
176 lambda_star = (
177 (8 * pi) ** (1 / 3) * max([v_w, c_s]) / (beta_H * H_star(T_star))
178 ) * c # characteristic light-crossing distance scale
179
180 Delta_w = (
181 sqrt((v_w - c_s) ** 2) / v_w
182 ) # <1, the width of the bubble wall in units of R_star
183 R_peak = (
184 Delta_w * lambda_star
185 ) # the GW spectrum peaks ad the frequency correspoding to the bubble wall width
186 u_star = sqrt(0.75 * epsilon_turb * Omega_star) # alfven velocity
187 dt_fin = 2 * lambda_star / u_star / c # eddy processing time
188
189 T_GW = np.log10(1 + (H_star(T_star) * dt_fin / (2 * pi))) * (
190 f < 1 / dt_fin
191 ) + np.log10(1 + (H_star(T_star) / (2 * pi * f))) * (f >= 1 / dt_fin)
192 T_GW = T_GW**2
193 alpha_pi = 2.15
194 P_pi = (1 + ((lambda_star * f) / (2.2 * c)) ** alpha_pi) ** (
195 -11 / (3 * alpha_pi)
196 )
197 M = (
198 (lambda_star * f / c) ** 3
199 * (4 * pi**2 * T_GW * P_pi)
200 / (0.84 * (lambda_star * H_star(T_star) / c) ** 2)
201 )
202 res = (
203 3
204 * (1.75 * 10 ** (-3))
205 * (Omega_star * epsilon_turb) ** 2
206 * (lambda_star * H_star(T_star) / c) ** 2
207 * 1.64
208 * 10 ** (-5)
209 * (100 / g_star) ** (1 / 3.0)
210 / h**2
211 )
212 return res * M
213
214 def GW_turb_Andrii(f, T_star, alpha, beta_H, v_w, epsilon_turb):
215
216 # Eq. 1 of 2307.10744
217 lambda_star = (
218 (8 * pi) ** (1 / 3) * max([v_w, c_s]) / (beta_H * Hstar)
219 ) * c # characteristic light-crossing distance scale
220
221 kappa_v = ka_v(alpha, v_w)
222 K = kappa_v * alpha / (1 + alpha)
223
224 # Eq. 10 of 2307.10744
225 Omega_star = epsilon_turb * K
226
227 # Eq. 13 of 2307.10744 and text after it
228 u_star = sqrt(0.75 * Omega_star)
229 dt_fin = 2 * lambda_star / u_star / c
230 s3 = dt_fin * f
231 s1 = f * lambda_star / c
232 print("u_star=", u_star, "lambda_star=", lambda_star.cgs)
233
234 # Eq. 15 of 2307.10744
235 T_GW = np.log(1 + Hstar * dt_fin / (2 * pi)) * (s3 < 1) + np.log(
236 1 + lambda_star * Hstar / c / (2 * pi * s1)
237 ) * (s3 >= 1)
238 T_GW = T_GW**2
239
240 # Eq. 17 of 2307.10744
241 alpha_pi = 2.15
242 s_pi = 2.2
243 P_pi = (1 + (s1 / s_pi) ** alpha_pi) ** (-11 / (3 * alpha_pi))
244
245 # Eq 18,19,20 of 2307.10744
246 A = 2e-3 * 1.4 * 0.6
247
248 # Eq. 14 of 2307.10744
249 Sturb = (
250 4
251 * pi**2
252 * s1**3
253 * T_GW
254 / (lambda_star * Hstar / c) ** 2
255 * P_pi
256 / 1.4
257 / 0.6
258 )
259
260 # Eq 9 of 2307.10744
261 res = (
262 3 * A * Omega_star**2 * (lambda_star * Hstar / c) ** 2 * F0_GW * Sturb
263 )
264 return res
265
266 # Eq 19 of 2308.08546
267 def GW_Ellis(f, T_star, alpha, beta_H, v_w, epsilon_turb):
268 A = 5.1e-2
269 a = 2.4
270 b = 2.4
271 c = 4.0
272 Hstar = H_star(T_star).cgs
273 f_H = Hstar / 2 / pi
274 fp = 0.7 * beta_H * f_H
275 lambda_star = (2 * pi * 3e10 * u.cm / u.s / fp).cgs
276
277 SH = 1 / (1 + (f / f_H) ** (a - 3))
278 Omega_star = beta_H ** (-2) * alpha / (1 + alpha) * A
279 Omega_B = epsilon_turb * Omega_star / 2.0
280 Omega_gamma = 2 / g_star
281 B = 3e-6 * (Omega_B / Omega_gamma) ** 0.5
282 res = (
283 beta_H ** (-2)
284 * alpha
285 / (1 + alpha)
286 * A
287 * (a + b) ** c
288 / (b * (f / fp) ** (-a / c) + a * (f / fp) ** (b / c)) ** c
289 * SH
290 )
291 return (
292 1.6e-5 * (g_star / 100) ** (-1 / 3.0) * res / h**2,
293 lambda_star.cgs.value,
294 B,
295 )
296
297 d = np.genfromtxt("NANOGrav23.csv")
298 gammas_nano = d[:, 0]
299 As_nano = 10 ** d[:, 1]
300 ps_nano = 5 - gammas_nano
301
302 d = np.genfromtxt("EPTA.csv")
303 gammas_epta = d[:, 0]
304 As_epta = 10 ** d[:, 1]
305 ps_epta = 5 - gammas_epta
306
307 d = np.genfromtxt("PPTA.csv")
308 gammas_ppta = d[:, 0]
309 As_ppta = 10 ** d[:, 1]
310 ps_ppta = 5 - gammas_ppta
311
312 H0 = 70 * (u.km / u.s) / u.Mpc
313
314 GW_t = GW_turb_Theo(
315 ff * u.Hz, T_star * u.GeV, alpha, beta_H, v_w, epsilon_turb
316 )
317 # plt.plot(ff,GW_t,color='magenta')
318 GW_t = GW_turb_Andrii(
319 ff * u.Hz, T_star * u.GeV, alpha, beta_H, v_w, epsilon_turb
320 )
321 GW_s = GW_sound(ff * u.Hz, T_star * u.GeV, alpha, beta_H, v_w)
322 GW = GW_s + GW_t
323
324 GW1 = GW_Ellis(ff * u.Hz, T_star * u.GeV, alpha, beta_H, v_w, epsilon_turb)[0]
325
326 # if(Parameterisation=='Lewicki2022'):
327 # plt.plot(ff,GW1,color='magenta',alpha=0.5,linewidth=4,label='2208.11697')
328 # else:
329 plt.plot(ff, GW_t, color="blue", linestyle="dashed", label="turbulence")
330 plt.plot(ff, GW_s, color="red", linestyle="dotted", label="sound waves")
331 plt.plot(ff, GW, linewidth=4, color="black", alpha=0.5, label="total")
332
333 fref = (1 / u.yr).cgs.value
334 lgfmin = np.log10(fref / 10.0)
335 lgfmax = np.log10(fref / 2.0)
336 fff = np.logspace(lgfmin, lgfmax, 10) * u.Hz
337 min_nano = np.ones(len(fff))
338 max_nano = np.zeros(len(fff))
339 for i in range(len(As_nano)):
340 spec = (
341 2
342 * pi**2
343 / 3
344 / H0**2
345 * fff**2
346 * As_nano[i] ** 2
347 * (fff / fref) ** (3 - gammas_nano[i])
348 ).cgs.value
349 min_nano = np.minimum(spec, min_nano)
350 max_nano = np.maximum(spec, max_nano)
351 # plt.plot(ff,spec)
352 plt.fill_between(
353 fff.value, min_nano, max_nano, color="red", alpha=0.5, label="NANOGrav"
354 )
355 min_epta = np.ones(len(fff))
356 max_epta = np.zeros(len(fff))
357 for i in range(len(As_epta)):
358 spec = (
359 2
360 * pi**2
361 / 3
362 / H0**2
363 * fff**2
364 * As_epta[i] ** 2
365 * (fff / fref) ** (3 - gammas_epta[i])
366 ).cgs.value
367 min_epta = np.minimum(spec, min_epta)
368 max_epta = np.maximum(spec, max_epta)
369 # plt.plot(ff,spec)
370 plt.fill_between(
371 fff.value, min_epta, max_epta, color="blue", alpha=0.5, label="EPTA"
372 )
373 min_ppta = np.ones(len(fff))
374 max_ppta = np.zeros(len(fff))
375 for i in range(len(As_ppta)):
376 spec = (
377 2
378 * pi**2
379 / 3
380 / H0**2
381 * fff**2
382 * As_ppta[i] ** 2
383 * (fff / fref) ** (3 - gammas_ppta[i])
384 ).cgs.value
385 min_ppta = np.minimum(spec, min_ppta)
386 max_ppta = np.maximum(spec, max_ppta)
387 # plt.plot(ff,spec)
388 plt.fill_between(
389 fff.value, min_ppta, max_ppta, color="green", alpha=0.5, label="PPTA"
390 )
391
392 maxGW = max(GW)
393 ind = np.argmax(GW)
394 fmax = ff[ind]
395 plt.xscale("log")
396 plt.yscale("log")
397 plt.ylim(maxGW / 1e5, maxGW * 10)
398 plt.xlim(fmax / 1e3, fmax * 1e3)
399 plt.grid()
400 plt.xscale("log")
401 plt.yscale("log")
402 plt.legend(loc="upper left")
403 plt.xlabel("$f$, Hz")
404 plt.ylabel("$\Omega_{gw}(f)$")
405 plt.savefig("Spectrum.png", format="png", bbox_inches="tight")
406
407 bin_image = PictureProduct.from_file("Spectrum.png")
408 from astropy.table import Table
409
410 # if(Parameterisation=='Lewicki2022'):
411 # data=[ff,GW1]
412 # names=('f[Hz]','Omega_gw')
413 # else:
414 data = [ff, GW_s, GW_t]
415 names = ("f[Hz]", "Omega_sound_waves", "Omega_turbulence")
416 spectrum = ODAAstropyTable(Table(data, names=names))
417
418 png = bin_image # http://odahub.io/ontology#ODAPictureProduct
419 astropy_table = spectrum # http://odahub.io/ontology#ODAAstropyTable
420
421 # output gathering
422 _galaxy_meta_data = {}
423 _oda_outs = []
424 _oda_outs.append(("out_Model_spectrum_png", "png_galaxy.output", png))
425 _oda_outs.append(
426 (
427 "out_Model_spectrum_astropy_table",
428 "astropy_table_galaxy.output",
429 astropy_table,
430 )
431 )
432
433 for _outn, _outfn, _outv in _oda_outs:
434 _galaxy_outfile_name = os.path.join(_galaxy_wd, _outfn)
435 if isinstance(_outv, str) and os.path.isfile(_outv):
436 shutil.move(_outv, _galaxy_outfile_name)
437 _galaxy_meta_data[_outn] = {"ext": "_sniff_"}
438 elif getattr(_outv, "write_fits_file", None):
439 _outv.write_fits_file(_galaxy_outfile_name)
440 _galaxy_meta_data[_outn] = {"ext": "fits"}
441 elif getattr(_outv, "write_file", None):
442 _outv.write_file(_galaxy_outfile_name)
443 _galaxy_meta_data[_outn] = {"ext": "_sniff_"}
444 else:
445 with open(_galaxy_outfile_name, "w") as fd:
446 json.dump(_outv, fd, cls=CustomJSONEncoder)
447 _galaxy_meta_data[_outn] = {"ext": "json"}
448
449 with open(os.path.join(_galaxy_wd, "galaxy.json"), "w") as fd:
450 json.dump(_galaxy_meta_data, fd)
451 print("*** Job finished successfully ***")