MP-Gadget  5.0.1.dev1-76bc7d4726-dirty
neutrinos_lra.c
Go to the documentation of this file.
1 
9 #include <math.h>
10 #include <string.h>
11 #include <bigfile-mpi.h>
12 #include <gsl/gsl_integration.h>
13 #include <gsl/gsl_errno.h>
14 #include <gsl/gsl_interp.h>
15 #include <gsl/gsl_sf_bessel.h>
16 
17 #include "neutrinos_lra.h"
18 
19 #include "utils/endrun.h"
20 #include "utils/mymalloc.h"
21 #include "utils/string.h"
22 #include "petaio.h"
23 #include "cosmology.h"
24 #include "powerspectrum.h"
25 #include "physconst.h"
26 
28 #define FLOAT_ACC 1e-6
30 #define GSL_VAL 200
31 
35 void update_delta_tot(_delta_tot_table * const d_tot, const double a, const double delta_cdm_curr[], const double delta_nu_curr[], const int overwrite);
36 
43 void get_delta_nu(Cosmology * CP, const _delta_tot_table * const d_tot, const double a, double delta_nu_curr[], const double mnu);
44 
47 void get_delta_nu_combined(Cosmology * CP, const _delta_tot_table * const d_tot, const double a, double delta_nu_curr[]);
48 
50 double specialJ(const double x, const double vcmnubylight, const double nufrac_low);
51 
61 double fslength(Cosmology * CP, const double logai, const double logaf, const double light);
62 
67 static inline double get_delta_tot(const double delta_nu_curr, const double delta_cdm_curr, const double OmegaNua3, const double Omeganonu, const double Omeganu1, const double particle_nu_fraction)
68 {
69  const double fcdm = 1 - OmegaNua3/(Omeganonu + Omeganu1);
70  return fcdm * (delta_cdm_curr + delta_nu_curr * OmegaNua3/(Omeganonu + Omeganu1*particle_nu_fraction));
71 }
72 
73 
74 /*Structure which holds the neutrino state*/
76 
84  double *logk;
85  /*This is T_nu / (T_not-nu), where T_not-nu is a weighted average of T_cdm and T_baryon*/
86  double *T_nu;
87 };
89 
92 
93 /* Constructor. transfer_init_tabulate must be called before this function.
94  * Initialises delta_tot (including from a file) and delta_nu_init from the transfer functions.
95  * read_all_nu_state must be called before this if you want reloading from a snapshot to work
96  * Note delta_cdm_curr includes baryons, and is only used if not resuming.*/
97 static void delta_tot_first_init(_delta_tot_table * const d_tot, const int nk_in, const double wavenum[], const double delta_cdm_curr[], const double TimeIC)
98 {
99  int ik;
100  d_tot->nk=nk_in;
101  const double OmegaNua3=get_omega_nu_nopart(d_tot->omnu, d_tot->TimeTransfer)*pow(d_tot->TimeTransfer,3);
102  const double OmegaNu1 = get_omega_nu(d_tot->omnu, 1);
103  gsl_interp_accel *acc = gsl_interp_accel_alloc();
104  gsl_interp * spline;
105  if(t_init->NPowerTable > 2)
106  spline = gsl_interp_alloc(gsl_interp_cspline,t_init->NPowerTable);
107  else
108  spline = gsl_interp_alloc(gsl_interp_linear,t_init->NPowerTable);
109  gsl_interp_init(spline,t_init->logk,t_init->T_nu,t_init->NPowerTable);
110  /*Check we have a long enough power table: power tables are in log_10*/
111  if(log10(wavenum[d_tot->nk-1]) > t_init->logk[t_init->NPowerTable-1])
112  endrun(2,"Want k = %g but maximum in CLASS table is %g\n",wavenum[d_tot->nk-1], pow(10, t_init->logk[t_init->NPowerTable-1]));
113  for(ik=0;ik<d_tot->nk;ik++) {
114  /* T_nu contains T_nu / T_cdm.*/
115  double T_nubyT_nonu = gsl_interp_eval(spline,t_init->logk,t_init->T_nu,log10(wavenum[ik]),acc);
116  /*Initialise delta_nu_init to use the first timestep's delta_cdm_curr
117  * so that it includes potential Rayleigh scattering. */
118  d_tot->delta_nu_init[ik] = delta_cdm_curr[ik]*T_nubyT_nonu;
119  const double partnu = particle_nu_fraction(&d_tot->omnu->hybnu, TimeIC, 0);
120  /*Initialise the first delta_tot*/
121  d_tot->delta_tot[ik][0] = get_delta_tot(d_tot->delta_nu_init[ik], delta_cdm_curr[ik], OmegaNua3, d_tot->Omeganonu, OmegaNu1, partnu);
122  /*Set up the wavenumber array*/
123  d_tot->wavenum[ik] = wavenum[ik];
124  }
125  gsl_interp_accel_free(acc);
126  gsl_interp_free(spline);
127 
128  /*If we are not restarting, make sure we set the scale factor*/
129  d_tot->scalefact[0]=log(TimeIC);
130  d_tot->ia=1;
131  return;
132 }
133 
134 void delta_nu_from_power(struct _powerspectrum * PowerSpectrum, Cosmology * CP, const double Time, const double TimeIC)
135 {
136  int i;
137  /*This is done on the first timestep: we need nk_nonzero for it to work.*/
139  if(delta_tot_table.ia == 0) {
140  /* Compute delta_nu from the transfer functions if first entry.*/
141  delta_tot_first_init(&delta_tot_table, PowerSpectrum->nonzero, PowerSpectrum->kk, PowerSpectrum->Power, TimeIC);
142  }
143 
144  /*Initialise the first delta_nu*/
147  }
148  for(i = 0; i < PowerSpectrum->nonzero; i++)
149  PowerSpectrum->logknu[i] = log(PowerSpectrum->kk[i]);
150 
151  double * Power_in = PowerSpectrum->Power;
152  /* Rebin the input power if necessary*/
153  if(delta_tot_table.nk != PowerSpectrum->nonzero) {
154  Power_in = (double *) mymalloc("pkint", delta_tot_table.nk * sizeof(double));
155  double * logPower = (double *) mymalloc("logpk", PowerSpectrum->nonzero * sizeof(double));
156  for(i = 0; i < PowerSpectrum->nonzero; i++)
157  logPower[i] = log(PowerSpectrum->Power[i]);
158  gsl_interp * pkint = gsl_interp_alloc(gsl_interp_linear, PowerSpectrum->nonzero);
159  gsl_interp_init(pkint, PowerSpectrum->logknu, logPower, PowerSpectrum->nonzero);
160  gsl_interp_accel * pkacc = gsl_interp_accel_alloc();
161  for(i = 0; i < delta_tot_table.nk; i++) {
162  double logk = log(delta_tot_table.wavenum[i]);
163  if(pkint->xmax < logk || pkint->xmin > logk)
164  Power_in[i] = delta_tot_table.delta_tot[i][delta_tot_table.ia-1];
165  else
166  Power_in[i] = exp(gsl_interp_eval(pkint, PowerSpectrum->logknu, logPower, logk, pkacc));
167  }
168  myfree(logPower);
169  gsl_interp_accel_free(pkacc);
170  gsl_interp_free(pkint);
171  }
172 
173  const double partnu = particle_nu_fraction(&CP->ONu.hybnu, Time, 0);
174  /* If we get called twice with the same scale factor, do nothing: delta_nu
175  * already stores the neutrino power from the current timestep.*/
176  if(1 - partnu > 1e-3 && log(Time)-delta_tot_table.scalefact[delta_tot_table.ia-1] > FLOAT_ACC) {
177  /*We need some estimate for delta_tot(current time) to obtain delta_nu(current time).
178  Even though delta_tot(current time) is not directly used (the integrand vanishes at a = a(current)),
179  it is indeed needed for interpolation */
180  /*It was checked that using delta_tot(current time) = delta_cdm(current time) leads to no more than 2%
181  error on delta_nu (and moreover for large k). Using the last timestep's delta_nu decreases error even more.
182  So we only need one step. */
183  /*This increments the number of stored spectra, although the last one is not yet final.*/
185  /*Get the new delta_nu_curr*/
187  /* Decide whether we save the current time or not */
188  if (Time > exp(delta_tot_table.scalefact[delta_tot_table.ia-2]) + 0.009) {
189  /* If so update delta_tot(a) correctly, overwriting current power spectrum */
191  }
192  /*Otherwise discard the last powerspectrum*/
193  else
195 
196  message(0,"Done getting neutrino power: nk = %d, k = %g, delta_nu = %g, delta_cdm = %g,\n", delta_tot_table.nk, delta_tot_table.wavenum[1], delta_tot_table.delta_nu_last[1], Power_in[1]);
197  /*kspace_prefac = M_nu (analytic) / M_particles */
198  const double OmegaNu_nop = get_omega_nu_nopart(&CP->ONu, Time);
199  const double omega_hybrid = get_omega_nu(&CP->ONu, 1) * partnu / pow(Time, 3);
200  /* Omega0 - Omega in neutrinos + Omega in particle neutrinos = Omega in particles*/
201  PowerSpectrum->nu_prefac = OmegaNu_nop/(delta_tot_table.Omeganonu/pow(Time,3) + omega_hybrid);
202  }
203  double * delta_nu_ratio = (double *) mymalloc2("dnu_rat", delta_tot_table.nk * sizeof(double));
204  double * logwavenum = (double *) mymalloc2("logwavenum", delta_tot_table.nk * sizeof(double));
205  gsl_interp * pkint = gsl_interp_alloc(gsl_interp_linear, delta_tot_table.nk);
206  gsl_interp_accel * pkacc = gsl_interp_accel_alloc();
207  /*We want to interpolate in log space*/
208  for(i=0; i < delta_tot_table.nk; i++) {
209  if(isnan(delta_tot_table.delta_nu_last[i]))
210  endrun(2004,"delta_nu_curr=%g i=%d delta_cdm_curr=%g kk=%g\n",delta_tot_table.delta_nu_last[i],i,Power_in[i],delta_tot_table.wavenum[i]);
211  /*Enforce positivity for sanity reasons*/
212  if(delta_tot_table.delta_nu_last[i] < 0)
214  delta_nu_ratio[i] = delta_tot_table.delta_nu_last[i]/ Power_in[i];
215  logwavenum[i] = log(delta_tot_table.wavenum[i]);
216  }
217  if(delta_tot_table.nk != PowerSpectrum->nonzero)
218  myfree(Power_in);
219  gsl_interp_init(pkint, logwavenum, delta_nu_ratio, delta_tot_table.nk);
220 
221  /*We want to interpolate in log space*/
222  for(i=0; i < PowerSpectrum->nonzero; i++) {
223  if(PowerSpectrum->nonzero == delta_tot_table.nk)
224  PowerSpectrum->delta_nu_ratio[i] = delta_nu_ratio[i];
225  else {
226  double logk = PowerSpectrum->logknu[i];
227  if(logk > pkint->xmax)
228  logk = pkint->xmax;
229  PowerSpectrum->delta_nu_ratio[i] = gsl_interp_eval(pkint, logwavenum, delta_nu_ratio, logk, pkacc);
230  }
231  }
232 
233  gsl_interp_accel_free(pkacc);
234  gsl_interp_free(pkint);
235  myfree(logwavenum);
236  myfree(delta_nu_ratio);
237 }
238 
239 /*Save the neutrino power spectrum to a file*/
240 void powerspectrum_nu_save(struct _powerspectrum * PowerSpectrum, const char * OutputDir, const char * filename, const double Time)
241 {
242  int i;
243  int ThisTask;
244  MPI_Comm_rank(MPI_COMM_WORLD, &ThisTask);
245  if(ThisTask != 0)
246  return;
247 
248  char * fname = fastpm_strdup_printf("%s/%s-%0.4f.txt", OutputDir, filename, Time);
249  /* Now save the neutrino power spectrum*/
250  FILE * fp = fopen(fname, "w");
251  fprintf(fp, "# in Mpc/h Units \n");
252  fprintf(fp, "# k P_nu(k) Nmodes\n");
253  fprintf(fp, "# a= %g\n", Time);
254  fprintf(fp, "# nk = %d\n", PowerSpectrum->nonzero);
255  for(i = 0; i < PowerSpectrum->nonzero; i++){
256  fprintf(fp, "%g %g %ld\n", PowerSpectrum->kk[i], pow(delta_tot_table.delta_nu_last[i],2), PowerSpectrum->Nmodes[i]);
257  }
258  fclose(fp);
259  myfree(fname);
260  /*Clean up the neutrino memory now we saved the power spectrum.*/
261  gsl_interp_free(PowerSpectrum->nu_spline);
262  gsl_interp_accel_free(PowerSpectrum->nu_acc);
263 }
264 
265 void petaio_save_neutrinos(BigFile * bf, int ThisTask)
266 {
267 #pragma omp master
268  {
269  double * scalefact = delta_tot_table.scalefact;
270  size_t nk = delta_tot_table.nk, ia = delta_tot_table.ia;
271  size_t ik, i;
272  double * delta_tot = (double *) mymalloc("tmp_delta",nk * ia * sizeof(double));
273  /*Save a flat memory block*/
274  for(ik=0;ik< nk;ik++)
275  for(i=0;i< ia;i++)
276  delta_tot[ik*ia+i] = delta_tot_table.delta_tot[ik][i];
277 
278  BigBlock bn;
279  if(0 != big_file_mpi_create_block(bf, &bn, "Neutrino", NULL, 0, 0, 0, MPI_COMM_WORLD)) {
280  endrun(0, "Failed to create block at %s:%s\n", "Neutrino",
281  big_file_get_error_message());
282  }
283  if ( (0 != big_block_set_attr(&bn, "Nscale", &ia, "u8", 1)) ||
284  (0 != big_block_set_attr(&bn, "scalefact", scalefact, "f8", ia)) ||
285  (0 != big_block_set_attr(&bn, "Nkval", &nk, "u8", 1)) ) {
286  endrun(0, "Failed to write neutrino attributes %s\n",
287  big_file_get_error_message());
288  }
289  if(0 != big_block_mpi_close(&bn, MPI_COMM_WORLD)) {
290  endrun(0, "Failed to close block %s\n",
291  big_file_get_error_message());
292  }
293  BigArray deltas = {0};
294  size_t dims[2] = {0 , ia};
295  /*The neutrino state is shared between all processors,
296  *so only write on master task*/
297  if(ThisTask == 0) {
298  dims[0] = nk;
299  }
300  ptrdiff_t strides[2] = {(ptrdiff_t) (sizeof(double) * ia), (ptrdiff_t) sizeof(double)};
301  big_array_init(&deltas, delta_tot, "=f8", 2, dims, strides);
302  petaio_save_block(bf, "Neutrino/Deltas", &deltas, 1);
303  myfree(delta_tot);
304  /*Now write the initial neutrino power*/
305  BigArray delta_nu = {0};
306  dims[1] = 1;
307  strides[0] = sizeof(double);
308  big_array_init(&delta_nu, delta_tot_table.delta_nu_init, "=f8", 2, dims, strides);
309  petaio_save_block(bf, "Neutrino/DeltaNuInit", &delta_nu, 1);
310  /*Now write the k values*/
311  BigArray kvalue = {0};
312  big_array_init(&kvalue, delta_tot_table.wavenum, "=f8", 2, dims, strides);
313  petaio_save_block(bf, "Neutrino/kvalue", &kvalue, 1);
314  }
315 }
316 
317 void petaio_read_icnutransfer(BigFile * bf, int ThisTask)
318 {
319 #pragma omp master
320  {
321  t_init->NPowerTable = 2;
322  BigBlock bn;
323  /* Read the size of the ICTransfer block.
324  * If we can't read it, just set it to zero*/
325  if(0 == big_file_mpi_open_block(bf, &bn, "ICTransfers", MPI_COMM_WORLD)) {
326  if(0 != big_block_get_attr(&bn, "Nentry", &t_init->NPowerTable, "u8", 1))
327  endrun(0, "Failed to read attr: %s\n", big_file_get_error_message());
328  if(0 != big_block_mpi_close(&bn, MPI_COMM_WORLD))
329  endrun(0, "Failed to close block %s\n",big_file_get_error_message());
330  }
331  message(0,"Found transfer function, using %d rows.\n", t_init->NPowerTable);
332  t_init->logk = (double *) mymalloc2("Transfer_functions", sizeof(double) * 2*t_init->NPowerTable);
334 
335  /*Defaults: a very small value*/
336  t_init->logk[0] = -100;
337  t_init->logk[t_init->NPowerTable-1] = 100;
338 
339  t_init->T_nu[0] = 1e-30;
340  t_init->T_nu[t_init->NPowerTable-1] = 1e-30;
341 
342  /*Now read the arrays*/
343  BigArray Tnu = {0};
344  BigArray logk = {0};
345 
346  size_t dims[2] = {0, 1};
347  ptrdiff_t strides[2] = {sizeof(double), sizeof(double)};
348  /*The neutrino state is shared between all processors,
349  *so only read on master task and broadcast*/
350  if(ThisTask == 0) {
351  dims[0] = t_init->NPowerTable;
352  }
353  big_array_init(&Tnu, t_init->T_nu, "=f8", 2, dims, strides);
354  big_array_init(&logk, t_init->logk, "=f8", 2, dims, strides);
355  /*This is delta_nu / delta_tot: note technically the most massive eigenstate only.
356  * But if there are significant differences the mass is so low this is basically zero.*/
357  petaio_read_block(bf, "ICTransfers/DELTA_NU", &Tnu, 0);
358  petaio_read_block(bf, "ICTransfers/logk", &logk, 0);
359  /*Also want d_{cdm+bar} / d_tot so we can get d_nu/(d_cdm+d_b)*/
360  double * T_cb = (double *) mymalloc("tmp1", t_init->NPowerTable* sizeof(double));
361  T_cb[0] = 1;
362  T_cb[t_init->NPowerTable-1] = 1;
363  BigArray Tcb = {0};
364  big_array_init(&Tcb, T_cb, "=f8", 2, dims, strides);
365  petaio_read_block(bf, "ICTransfers/DELTA_CB", &Tcb, 0);
366  int i;
367  for(i = 0; i < t_init->NPowerTable; i++)
368  t_init->T_nu[i] /= T_cb[i];
369  myfree(T_cb);
370  /*Broadcast the arrays.*/
371  MPI_Bcast(t_init->logk,2*t_init->NPowerTable,MPI_DOUBLE,0,MPI_COMM_WORLD);
372  }
373 }
374 
375 /*Read the neutrino data from the snapshot*/
376 void petaio_read_neutrinos(BigFile * bf, int ThisTask)
377 {
378 #pragma omp master
379  {
380  size_t nk, ia, ik, i;
381  BigBlock bn;
382  if(0 != big_file_mpi_open_block(bf, &bn, "Neutrino", MPI_COMM_WORLD)) {
383  endrun(0, "Failed to open block at %s:%s\n", "Neutrino",
384  big_file_get_error_message());
385  }
386  if(
387  (0 != big_block_get_attr(&bn, "Nscale", &ia, "u8", 1)) ||
388  (0 != big_block_get_attr(&bn, "Nkval", &nk, "u8", 1))) {
389  endrun(0, "Failed to read attr: %s\n",
390  big_file_get_error_message());
391  }
392  double *delta_tot = (double *) mymalloc("tmp_nusave",ia*nk*sizeof(double));
393  /*Allocate list of scale factors, and space for delta_tot, in one operation.*/
394  if(0 != big_block_get_attr(&bn, "scalefact", delta_tot_table.scalefact, "f8", ia))
395  endrun(0, "Failed to read attr: %s\n", big_file_get_error_message());
396  if(0 != big_block_mpi_close(&bn, MPI_COMM_WORLD)) {
397  endrun(0, "Failed to close block %s\n",
398  big_file_get_error_message());
399  }
400  BigArray deltas = {0};
401  size_t dims[2] = {0, ia};
402  ptrdiff_t strides[2] = {(ptrdiff_t) (sizeof(double)*ia), (ptrdiff_t)sizeof(double)};
403  /*The neutrino state is shared between all processors,
404  *so only read on master task and broadcast*/
405  if(ThisTask == 0) {
406  dims[0] = nk;
407  }
408  big_array_init(&deltas, delta_tot, "=f8", 2, dims, strides);
409  petaio_read_block(bf, "Neutrino/Deltas", &deltas, 1);
410  if(nk > 1Lu*delta_tot_table.nk_allocated || ia > 1Lu*delta_tot_table.namax)
411  endrun(5, "Allocated nk %d na %d for neutrino power but need nk %d na %d\n", delta_tot_table.nk_allocated, delta_tot_table.namax, nk, ia);
412  /*Save a flat memory block*/
413  for(ik=0;ik<nk;ik++)
414  for(i=0;i<ia;i++)
415  delta_tot_table.delta_tot[ik][i] = delta_tot[ik*ia+i];
416  delta_tot_table.nk = nk;
417  delta_tot_table.ia = ia;
418  myfree(delta_tot);
419  /* Read the initial delta_nu. This is basically zero anyway,
420  * so for backwards compatibility do not require it*/
421  BigArray delta_nu = {0};
422  dims[1] = 1;
423  strides[0] = sizeof(double);
425  big_array_init(&delta_nu, delta_tot_table.delta_nu_init, "=f8", 2, dims, strides);
426  petaio_read_block(bf, "Neutrino/DeltaNuInit", &delta_nu, 0);
427  /* Read the k values*/
428  BigArray kvalue = {0};
430  big_array_init(&kvalue, delta_tot_table.wavenum, "=f8", 2, dims, strides);
431  petaio_read_block(bf, "Neutrino/kvalue", &kvalue, 0);
432 
433  /*Broadcast the arrays.*/
434  MPI_Bcast(&(delta_tot_table.ia), 1,MPI_INT,0,MPI_COMM_WORLD);
435  MPI_Bcast(&(delta_tot_table.nk), 1,MPI_INT,0,MPI_COMM_WORLD);
436  MPI_Bcast(delta_tot_table.delta_nu_init,delta_tot_table.nk,MPI_DOUBLE,0,MPI_COMM_WORLD);
437  MPI_Bcast(delta_tot_table.wavenum,delta_tot_table.nk,MPI_DOUBLE,0,MPI_COMM_WORLD);
438 
439  if(delta_tot_table.ia > 0) {
440  /*Broadcast data for scalefact and delta_tot, Delta_tot is allocated as the same block of memory as scalefact.
441  Not all this memory will actually have been used, but it is easiest to bcast all of it.*/
442  MPI_Bcast(delta_tot_table.scalefact,delta_tot_table.namax*(delta_tot_table.nk+1),MPI_DOUBLE,0,MPI_COMM_WORLD);
443  }
444  }
445 }
446 
447 
448 /*Allocate memory for delta_tot_table. This is separate from delta_tot_init because we need to allocate memory
449  * before we have the information needed to initialise it*/
450 void init_neutrinos_lra(const int nk_in, const double TimeTransfer, const double TimeMax, const double Omega0, const _omega_nu * const omnu, const double UnitTime_in_s, const double UnitLength_in_cm)
451 {
453  int count;
454  /*Memory allocations need to be done on all processors*/
455  d_tot->nk_allocated=nk_in;
456  d_tot->nk=nk_in;
457  /*Store starting time*/
458  d_tot->TimeTransfer = TimeTransfer;
459  /* Allocate memory for delta_tot here, so that we can have further memory allocated and freed
460  * before delta_tot_init is called. The number nk here should be larger than the actual value needed.*/
461  /*Allocate pointers to each k-vector*/
462  d_tot->namax=ceil(100*(TimeMax-TimeTransfer))+2;
463  d_tot->ia=0;
464  d_tot->delta_tot =(double **) mymalloc("kspace_delta_tot",nk_in*sizeof(double *));
465  /*Allocate list of scale factors, and space for delta_tot, in one operation.*/
466  d_tot->scalefact = (double *) mymalloc("kspace_scalefact",d_tot->namax*(nk_in+1)*sizeof(double));
467  /*Allocate space for delta_nu and wavenumbers*/
468  d_tot->delta_nu_last = (double *) mymalloc("kspace_delta_nu", sizeof(double) * 3 * nk_in);
469  d_tot->delta_nu_init = d_tot->delta_nu_last + nk_in;
470  d_tot->wavenum = d_tot->delta_nu_init + nk_in;
471  /*Allocate actual data. Note that this means data can be accessed either as:
472  * delta_tot[k][a] OR as
473  * delta_tot[0][a+k*namax] */
474  d_tot->delta_tot[0] = d_tot->scalefact+d_tot->namax;
475  for(count=1; count< nk_in; count++)
476  d_tot->delta_tot[count] = d_tot->delta_tot[0] + count*d_tot->namax;
477  /*Allocate space for the initial neutrino power spectrum*/
478  /*Setup pointer to the matter density*/
479  d_tot->omnu = omnu;
480  /*Set the prefactor for delta_nu, and the units system*/
481  d_tot->light = LIGHTCGS * UnitTime_in_s/UnitLength_in_cm;
482  d_tot->delta_nu_prefac = 1.5 *Omega0 * HUBBLE * HUBBLE * pow(UnitTime_in_s,2)/d_tot->light;
483  /*Matter fraction excluding neutrinos*/
484  d_tot->Omeganonu = Omega0 - get_omega_nu(omnu, 1);
485 }
486 
487 /*Begin functions that do the actual computation of the neutrino power spectra.
488  * The algorithms executed are explained in Ali-Haimoud & Bird 2012 and Bird, Ali-Haimoud, Feng & Liu 2018
489  * arXiv:1209.0461 and arXiv:1803.09854.
490  * This is a Fourier-space linear response method for computing neutrino overdensities from CDM overdensities.*/
491 
492 /*Function which wraps three get_delta_nu calls to get delta_nu three times,
493  * so that the final value is for all neutrino species*/
494 void get_delta_nu_combined(Cosmology * CP, const _delta_tot_table * const d_tot, const double a, double delta_nu_curr[])
495 {
496  const double Omega_nu_tot=get_omega_nu_nopart(d_tot->omnu, a);
497  int mi;
498  /*Initialise delta_nu_curr*/
499  memset(delta_nu_curr, 0, d_tot->nk*sizeof(double));
500  /*Get each neutrinos species and density separately and add them to the total.
501  * Neglect perturbations in massless neutrinos.*/
502  for(mi=0; mi<NUSPECIES; mi++) {
503  if(d_tot->omnu->nu_degeneracies[mi] > 0) {
504  int ik;
505  double * delta_nu_single = (double *) mymalloc("delta_nu_single", sizeof(double) * d_tot->nk);
506  const double omeganu = d_tot->omnu->nu_degeneracies[mi] * omega_nu_single(d_tot->omnu, a, mi);
507  get_delta_nu(CP, d_tot, a, delta_nu_single,d_tot->omnu->RhoNuTab[mi].mnu);
508  for(ik=0; ik<d_tot->nk; ik++)
509  delta_nu_curr[ik]+=delta_nu_single[ik]*omeganu/Omega_nu_tot;
510  myfree(delta_nu_single);
511  }
512  }
513  return;
514 }
515 
516 /*Update the last value of delta_tot in the table with a new value computed
517  from the given delta_cdm_curr and delta_nu_curr.
518  If overwrite is true, overwrite the existing final entry.*/
519 void update_delta_tot(_delta_tot_table * const d_tot, const double a, const double delta_cdm_curr[], const double delta_nu_curr[], const int overwrite)
520 {
521  const double OmegaNua3 = get_omega_nu_nopart(d_tot->omnu, a)*pow(a,3);
522  const double OmegaNu1 = get_omega_nu(d_tot->omnu, 1);
523  const double partnu = particle_nu_fraction(&d_tot->omnu->hybnu, a, 0);
524  int ik;
525  if(!overwrite)
526  d_tot->ia++;
527  /*Update the scale factor*/
528  d_tot->scalefact[d_tot->ia-1] = log(a);
529  /* Update delta_tot(a)*/
530  for (ik = 0; ik < d_tot->nk; ik++){
531  d_tot->delta_tot[ik][d_tot->ia-1] = get_delta_tot(delta_nu_curr[ik], delta_cdm_curr[ik], OmegaNua3, d_tot->Omeganonu, OmegaNu1,partnu);
532  }
533 }
534 
535 /*Kernel function for the fslength integration*/
536 double fslength_int(const double loga, void *params)
537 {
538  Cosmology * CP = (Cosmology *) params;
539  /*This should be M_nu / k_B T_nu (which is dimensionless)*/
540  const double a = exp(loga);
541  return 1./a/(a*hubble_function(CP, a));
542 }
543 
544 /******************************************************************************************************
545 Free-streaming length (times Mnu/k_BT_nu, which is dimensionless) for a non-relativistic
546 particle of momentum q = T0, from scale factor ai to af.
547 Arguments:
548 logai - log of initial scale factor
549 logaf - log of final scale factor
550 light - speed of light in internal units.
551 Result is in Unit_Length/Unit_Time.
552 ******************************************************************************************************/
553 double fslength(Cosmology * CP, const double logai, const double logaf, const double light)
554 {
555  double abserr;
556  double fslength_val;
557  gsl_function F;
558  gsl_integration_workspace * w = gsl_integration_workspace_alloc (GSL_VAL);
559  F.function = &fslength_int;
560  F.params = CP;
561  if(logai >= logaf)
562  return 0;
563  gsl_integration_qag (&F, logai, logaf, 0, 1e-6,GSL_VAL,6,w,&(fslength_val), &abserr);
564  gsl_integration_workspace_free (w);
565  return light*fslength_val;
566 }
567 
568 /**************************************************************************************************
569 Fit to the special function J(x) that is accurate to better than 3% relative and 0.07% absolute
570  J(x) = Integrate[(Sin[q*x]/(q*x))*(q^2/(Exp[q] + 1)), {q, 0, Infinity}]
571  and J(0) = 1.
572  Mathematica gives this in terms of the PolyGamma function:
573  (PolyGamma[1, 1/2 - i x/2] - PolyGamma[1, 1 - i x/2] - PolyGamma[1, 1/2 + i x/2] +
574  PolyGamma[1, 1 + i x/2])/(12 x Zeta[3]), which we could evaluate exactly if we wanted to.
575 ***************************************************************************************************/
576 static inline double specialJ_fit(const double x)
577 {
578 
579  double x2, x4, x8;
580  if (x <= 0.)
581  return 1.;
582  x2 = x*x;
583  x4 = x2*x2;
584  x8 = x4*x4;
585 
586  return (1.+ 0.0168 * x2 + 0.0407* x4)/(1. + 2.1734 * x2 + 1.6787 * exp(4.1811*log(x)) + 0.1467 * x8);
587 }
588 
589 /*Asymptotic series expansion from YAH. Not good when qc * x is small, but fine otherwise.*/
590 static inline double II(const double x, const double qc, const int n)
591 {
592  return (n*n+n*n*n*qc+n*qc*x*x - x*x)* qc*gsl_sf_bessel_j0(qc*x) + (2*n+n*n*qc+qc*x*x)*cos(qc*x);
593 }
594 
595 /* Fourier transform of truncated Fermi Dirac distribution, with support on q > qc only.
596  * qc is a dimensionless momentum (normalized to TNU),
597  * mnu is in eV. x has units of inverse dimensionless momentum
598  * This is an approximation to integral f_0(q) q^2 j_0(qX) dq between qc and infinity.
599  * It gives the fraction of the integral that is due to neutrinos above a certain threshold.
600  * Arguments: vcmnu is vcrit*mnu/LIGHT */
601 static inline double Jfrac_high(const double x, const double qc, const double nufrac_low)
602 {
603  double integ=0;
604  int n;
605  for(n=1; n<20; n++)
606  {
607  integ+= -1*pow((-1),n)*exp(-n*qc)/(n*n+x*x)/(n*n+x*x)*II(x,qc,n);
608  }
609  /* Normalise with integral_qc^infty(f_0(q)q^2 dq), same as I(X).
610  * So that as qc-> infinity, this -> specialJ_fit(x)*/
611  integ /= 1.5 * 1.202056903159594 * (1 - nufrac_low);
612  return integ;
613 }
614 
615 /*Function that picks whether to use the truncated integrator or not*/
616 double specialJ(const double x, const double qc, const double nufrac_low)
617 {
618  if( qc > 0 ) {
619  return Jfrac_high(x, qc, nufrac_low);
620  }
621  return specialJ_fit(x);
622 }
623 
626 {
628  double k;
630  double mnubykT;
631  gsl_interp_accel *acc;
632  gsl_interp *spline;
635  gsl_interp_accel *fs_acc;
636  gsl_interp *fs_spline;
637  double * fslengths;
638  double * fsscales;
640  double * delta_tot;
641  double * scale;
645  double qc;
646  /*Fraction of neutrinos in particles for normalisation with hybrid neutrinos*/
647  double nufrac_low;
648 };
650 
652 double get_delta_nu_int(double logai, void * params)
653 {
654  delta_nu_int_params * p = (delta_nu_int_params *) params;
655  double fsl_aia = gsl_interp_eval(p->fs_spline,p->fsscales,p->fslengths,logai,p->fs_acc);
656  double delta_tot_at_a = gsl_interp_eval(p->spline,p->scale,p->delta_tot,logai,p->acc);
657  double specJ = specialJ(p->k*fsl_aia/p->mnubykT, p->qc, p->nufrac_low);
658  double ai = exp(logai);
659  return fsl_aia/(ai*hubble_function(p->CP, ai)) * specJ * delta_tot_at_a;
660 }
661 
662 /*
663 Main function: given tables of wavenumbers, total delta at Na earlier times (<= a),
664 and initial conditions for neutrinos, computes the current delta_nu.
665 Na is the number of currently stored time steps.
666 */
667 void get_delta_nu(Cosmology * CP, const _delta_tot_table * const d_tot, const double a, double delta_nu_curr[],const double mnu)
668 {
669  double fsl_A0a,deriv_prefac;
670  int ik;
671  /* Variable is unused unless we have hybrid neutrinos,
672  * but we define it anyway to save ifdeffing later.*/
673  double qc = 0;
674  /*Number of stored power spectra. This includes the initial guess for the next step*/
675  const int Na = d_tot->ia;
676  const double mnubykT = mnu /d_tot->omnu->kBtnu;
677  /*Tolerated integration error*/
678  double relerr = 1e-6;
679 // message(0,"Start get_delta_nu: a=%g Na =%d wavenum[0]=%g delta_tot[0]=%g m_nu=%g\n",a,Na,wavenum[0],d_tot->delta_tot[0][Na-1],mnu);
680 
681  fsl_A0a = fslength(CP, log(d_tot->TimeTransfer), log(a),d_tot->light);
682  /*Precompute factor used to get delta_nu_init. This assumes that delta ~ a, so delta-dot is roughly 1.*/
683  deriv_prefac = d_tot->TimeTransfer*(hubble_function(CP, d_tot->TimeTransfer)/d_tot->light)* d_tot->TimeTransfer;
684  for (ik = 0; ik < d_tot->nk; ik++) {
685  /* Initial condition piece, assuming linear evolution of delta with a up to startup redshift */
686  /* This assumes that delta ~ a, so delta-dot is roughly 1. */
687  /* Also ignores any difference in the transfer functions between species.
688  * This will be good if all species have similar masses, or
689  * if two species are massless.
690  * Also, since at early times the clustering is tiny, it is very unlikely to matter.*/
691  /*For zero mass neutrinos just use the initial conditions piece, modulating to zero inside the horizon*/
692  const double specJ = specialJ(d_tot->wavenum[ik]*fsl_A0a/(mnubykT > 0 ? mnubykT : 1),qc, d_tot->omnu->hybnu.nufrac_low[0]);
693  delta_nu_curr[ik] = specJ*d_tot->delta_nu_init[ik] *(1.+ deriv_prefac*fsl_A0a);
694  }
695  /* Check whether the particle neutrinos are active at this point.
696  * If they are we want to truncate our integration.
697  * Only do this is hybrid neutrinos are activated in the param file.*/
698  const double partnu = particle_nu_fraction(&d_tot->omnu->hybnu, a, 0);
699  if(partnu > 0) {
700 /* message(0,"Particle neutrinos gravitating: a=%g partnu: %g qc is: %g\n",a, partnu,qc); */
701  /*If the particles are everything, be done now*/
702  if(1 - partnu < 1e-3)
703  return;
704  qc = d_tot->omnu->hybnu.vcrit * mnubykT;
705  /*More generous integration error for particle neutrinos*/
706  relerr /= (1.+1e-5-particle_nu_fraction(&d_tot->omnu->hybnu,a,0));
707  }
708  /*If only one time given, we are still at the initial time*/
709  /*If neutrino mass is zero, we are not accurate, just use the initial conditions piece*/
710  if(Na > 1 && mnubykT > 0){
711  delta_nu_int_params params;
712  params.acc = gsl_interp_accel_alloc();
713  gsl_integration_workspace * w = gsl_integration_workspace_alloc (GSL_VAL);
714  gsl_function F;
715  F.function = &get_delta_nu_int;
716  F.params=&params;
717  /*Use cubic interpolation*/
718  if(Na > 2) {
719  params.spline=gsl_interp_alloc(gsl_interp_cspline,Na);
720  }
721  /*Unless we have only two points*/
722  else {
723  params.spline=gsl_interp_alloc(gsl_interp_linear,Na);
724  }
725  params.scale=d_tot->scalefact;
726  params.mnubykT=mnubykT;
727  params.qc = qc;
728  params.nufrac_low = d_tot->omnu->hybnu.nufrac_low[0];
729  /* Massively over-sample the free-streaming lengths.
730  * Interpolation is least accurate where the free-streaming length -> 0,
731  * which is exactly where it doesn't matter, but
732  * we still want to be safe. */
733  int Nfs = Na*16;
734  params.fs_acc = gsl_interp_accel_alloc();
735  params.fs_spline=gsl_interp_alloc(gsl_interp_cspline,Nfs);
736  params.CP = CP;
737  /*Pre-compute the free-streaming lengths, which are scale-independent*/
738  double * fslengths = (double *) mymalloc("fslengths", Nfs* sizeof(double));
739  double * fsscales = (double *) mymalloc("fsscales", Nfs* sizeof(double));
740  for(ik=0; ik < Nfs; ik++) {
741  fsscales[ik] = log(d_tot->TimeTransfer) + ik*(log(a) - log(d_tot->TimeTransfer))/(Nfs-1.);
742  fslengths[ik] = fslength(CP, fsscales[ik], log(a),d_tot->light);
743  }
744  params.fslengths = fslengths;
745  params.fsscales = fsscales;
746 
747  if(!params.spline || !params.acc || !w || !params.fs_spline || !params.fs_acc || !fslengths || !fsscales)
748  endrun(2016,"Error initialising and allocating memory for gsl interpolator and integrator.\n");
749 
750  gsl_interp_init(params.fs_spline,params.fsscales,params.fslengths,Nfs);
751  for (ik = 0; ik < d_tot->nk; ik++) {
752  double abserr,d_nu_tmp;
753  params.k=d_tot->wavenum[ik];
754  params.delta_tot=d_tot->delta_tot[ik];
755  gsl_interp_init(params.spline,params.scale,params.delta_tot,Na);
756  gsl_integration_qag (&F, log(d_tot->TimeTransfer), log(a), 0, relerr,GSL_VAL,6,w,&d_nu_tmp, &abserr);
757  delta_nu_curr[ik] += d_tot->delta_nu_prefac * d_nu_tmp;
758  }
759  gsl_integration_workspace_free (w);
760  gsl_interp_free(params.spline);
761  gsl_interp_accel_free(params.acc);
762  myfree(fsscales);
763  myfree(fslengths);
764  }
765 // for(ik=0; ik< 3; ik++)
766 // message(0,"k %g d_nu %g\n",wavenum[d_tot->nk/8*ik], delta_nu_curr[d_tot->nk/8*ik]);
767  return;
768 }
double hubble_function(const Cosmology *CP, double a)
Definition: cosmology.c:58
void message(int where, const char *fmt,...)
Definition: endrun.c:175
void endrun(int where, const char *fmt,...)
Definition: endrun.c:147
#define mymalloc(name, size)
Definition: mymalloc.h:15
#define mymalloc2(name, size)
Definition: mymalloc.h:16
#define myfree(x)
Definition: mymalloc.h:19
void init_neutrinos_lra(const int nk_in, const double TimeTransfer, const double TimeMax, const double Omega0, const _omega_nu *const omnu, const double UnitTime_in_s, const double UnitLength_in_cm)
static double get_delta_tot(const double delta_nu_curr, const double delta_cdm_curr, const double OmegaNua3, const double Omeganonu, const double Omeganu1, const double particle_nu_fraction)
Definition: neutrinos_lra.c:67
static double specialJ_fit(const double x)
void petaio_save_neutrinos(BigFile *bf, int ThisTask)
#define GSL_VAL
Definition: neutrinos_lra.c:30
double fslength(Cosmology *CP, const double logai, const double logaf, const double light)
void petaio_read_neutrinos(BigFile *bf, int ThisTask)
#define FLOAT_ACC
Definition: neutrinos_lra.c:28
static double II(const double x, const double qc, const int n)
static void delta_tot_first_init(_delta_tot_table *const d_tot, const int nk_in, const double wavenum[], const double delta_cdm_curr[], const double TimeIC)
Definition: neutrinos_lra.c:97
static double Jfrac_high(const double x, const double qc, const double nufrac_low)
static _transfer_init_table * t_init
Definition: neutrinos_lra.c:91
double get_delta_nu_int(double logai, void *params)
void petaio_read_icnutransfer(BigFile *bf, int ThisTask)
_delta_tot_table delta_tot_table
Definition: neutrinos_lra.c:75
void get_delta_nu_combined(Cosmology *CP, const _delta_tot_table *const d_tot, const double a, double delta_nu_curr[])
double fslength_int(const double loga, void *params)
void delta_nu_from_power(struct _powerspectrum *PowerSpectrum, Cosmology *CP, const double Time, const double TimeIC)
void get_delta_nu(Cosmology *CP, const _delta_tot_table *const d_tot, const double a, double delta_nu_curr[], const double mnu)
static _transfer_init_table t_init_data
Definition: neutrinos_lra.c:90
void update_delta_tot(_delta_tot_table *const d_tot, const double a, const double delta_cdm_curr[], const double delta_nu_curr[], const int overwrite)
void powerspectrum_nu_save(struct _powerspectrum *PowerSpectrum, const char *OutputDir, const char *filename, const double Time)
double specialJ(const double x, const double vcmnubylight, const double nufrac_low)
double omega_nu_single(const _omega_nu *const omnu, const double a, int i)
double particle_nu_fraction(const _hybrid_nu *const hybnu, const double a, const int i)
double nufrac_low(const double qc)
double get_omega_nu(const _omega_nu *const omnu, const double a)
double get_omega_nu_nopart(const _omega_nu *const omnu, const double a)
#define NUSPECIES
int petaio_read_block(BigFile *bf, const char *blockname, BigArray *array, int required)
Definition: petaio.c:562
void petaio_save_block(BigFile *bf, const char *blockname, BigArray *array, int verbose)
Definition: petaio.c:587
#define HUBBLE
Definition: physconst.h:25
#define LIGHTCGS
Definition: physconst.h:18
static Cosmology * CP
Definition: power.c:27
static double UnitLength_in_cm
Definition: power.c:26
char * fastpm_strdup_printf(const char *fmt,...)
Definition: string.c:41
_omega_nu ONu
Definition: cosmology.h:24
gsl_interp_accel * acc
gsl_interp_accel * fs_acc
gsl_interp * fs_spline
double ** delta_tot
Definition: neutrinos_lra.h:27
double * scalefact
Definition: neutrinos_lra.h:29
double * delta_nu_init
Definition: neutrinos_lra.h:31
const _omega_nu * omnu
Definition: neutrinos_lra.h:37
double delta_nu_prefac
Definition: neutrinos_lra.h:23
double * delta_nu_last
Definition: neutrinos_lra.h:33
double nufrac_low[NUSPECIES]
_hybrid_nu hybnu
int nu_degeneracies[NUSPECIES]
_rho_nu_single RhoNuTab[NUSPECIES]
gsl_interp_accel * nu_acc
Definition: powerspectrum.h:24
double * logknu
Definition: powerspectrum.h:20
double * delta_nu_ratio
Definition: powerspectrum.h:21
double * Power
Definition: powerspectrum.h:10
gsl_interp * nu_spline
Definition: powerspectrum.h:23
int64_t * Nmodes
Definition: powerspectrum.h:11
int ThisTask
Definition: test_exchange.c:23