From ad5d4ba03c0dafb04a9109912f41b4bc11d05e2d Mon Sep 17 00:00:00 2001 From: James Davies Date: Fri, 10 Oct 2025 12:46:15 +1100 Subject: [PATCH 01/18] WIP making correlated SFH sampler --- src/py21cmfast/src/cosmology.c | 24 +++ src/py21cmfast/src/cosmology.h | 2 + src/py21cmfast/src/scaling_relations.c | 237 +++++++++++++++++++++++++ src/py21cmfast/src/scaling_relations.h | 6 + 4 files changed, 269 insertions(+) diff --git a/src/py21cmfast/src/cosmology.c b/src/py21cmfast/src/cosmology.c index 58b3e3458..1ff27c0b9 100644 --- a/src/py21cmfast/src/cosmology.c +++ b/src/py21cmfast/src/cosmology.c @@ -761,3 +761,27 @@ double t_hubble(float z) { return 1.0 / hubble(z); } /* comoving distance (in cm) per unit redshift */ double drdz(float z) { return (1.0 + z) * physconst.c_cms * dtdz(z); } + +double time_between_z(double z_low, double z_high) { + double result, error; + gsl_function F; + double rel_tol = 1e-4; //<- relative tolerance + int w_size = 1000; + gsl_integration_workspace* w = gsl_integration_workspace_alloc(w_size); + + int status; + F.function = &dtdz; + + gsl_set_error_handler_off(); + status = gsl_integration_qag(&F, z_low, z_high, 0, rel_tol, w_size, GSL_INTEG_GAUSS61, w, + &result, &error); + + if (status != 0) { + LOG_ERROR("gsl integration error occured!"); + LOG_ERROR("z_low = %.4e z_high = %.4e", z_low, z_high); + CATCH_GSL_ERROR(status); + } + + gsl_integration_workspace_free(w); + return result; +} diff --git a/src/py21cmfast/src/cosmology.h b/src/py21cmfast/src/cosmology.h index 7da11f2df..adbecc4a1 100644 --- a/src/py21cmfast/src/cosmology.h +++ b/src/py21cmfast/src/cosmology.h @@ -33,4 +33,6 @@ double hubble(float z); double t_hubble(float z); double M_J_WDM(); +double time_between_z(double z_low, double z_high); + #endif diff --git a/src/py21cmfast/src/scaling_relations.c b/src/py21cmfast/src/scaling_relations.c index 93f5a4d33..2222b2af5 100644 --- a/src/py21cmfast/src/scaling_relations.c +++ b/src/py21cmfast/src/scaling_relations.c @@ -2,6 +2,10 @@ the integrals in hmf.c or the sampled halos in HaloBox.c*/ #include "scaling_relations.h" +#include +#include +#include +#include #include #include #include @@ -14,6 +18,7 @@ #include "cosmology.h" #include "exceptions.h" #include "hmf.h" +#include "interpolation.h" #include "logger.h" #include "photoncons.h" #include "thermochem.h" @@ -102,6 +107,238 @@ void set_scaling_constants(double redshift, ScalingConstants *consts, bool use_p } } +// Carvajal-Bohorquez et al. 2025 form +// Normalised to 1 for later multiplication +double psd_sfh_powerlaw(double w) { + return 1.0 / (1 + pow(w * astro_params_global->tau_SFH, astro_params_global->b_SFH)); +} + +#define MAX_TAU (double)(100) // Myr +#define N_TAU_SFH (double)(1000) // number of time bins in SFH +#define N_FREQ_SFH (int)(N_TAU_SFH / 2 + 1) // number of frequency bins in SFH + +// We want a singleton struct which holds the SFH correlation functions +typedef struct SFH_Correlation { + RGTable1D *corr_10_10; + RGTable1D *corr_10_100; + RGTable1D *corr_100_100; + RGTable1D *corr_10_snap; + RGTable1D *corr_100_snap; + RGTable1D *corr_snap_snap; +} SFH_Correlation; + +static SFH_Correlation sfh_corr; + +fftwf_complex shifted_tophat_1d(double wt) { + // fourier transform of a real space tophat shift in the positive direction by R/2 + // We use this in the SFH model to get SFR between now and R Myr ago + fftwf_complex result; + if (wt < 1e-4) + return 1.0 - 0.5 * I * wt - wt * wt / 6.0; // second order taylor expansion around kR==0 + return I / wt * (exp(-I * wt) - 1.); +} + +void initialise_sfh_correlation(double z, double z_prev) { + allocate_RGTable1D(N_FREQ_SFH, sfh_corr.corr_10_10); + allocate_RGTable1D(N_FREQ_SFH, sfh_corr.corr_10_100); + allocate_RGTable1D(N_FREQ_SFH, sfh_corr.corr_100_100); + allocate_RGTable1D(N_FREQ_SFH, sfh_corr.corr_10_snap); + allocate_RGTable1D(N_FREQ_SFH, sfh_corr.corr_100_snap); + allocate_RGTable1D(N_FREQ_SFH, sfh_corr.corr_snap_snap); + + // set up the RGTable1D x-axis + sfh_corr.corr_10_10->x_min = 0.; + sfh_corr.corr_10_10->x_width = MAX_TAU / (N_TAU_SFH - 1); + sfh_corr.corr_10_10->n_bin = N_TAU_SFH; + + sfh_corr.corr_10_100->x_min = 0.; + sfh_corr.corr_10_100->x_width = MAX_TAU / (N_TAU_SFH - 1); + sfh_corr.corr_10_100->n_bin = N_TAU_SFH; + + sfh_corr.corr_100_100->x_min = 0.; + sfh_corr.corr_100_100->x_width = MAX_TAU / (N_TAU_SFH - 1); + sfh_corr.corr_100_100->n_bin = N_TAU_SFH; + + double w_arr[N_FREQ_SFH]; + for (int i = 0; i < N_FREQ_SFH; i++) { + w_arr[i] = 2 * M_PI * i / MAX_TAU; + } + + double t_snap = time_between_z(z, z_prev) / (SperYR * 1e6); // Myr + + // determine the SFH correlation functions + fftwf_complex psd_unfiltered[N_FREQ_SFH]; + fftwf_complex W_10[N_FREQ_SFH], W_100[N_FREQ_SFH], W_snap[N_FREQ_SFH], psd_filtered[N_FREQ_SFH]; + + fftwf_complex *in; + float *out; + in = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * N_FREQ_SFH); + out = (float *)calloc(N_FREQ_SFH, sizeof(float)); + fftwf_plan p = fftwf_plan_dft_c2r_1d(N_TAU_SFH, in, out, FFTW_ESTIMATE); + + // get the filters + for (int i = 0; i < N_FREQ_SFH; i++) { + psd_unfiltered[i] = psd_sfh_powerlaw(w_arr[i]); + W_10[i] = shifted_tophat_1d(w_arr[i] * 10.); + W_100[i] = shifted_tophat_1d(w_arr[i] * 100.); + W_snap[i] = shifted_tophat_1d(w_arr[i] * t_snap); + } + + // TODO: check that there N_TAU_SFH normalization on the iFFT + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_10[i] * conj(W_10[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_10_10->y_arr[i] = out[i] / N_TAU_SFH; + } + + // 10_100 + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_100[i] * conj(W_10[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_10_100->y_arr[i] = out[i] / N_TAU_SFH; + } + + // 100_100 + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_100[i] * conj(W_100[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_100_100->y_arr[i] = out[i] / N_TAU_SFH; + } + + // 10_snap + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_10[i] * conj(W_snap[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_10_snap->y_arr[i] = out[i] / N_TAU_SFH; + } + + // 100_snap + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_100[i] * conj(W_snap[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_100_snap->y_arr[i] = out[i] / N_TAU_SFH; + } + + // snap_snap + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_snap[i] * conj(W_snap[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_snap_snap->y_arr[i] = out[i] / N_TAU_SFH; + } + + fftwf_destroy_plan(p); + fftwf_free(in); + fftwf_free(out); +} + +void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean_correction) { + /* Evaluate the SFH covariance matrix at a given time step tau + + Outputs are the two matrices required for sampling the SFR correctly + + out_chol_cov = Cholesky factor of Cov(curr|prev), multiplies standard normal vector + to get correlated & conditioned SFRs + + out_mean_correction = multiplies the condition vector, to be added to the correlated samples. + */ + + // Interpolate correlation functions at lag tau + double cov_10_10_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_10); + double cov_10_100_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_100); + double cov_10_snap_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_snap); + double cov_100_100_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_100); + double cov_100_snap_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_snap); + double cov_snap_snap_tau = EvaluateRGTable1D(tau, sfh_corr.corr_snap_snap); + + // Get zero-lag correlations + double cov_10_10_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_10); + double cov_10_100_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_100); + double cov_10_snap_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_snap); + double cov_100_100_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_100); + double cov_100_snap_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_snap); + double cov_snap_snap_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_snap_snap); + + gsl_matrix *prev_cov = gsl_matrix_alloc(3, 3); + gsl_matrix_set(prev_cov, 0, 0, cov_10_10_zero); // 10_prev vs 10_prev + gsl_matrix_set(prev_cov, 0, 1, cov_10_100_zero); // 10_prev vs 100_prev + gsl_matrix_set(prev_cov, 0, 2, cov_10_snap_zero); // 10_prev vs snap_prev + gsl_matrix_set(prev_cov, 1, 0, cov_10_100_zero); // 100_prev vs 10_prev + gsl_matrix_set(prev_cov, 1, 1, cov_100_100_zero); // 100_prev vs 100_prev + gsl_matrix_set(prev_cov, 1, 2, cov_100_snap_zero); // 100_prev vs snap_prev + gsl_matrix_set(prev_cov, 2, 0, cov_10_snap_zero); // snap_prev vs 10_prev + gsl_matrix_set(prev_cov, 2, 1, cov_100_snap_zero); // snap_prev vs 100_prev + gsl_matrix_set(prev_cov, 2, 2, cov_snap_snap_zero); // snap_prev vs snap_prev + + gsl_matrix *cross_cov = gsl_matrix_alloc(3, 3); + gsl_matrix_set(cross_cov, 0, 0, cov_10_10_tau); // 10_prev vs 10_curr + gsl_matrix_set(cross_cov, 0, 1, cov_10_100_tau); // 10_prev vs 100_curr + gsl_matrix_set(cross_cov, 0, 2, cov_10_snap_tau); // 10_prev vs snap_curr + gsl_matrix_set(cross_cov, 1, 0, cov_10_100_tau); // 100_prev vs 10_curr + gsl_matrix_set(cross_cov, 1, 1, cov_100_100_tau); // 100_prev vs 100_curr + gsl_matrix_set(cross_cov, 1, 2, cov_100_snap_tau); // 100_prev vs snap_curr + gsl_matrix_set(cross_cov, 2, 0, cov_10_snap_tau); // snap_prev vs 10_curr + gsl_matrix_set(cross_cov, 2, 1, cov_100_snap_tau); // snap_prev vs 100_curr + gsl_matrix_set(cross_cov, 2, 2, cov_snap_snap_tau); // snap_prev vs snap_curr + + gsl_matrix *curr_cov = gsl_matrix_alloc(3, 3); + + // NOTE: Currently PSD is not a function of z, so curr_cov == prev_cov + gsl_matrix_memcpy(curr_cov, prev_cov); + + // NOTE: Currently Cov(curr,prev) == Cov(prev,curr) == Cov(prev,curr)^T == Cov(curr,prev)^T + gsl_matrix *matrix_buf = gsl_matrix_alloc(3, 3); + gsl_matrix *conditional_cov = gsl_matrix_alloc(3, 3); + + // Cholesky factorization of Cov(prev) = L L^T to do implicit inversion + gsl_linalg_cholesky_decomp1(prev_cov); // holds L + + // Compute the conditional covariance matrix Cov(curr|prev) = Cov(curr) - Cov(curr,prev) + // Cov(prev)^-1 Cov(prev,curr) + gsl_matrix_transpose_memcpy(matrix_buf, cross_cov); // The transpose is currently not needed + // Compute Cov(prev,curr) L^-1^T + gsl_blas_dtrsm(CblasRight, CblasLower, CblasTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); + + // Compute Cov(prev,curr) L^-T^-1 L^-1 = Cov(curr,prev) Cov(prev)^-1 + gsl_blas_dtrsm(CblasRight, CblasLower, CblasTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); + + gsl_matrix_free(prev_cov); + + // Since, for zero mean, E[X|Y] = Cov(X,Y) Cov(Y)^-1 Y + gsl_matrix_memcpy(out_mean_correction, matrix_buf); // store for output + + // compute Cov(curr) - Cov(curr|prev) Cov(prev)^-1 Cov(prev,curr) + gsl_matrix_memcpy(conditional_cov, curr_cov); + gsl_blas_dsymm(CblasLeft, CblasLower, -1.0, matrix_buf, cross_cov, 1.0, conditional_cov); + + // Perform Cholesky decomposition + gsl_linalg_cholesky_decomp1(conditional_cov); + + gsl_matrix_memcpy(out_chol_cov, conditional_cov); + gsl_matrix_free(curr_cov); + gsl_matrix_free(cross_cov); +} + +void free_sfh_correlation() { + free_RGTable1D(sfh_corr.corr_10_10); + free_RGTable1D(sfh_corr.corr_10_100); + free_RGTable1D(sfh_corr.corr_100_100); + free_RGTable1D(sfh_corr.corr_10_snap); + free_RGTable1D(sfh_corr.corr_100_snap); + free_RGTable1D(sfh_corr.corr_snap_snap); +} + // It's often useful to create a copy of scaling constants without F_ESC ScalingConstants evolve_scaling_constants_sfr(ScalingConstants *sc) { ScalingConstants sc_sfrd = *sc; diff --git a/src/py21cmfast/src/scaling_relations.h b/src/py21cmfast/src/scaling_relations.h index 0743ae98f..4de4cf150 100644 --- a/src/py21cmfast/src/scaling_relations.h +++ b/src/py21cmfast/src/scaling_relations.h @@ -1,6 +1,7 @@ #ifndef _SCALING_H #define _SCALING_H +#include #include #include "InputParameters.h" @@ -72,5 +73,10 @@ ScalingConstants evolve_scaling_constants_to_redshift(double redshift, ScalingCo bool use_photoncons); ScalingConstants mimic_scatter_in_consts(ScalingConstants *sc); void print_sc_consts(ScalingConstants *c); +void initialise_sfh_correlation(double z, double z_prev); + +// Forward define GSL types to avoid including GSL headers here +void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean_correction); +void free_sfh_correlation(); #endif From 5f30916b5117fb3ac0d216786d84a1ec550e4d7f Mon Sep 17 00:00:00 2001 From: James Davies Date: Wed, 22 Oct 2025 13:18:14 +1100 Subject: [PATCH 02/18] finish matrix math --- src/py21cmfast/src/scaling_relations.c | 207 ++++++++++++++++--------- 1 file changed, 133 insertions(+), 74 deletions(-) diff --git a/src/py21cmfast/src/scaling_relations.c b/src/py21cmfast/src/scaling_relations.c index 2222b2af5..7dcb76b69 100644 --- a/src/py21cmfast/src/scaling_relations.c +++ b/src/py21cmfast/src/scaling_relations.c @@ -118,17 +118,29 @@ double psd_sfh_powerlaw(double w) { #define N_FREQ_SFH (int)(N_TAU_SFH / 2 + 1) // number of frequency bins in SFH // We want a singleton struct which holds the SFH correlation functions +// We need the crosses between four timescales: 10 Myr, 100 Myr, snapshot interval, previous +// snapshot interval typedef struct SFH_Correlation { RGTable1D *corr_10_10; RGTable1D *corr_10_100; RGTable1D *corr_100_100; - RGTable1D *corr_10_snap; - RGTable1D *corr_100_snap; - RGTable1D *corr_snap_snap; + RGTable1D *corr_10_curr; + RGTable1D *corr_100_curr; + RGTable1D *corr_curr_curr; + RGTable1D *corr_10_prev; + RGTable1D *corr_100_prev; + RGTable1D *corr_curr_prev; + RGTable1D *corr_prev_prev; } SFH_Correlation; static SFH_Correlation sfh_corr; +// Make a union type for easy initialisation +typedef union sfh_c_u { + SFH_Correlation sfh_c_s; + RGTable1D *sfh_c_a[10]; +} sfh_c_u; + fftwf_complex shifted_tophat_1d(double wt) { // fourier transform of a real space tophat shift in the positive direction by R/2 // We use this in the SFH model to get SFR between now and R Myr ago @@ -138,37 +150,30 @@ fftwf_complex shifted_tophat_1d(double wt) { return I / wt * (exp(-I * wt) - 1.); } -void initialise_sfh_correlation(double z, double z_prev) { - allocate_RGTable1D(N_FREQ_SFH, sfh_corr.corr_10_10); - allocate_RGTable1D(N_FREQ_SFH, sfh_corr.corr_10_100); - allocate_RGTable1D(N_FREQ_SFH, sfh_corr.corr_100_100); - allocate_RGTable1D(N_FREQ_SFH, sfh_corr.corr_10_snap); - allocate_RGTable1D(N_FREQ_SFH, sfh_corr.corr_100_snap); - allocate_RGTable1D(N_FREQ_SFH, sfh_corr.corr_snap_snap); - - // set up the RGTable1D x-axis - sfh_corr.corr_10_10->x_min = 0.; - sfh_corr.corr_10_10->x_width = MAX_TAU / (N_TAU_SFH - 1); - sfh_corr.corr_10_10->n_bin = N_TAU_SFH; - - sfh_corr.corr_10_100->x_min = 0.; - sfh_corr.corr_10_100->x_width = MAX_TAU / (N_TAU_SFH - 1); - sfh_corr.corr_10_100->n_bin = N_TAU_SFH; - - sfh_corr.corr_100_100->x_min = 0.; - sfh_corr.corr_100_100->x_width = MAX_TAU / (N_TAU_SFH - 1); - sfh_corr.corr_100_100->n_bin = N_TAU_SFH; +void initialise_sfh_correlation(double z, double z_prev, double z_prev_2) { + sfh_c_u sfh_corr_u; + RGTable1D *table_ptr; + sfh_corr_u.sfh_c_s = sfh_corr; + + for (int i = 0; i < 9; i++) { + table_ptr = sfh_corr_u.sfh_c_a[i]; + allocate_RGTable1D(N_FREQ_SFH, table_ptr); + table_ptr->x_min = 0.; + table_ptr->x_width = MAX_TAU / (N_TAU_SFH - 1); + table_ptr->n_bin = N_TAU_SFH; + } double w_arr[N_FREQ_SFH]; for (int i = 0; i < N_FREQ_SFH; i++) { w_arr[i] = 2 * M_PI * i / MAX_TAU; } - double t_snap = time_between_z(z, z_prev) / (SperYR * 1e6); // Myr + double t_snap = time_between_z(z, z_prev) / (physconst.s_per_yr * 1e6); // Myr + double t_snap_prev = time_between_z(z_prev, z_prev_2) / (physconst.s_per_yr * 1e6); // Myr // determine the SFH correlation functions - fftwf_complex psd_unfiltered[N_FREQ_SFH]; - fftwf_complex W_10[N_FREQ_SFH], W_100[N_FREQ_SFH], W_snap[N_FREQ_SFH], psd_filtered[N_FREQ_SFH]; + fftwf_complex W_10[N_FREQ_SFH], W_100[N_FREQ_SFH], W_curr[N_FREQ_SFH], W_prev[N_FREQ_SFH]; + fftwf_complex psd_unfiltered[N_FREQ_SFH], psd_filtered[N_FREQ_SFH]; fftwf_complex *in; float *out; @@ -181,7 +186,8 @@ void initialise_sfh_correlation(double z, double z_prev) { psd_unfiltered[i] = psd_sfh_powerlaw(w_arr[i]); W_10[i] = shifted_tophat_1d(w_arr[i] * 10.); W_100[i] = shifted_tophat_1d(w_arr[i] * 100.); - W_snap[i] = shifted_tophat_1d(w_arr[i] * t_snap); + W_curr[i] = shifted_tophat_1d(w_arr[i] * t_snap); + W_prev[i] = shifted_tophat_1d(w_arr[i] * t_snap_prev); } // TODO: check that there N_TAU_SFH normalization on the iFFT @@ -193,7 +199,7 @@ void initialise_sfh_correlation(double z, double z_prev) { sfh_corr.corr_10_10->y_arr[i] = out[i] / N_TAU_SFH; } - // 10_100 + // 10Myr X 100Myr for (int i = 0; i < N_FREQ_SFH; i++) { in[i] = psd_unfiltered[i] * W_100[i] * conj(W_10[i]); } @@ -202,7 +208,7 @@ void initialise_sfh_correlation(double z, double z_prev) { sfh_corr.corr_10_100->y_arr[i] = out[i] / N_TAU_SFH; } - // 100_100 + // 100Myr X 100Myr for (int i = 0; i < N_FREQ_SFH; i++) { in[i] = psd_unfiltered[i] * W_100[i] * conj(W_100[i]); } @@ -211,31 +217,67 @@ void initialise_sfh_correlation(double z, double z_prev) { sfh_corr.corr_100_100->y_arr[i] = out[i] / N_TAU_SFH; } - // 10_snap + // 10Myr X Current snapshot length + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_10[i] * conj(W_curr[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_10_curr->y_arr[i] = out[i] / N_TAU_SFH; + } + + // 100Myr X Current snapshot length + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_100[i] * conj(W_curr[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_100_curr->y_arr[i] = out[i] / N_TAU_SFH; + } + + // Current snapshot length X Current snapshot length + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_curr[i] * conj(W_curr[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_curr_curr->y_arr[i] = out[i] / N_TAU_SFH; + } + + // 10Myr X Previous snapshot length + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_10[i] * conj(W_prev[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_10_prev->y_arr[i] = out[i] / N_TAU_SFH; + } + + // 100Myr X Previous snapshot length for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_10[i] * conj(W_snap[i]); + in[i] = psd_unfiltered[i] * W_100[i] * conj(W_prev[i]); } fftwf_execute(p); for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_10_snap->y_arr[i] = out[i] / N_TAU_SFH; + sfh_corr.corr_100_prev->y_arr[i] = out[i] / N_TAU_SFH; } - // 100_snap + // Current snapshot length X Previous snapshot length for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_100[i] * conj(W_snap[i]); + in[i] = psd_unfiltered[i] * W_curr[i] * conj(W_prev[i]); } fftwf_execute(p); for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_100_snap->y_arr[i] = out[i] / N_TAU_SFH; + sfh_corr.corr_curr_prev->y_arr[i] = out[i] / N_TAU_SFH; } - // snap_snap + // Previous snapshot length X Previous snapshot length for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_snap[i] * conj(W_snap[i]); + in[i] = psd_unfiltered[i] * W_prev[i] * conj(W_prev[i]); } fftwf_execute(p); for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_snap_snap->y_arr[i] = out[i] / N_TAU_SFH; + sfh_corr.corr_prev_prev->y_arr[i] = out[i] / N_TAU_SFH; } fftwf_destroy_plan(p); @@ -249,7 +291,7 @@ void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean Outputs are the two matrices required for sampling the SFR correctly out_chol_cov = Cholesky factor of Cov(curr|prev), multiplies standard normal vector - to get correlated & conditioned SFRs + to get correlated & conditioned SFRs. NOTE: The upper triangle is out_mean_correction = multiplies the condition vector, to be added to the correlated samples. */ @@ -257,86 +299,103 @@ void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean // Interpolate correlation functions at lag tau double cov_10_10_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_10); double cov_10_100_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_100); - double cov_10_snap_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_snap); + double cov_10_curr_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_curr); + double cov_10_prev_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_prev); double cov_100_100_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_100); - double cov_100_snap_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_snap); - double cov_snap_snap_tau = EvaluateRGTable1D(tau, sfh_corr.corr_snap_snap); + double cov_100_curr_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_curr); + double cov_100_prev_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_prev); + double cov_curr_prev_tau = EvaluateRGTable1D(tau, sfh_corr.corr_curr_prev); // Get zero-lag correlations double cov_10_10_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_10); double cov_10_100_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_100); - double cov_10_snap_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_snap); + double cov_10_curr_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_curr); + double cov_10_prev_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_prev); double cov_100_100_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_100); - double cov_100_snap_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_snap); - double cov_snap_snap_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_snap_snap); + double cov_100_curr_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_curr); + double cov_100_prev_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_prev); + double cov_curr_curr_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_curr_curr); + double cov_prev_prev_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_prev_prev); + // Previous Snapshot covariance matrix gsl_matrix *prev_cov = gsl_matrix_alloc(3, 3); gsl_matrix_set(prev_cov, 0, 0, cov_10_10_zero); // 10_prev vs 10_prev gsl_matrix_set(prev_cov, 0, 1, cov_10_100_zero); // 10_prev vs 100_prev - gsl_matrix_set(prev_cov, 0, 2, cov_10_snap_zero); // 10_prev vs snap_prev + gsl_matrix_set(prev_cov, 0, 2, cov_10_prev_zero); // 10_prev vs snap_prev gsl_matrix_set(prev_cov, 1, 0, cov_10_100_zero); // 100_prev vs 10_prev gsl_matrix_set(prev_cov, 1, 1, cov_100_100_zero); // 100_prev vs 100_prev - gsl_matrix_set(prev_cov, 1, 2, cov_100_snap_zero); // 100_prev vs snap_prev - gsl_matrix_set(prev_cov, 2, 0, cov_10_snap_zero); // snap_prev vs 10_prev - gsl_matrix_set(prev_cov, 2, 1, cov_100_snap_zero); // snap_prev vs 100_prev - gsl_matrix_set(prev_cov, 2, 2, cov_snap_snap_zero); // snap_prev vs snap_prev + gsl_matrix_set(prev_cov, 1, 2, cov_100_prev_zero); // 100_prev vs snap_prev + gsl_matrix_set(prev_cov, 2, 0, cov_10_prev_zero); // snap_prev vs 10_prev + gsl_matrix_set(prev_cov, 2, 1, cov_100_prev_zero); // snap_prev vs 100_prev + gsl_matrix_set(prev_cov, 2, 2, cov_prev_prev_zero); // snap_prev vs snap_prev + // Lower left corner Covariance is Cov(curr,prev) == Cov(prev,curr)^T gsl_matrix *cross_cov = gsl_matrix_alloc(3, 3); gsl_matrix_set(cross_cov, 0, 0, cov_10_10_tau); // 10_prev vs 10_curr gsl_matrix_set(cross_cov, 0, 1, cov_10_100_tau); // 10_prev vs 100_curr - gsl_matrix_set(cross_cov, 0, 2, cov_10_snap_tau); // 10_prev vs snap_curr + gsl_matrix_set(cross_cov, 0, 2, cov_10_prev_tau); // 10_prev vs snap_curr gsl_matrix_set(cross_cov, 1, 0, cov_10_100_tau); // 100_prev vs 10_curr gsl_matrix_set(cross_cov, 1, 1, cov_100_100_tau); // 100_prev vs 100_curr - gsl_matrix_set(cross_cov, 1, 2, cov_100_snap_tau); // 100_prev vs snap_curr - gsl_matrix_set(cross_cov, 2, 0, cov_10_snap_tau); // snap_prev vs 10_curr - gsl_matrix_set(cross_cov, 2, 1, cov_100_snap_tau); // snap_prev vs 100_curr - gsl_matrix_set(cross_cov, 2, 2, cov_snap_snap_tau); // snap_prev vs snap_curr + gsl_matrix_set(cross_cov, 1, 2, cov_100_prev_tau); // 100_prev vs snap_curr + gsl_matrix_set(cross_cov, 2, 0, cov_10_curr_tau); // snap_prev vs 10_curr + gsl_matrix_set(cross_cov, 2, 1, cov_100_curr_tau); // snap_prev vs 100_curr + gsl_matrix_set(cross_cov, 2, 2, cov_curr_prev_tau); // snap_prev vs snap_curr + // Current Snapshot covariance matrix gsl_matrix *curr_cov = gsl_matrix_alloc(3, 3); - - // NOTE: Currently PSD is not a function of z, so curr_cov == prev_cov + // NOTE: Since the snapshot lengths are different, the snap variances are different gsl_matrix_memcpy(curr_cov, prev_cov); + gsl_matrix_set(curr_cov, 0, 2, cov_10_curr_zero); // 10_curr vs 10_curr + gsl_matrix_set(curr_cov, 1, 2, cov_100_curr_zero); // 100_curr vs 100_curr + gsl_matrix_set(curr_cov, 2, 0, cov_10_curr_zero); // 10_curr vs 10_curr + gsl_matrix_set(curr_cov, 2, 1, cov_100_curr_zero); // 100_curr vs 100_curr + gsl_matrix_set(curr_cov, 2, 2, cov_curr_curr_zero); // snap_curr vs snap_curr // NOTE: Currently Cov(curr,prev) == Cov(prev,curr) == Cov(prev,curr)^T == Cov(curr,prev)^T gsl_matrix *matrix_buf = gsl_matrix_alloc(3, 3); - gsl_matrix *conditional_cov = gsl_matrix_alloc(3, 3); // Cholesky factorization of Cov(prev) = L L^T to do implicit inversion gsl_linalg_cholesky_decomp1(prev_cov); // holds L // Compute the conditional covariance matrix Cov(curr|prev) = Cov(curr) - Cov(curr,prev) // Cov(prev)^-1 Cov(prev,curr) - gsl_matrix_transpose_memcpy(matrix_buf, cross_cov); // The transpose is currently not needed - // Compute Cov(prev,curr) L^-1^T + gsl_matrix_memcpy(matrix_buf, cross_cov); + // Cov(curr,prev) L^T^-1 gsl_blas_dtrsm(CblasRight, CblasLower, CblasTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); - // Compute Cov(prev,curr) L^-T^-1 L^-1 = Cov(curr,prev) Cov(prev)^-1 - gsl_blas_dtrsm(CblasRight, CblasLower, CblasTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); + // compute Cov(curr) - BUF*BUF^T == Cov(curr) - Cov(curr|prev) Cov(prev)^-1 Cov(prev,curr) + // NOTE, curr_cov is symmetric here + gsl_blas_dsyrk(CblasLower, CblasNoTrans, -1.0, matrix_buf, 1.0, curr_cov); + // The lower triangle of curr_cov now holds Cov(curr|prev) - gsl_matrix_free(prev_cov); + // Perform Cholesky decomposition (only uses lower triangle) + gsl_linalg_cholesky_decomp1(curr_cov); + gsl_matrix_memcpy(out_chol_cov, curr_cov); + + // Now Compute the mean correction term, since BUF was preserved from the rank-k above + // Compute Cov(prev,curr) L^-T^-1 L^-1 = Cov(curr,prev) Cov(prev)^-1 + gsl_blas_dtrsm(CblasRight, CblasLower, CblasNoTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); // Since, for zero mean, E[X|Y] = Cov(X,Y) Cov(Y)^-1 Y gsl_matrix_memcpy(out_mean_correction, matrix_buf); // store for output - // compute Cov(curr) - Cov(curr|prev) Cov(prev)^-1 Cov(prev,curr) - gsl_matrix_memcpy(conditional_cov, curr_cov); - gsl_blas_dsymm(CblasLeft, CblasLower, -1.0, matrix_buf, cross_cov, 1.0, conditional_cov); - - // Perform Cholesky decomposition - gsl_linalg_cholesky_decomp1(conditional_cov); - - gsl_matrix_memcpy(out_chol_cov, conditional_cov); + gsl_matrix_free(prev_cov); gsl_matrix_free(curr_cov); gsl_matrix_free(cross_cov); + gsl_matrix_free(matrix_buf); } void free_sfh_correlation() { free_RGTable1D(sfh_corr.corr_10_10); free_RGTable1D(sfh_corr.corr_10_100); free_RGTable1D(sfh_corr.corr_100_100); - free_RGTable1D(sfh_corr.corr_10_snap); - free_RGTable1D(sfh_corr.corr_100_snap); - free_RGTable1D(sfh_corr.corr_snap_snap); + free_RGTable1D(sfh_corr.corr_10_curr); + free_RGTable1D(sfh_corr.corr_100_curr); + free_RGTable1D(sfh_corr.corr_curr_curr); + free_RGTable1D(sfh_corr.corr_10_prev); + free_RGTable1D(sfh_corr.corr_100_prev); + free_RGTable1D(sfh_corr.corr_curr_prev); + free_RGTable1D(sfh_corr.corr_prev_prev); } // It's often useful to create a copy of scaling constants without F_ESC From 570312856e97b34c11c4b7d733207cbbb6939c38 Mon Sep 17 00:00:00 2001 From: James Davies Date: Wed, 22 Oct 2025 13:28:00 +1100 Subject: [PATCH 03/18] micro-optimisation --- src/py21cmfast/src/scaling_relations.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/py21cmfast/src/scaling_relations.c b/src/py21cmfast/src/scaling_relations.c index 7dcb76b69..40e076c1e 100644 --- a/src/py21cmfast/src/scaling_relations.c +++ b/src/py21cmfast/src/scaling_relations.c @@ -360,12 +360,12 @@ void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean // Compute the conditional covariance matrix Cov(curr|prev) = Cov(curr) - Cov(curr,prev) // Cov(prev)^-1 Cov(prev,curr) gsl_matrix_memcpy(matrix_buf, cross_cov); - // Cov(curr,prev) L^T^-1 - gsl_blas_dtrsm(CblasRight, CblasLower, CblasTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); + // L^-1 Cov(curr,prev) + gsl_blas_dtrsm(CblasLeft, CblasLower, CblasNoTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); // compute Cov(curr) - BUF*BUF^T == Cov(curr) - Cov(curr|prev) Cov(prev)^-1 Cov(prev,curr) // NOTE, curr_cov is symmetric here - gsl_blas_dsyrk(CblasLower, CblasNoTrans, -1.0, matrix_buf, 1.0, curr_cov); + gsl_blas_dsyrk(CblasLower, CblasTrans, -1.0, matrix_buf, 1.0, curr_cov); // The lower triangle of curr_cov now holds Cov(curr|prev) // Perform Cholesky decomposition (only uses lower triangle) @@ -374,6 +374,7 @@ void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean // Now Compute the mean correction term, since BUF was preserved from the rank-k above // Compute Cov(prev,curr) L^-T^-1 L^-1 = Cov(curr,prev) Cov(prev)^-1 + gsl_matrix_transpose(matrix_buf); gsl_blas_dtrsm(CblasRight, CblasLower, CblasNoTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); // Since, for zero mean, E[X|Y] = Cov(X,Y) Cov(Y)^-1 Y From 40b20f1894991deba86386b7a025b216a72d05aa Mon Sep 17 00:00:00 2001 From: James Davies Date: Wed, 22 Oct 2025 14:57:25 +1100 Subject: [PATCH 04/18] move sfh functions to new file --- src/py21cmfast/src/correlated_sfh.c | 306 +++++++++++++++++++++++++ src/py21cmfast/src/correlated_sfh.h | 11 + src/py21cmfast/src/scaling_relations.c | 294 ------------------------ 3 files changed, 317 insertions(+), 294 deletions(-) create mode 100644 src/py21cmfast/src/correlated_sfh.c create mode 100644 src/py21cmfast/src/correlated_sfh.h diff --git a/src/py21cmfast/src/correlated_sfh.c b/src/py21cmfast/src/correlated_sfh.c new file mode 100644 index 000000000..964e9a1fe --- /dev/null +++ b/src/py21cmfast/src/correlated_sfh.c @@ -0,0 +1,306 @@ +// This file contains the code to generate and evaluate the correlated +// star formation histories (SFH). + +#include "correlated_sfh.h" + +#include +#include +#include +#include +#include + +#include "Constants.h" +#include "InputParameters.h" +#include "interpolation.h" + +#define MAX_TAU (double)(100) // Myr +#define N_TAU_SFH (double)(1000) // number of time bins in SFH +#define N_FREQ_SFH (int)(N_TAU_SFH / 2 + 1) // number of frequency bins in SFH + +// We want a singleton struct which holds the SFH correlation functions +// We need the crosses between four timescales: 10 Myr, 100 Myr, snapshot interval, previous +// snapshot interval +typedef struct SFH_Correlation { + RGTable1D *corr_10_10; + RGTable1D *corr_10_100; + RGTable1D *corr_100_100; + RGTable1D *corr_10_curr; + RGTable1D *corr_100_curr; + RGTable1D *corr_curr_curr; + RGTable1D *corr_10_prev; + RGTable1D *corr_100_prev; + RGTable1D *corr_curr_prev; + RGTable1D *corr_prev_prev; +} SFH_Correlation; + +static SFH_Correlation sfh_corr; + +// Make a union type for easy initialisation +typedef union sfh_c_u { + SFH_Correlation sfh_c_s; + RGTable1D *sfh_c_a[10]; +} sfh_c_u; + +// Carvajal-Bohorquez et al. 2025 form +// Normalised to 1 for later multiplication +double psd_sfh_powerlaw(double w) { + return 1.0 / (1 + pow(w * astro_params_global->tau_SFH, astro_params_global->b_SFH)); +} + +fftwf_complex shifted_tophat_1d(double wt) { + // fourier transform of a real space tophat shift in the positive direction by R/2 + // We use this in the SFH model to get SFR between now and R Myr ago + fftwf_complex result; + if (wt < 1e-4) + return 1.0 - 0.5 * I * wt - wt * wt / 6.0; // second order taylor expansion around kR==0 + return I / wt * (exp(-I * wt) - 1.); +} + +void initialise_sfh_correlation(double z, double z_prev, double z_prev_2) { + sfh_c_u sfh_corr_u; + RGTable1D *table_ptr; + sfh_corr_u.sfh_c_s = sfh_corr; + + for (int i = 0; i < 9; i++) { + table_ptr = sfh_corr_u.sfh_c_a[i]; + allocate_RGTable1D(N_FREQ_SFH, table_ptr); + table_ptr->x_min = 0.; + table_ptr->x_width = MAX_TAU / (N_TAU_SFH - 1); + table_ptr->n_bin = N_TAU_SFH; + } + + double w_arr[N_FREQ_SFH]; + for (int i = 0; i < N_FREQ_SFH; i++) { + w_arr[i] = 2 * M_PI * i / MAX_TAU; + } + + double t_snap = time_between_z(z, z_prev) / (physconst.s_per_yr * 1e6); // Myr + double t_snap_prev = time_between_z(z_prev, z_prev_2) / (physconst.s_per_yr * 1e6); // Myr + + // determine the SFH correlation functions + fftwf_complex W_10[N_FREQ_SFH], W_100[N_FREQ_SFH], W_curr[N_FREQ_SFH], W_prev[N_FREQ_SFH]; + fftwf_complex psd_unfiltered[N_FREQ_SFH], psd_filtered[N_FREQ_SFH]; + + fftwf_complex *in; + float *out; + in = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * N_FREQ_SFH); + out = (float *)calloc(N_FREQ_SFH, sizeof(float)); + fftwf_plan p = fftwf_plan_dft_c2r_1d(N_TAU_SFH, in, out, FFTW_ESTIMATE); + + // get the filters + for (int i = 0; i < N_FREQ_SFH; i++) { + psd_unfiltered[i] = psd_sfh_powerlaw(w_arr[i]); + W_10[i] = shifted_tophat_1d(w_arr[i] * 10.); + W_100[i] = shifted_tophat_1d(w_arr[i] * 100.); + W_curr[i] = shifted_tophat_1d(w_arr[i] * t_snap); + W_prev[i] = shifted_tophat_1d(w_arr[i] * t_snap_prev); + } + + // TODO: check that there N_TAU_SFH normalization on the iFFT + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_10[i] * conj(W_10[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_10_10->y_arr[i] = out[i] / N_TAU_SFH; + } + + // 10Myr X 100Myr + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_100[i] * conj(W_10[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_10_100->y_arr[i] = out[i] / N_TAU_SFH; + } + + // 100Myr X 100Myr + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_100[i] * conj(W_100[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_100_100->y_arr[i] = out[i] / N_TAU_SFH; + } + + // 10Myr X Current snapshot length + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_10[i] * conj(W_curr[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_10_curr->y_arr[i] = out[i] / N_TAU_SFH; + } + + // 100Myr X Current snapshot length + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_100[i] * conj(W_curr[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_100_curr->y_arr[i] = out[i] / N_TAU_SFH; + } + + // Current snapshot length X Current snapshot length + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_curr[i] * conj(W_curr[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_curr_curr->y_arr[i] = out[i] / N_TAU_SFH; + } + + // 10Myr X Previous snapshot length + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_10[i] * conj(W_prev[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_10_prev->y_arr[i] = out[i] / N_TAU_SFH; + } + + // 100Myr X Previous snapshot length + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_100[i] * conj(W_prev[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_100_prev->y_arr[i] = out[i] / N_TAU_SFH; + } + + // Current snapshot length X Previous snapshot length + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_curr[i] * conj(W_prev[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_curr_prev->y_arr[i] = out[i] / N_TAU_SFH; + } + + // Previous snapshot length X Previous snapshot length + for (int i = 0; i < N_FREQ_SFH; i++) { + in[i] = psd_unfiltered[i] * W_prev[i] * conj(W_prev[i]); + } + fftwf_execute(p); + for (int i = 0; i < N_TAU_SFH; i++) { + sfh_corr.corr_prev_prev->y_arr[i] = out[i] / N_TAU_SFH; + } + + fftwf_destroy_plan(p); + fftwf_free(in); + fftwf_free(out); +} + +void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean_correction) { + /* Evaluate the SFH covariance matrix at a given time step tau + + Outputs are the two matrices required for sampling the SFR correctly + + out_chol_cov = Cholesky factor of Cov(curr|prev), multiplies standard normal vector + to get correlated & conditioned SFRs. NOTE: The upper triangle is garbage + + out_mean_correction = multiplies the condition vector, to be added to the correlated samples. + */ + + // Interpolate correlation functions at lag tau + double cov_10_10_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_10); + double cov_10_100_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_100); + double cov_10_curr_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_curr); + double cov_10_prev_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_prev); + double cov_100_100_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_100); + double cov_100_curr_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_curr); + double cov_100_prev_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_prev); + double cov_curr_prev_tau = EvaluateRGTable1D(tau, sfh_corr.corr_curr_prev); + + // Get zero-lag correlations + double cov_10_10_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_10); + double cov_10_100_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_100); + double cov_10_curr_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_curr); + double cov_10_prev_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_prev); + double cov_100_100_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_100); + double cov_100_curr_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_curr); + double cov_100_prev_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_prev); + double cov_curr_curr_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_curr_curr); + double cov_prev_prev_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_prev_prev); + + // Previous Snapshot covariance matrix + gsl_matrix *prev_cov = gsl_matrix_alloc(3, 3); + gsl_matrix_set(prev_cov, 0, 0, cov_10_10_zero); // 10_prev vs 10_prev + gsl_matrix_set(prev_cov, 0, 1, cov_10_100_zero); // 10_prev vs 100_prev + gsl_matrix_set(prev_cov, 0, 2, cov_10_prev_zero); // 10_prev vs snap_prev + gsl_matrix_set(prev_cov, 1, 0, cov_10_100_zero); // 100_prev vs 10_prev + gsl_matrix_set(prev_cov, 1, 1, cov_100_100_zero); // 100_prev vs 100_prev + gsl_matrix_set(prev_cov, 1, 2, cov_100_prev_zero); // 100_prev vs snap_prev + gsl_matrix_set(prev_cov, 2, 0, cov_10_prev_zero); // snap_prev vs 10_prev + gsl_matrix_set(prev_cov, 2, 1, cov_100_prev_zero); // snap_prev vs 100_prev + gsl_matrix_set(prev_cov, 2, 2, cov_prev_prev_zero); // snap_prev vs snap_prev + + // Lower left corner Covariance is Cov(curr,prev) == Cov(prev,curr)^T + gsl_matrix *cross_cov = gsl_matrix_alloc(3, 3); + gsl_matrix_set(cross_cov, 0, 0, cov_10_10_tau); // 10_prev vs 10_curr + gsl_matrix_set(cross_cov, 0, 1, cov_10_100_tau); // 10_prev vs 100_curr + gsl_matrix_set(cross_cov, 0, 2, cov_10_prev_tau); // 10_prev vs snap_curr + gsl_matrix_set(cross_cov, 1, 0, cov_10_100_tau); // 100_prev vs 10_curr + gsl_matrix_set(cross_cov, 1, 1, cov_100_100_tau); // 100_prev vs 100_curr + gsl_matrix_set(cross_cov, 1, 2, cov_100_prev_tau); // 100_prev vs snap_curr + gsl_matrix_set(cross_cov, 2, 0, cov_10_curr_tau); // snap_prev vs 10_curr + gsl_matrix_set(cross_cov, 2, 1, cov_100_curr_tau); // snap_prev vs 100_curr + gsl_matrix_set(cross_cov, 2, 2, cov_curr_prev_tau); // snap_prev vs snap_curr + + // Current Snapshot covariance matrix + gsl_matrix *curr_cov = gsl_matrix_alloc(3, 3); + // NOTE: Since the snapshot lengths are different, the snap variances are different + gsl_matrix_memcpy(curr_cov, prev_cov); + gsl_matrix_set(curr_cov, 0, 2, cov_10_curr_zero); // 10_curr vs 10_curr + gsl_matrix_set(curr_cov, 1, 2, cov_100_curr_zero); // 100_curr vs 100_curr + gsl_matrix_set(curr_cov, 2, 0, cov_10_curr_zero); // 10_curr vs 10_curr + gsl_matrix_set(curr_cov, 2, 1, cov_100_curr_zero); // 100_curr vs 100_curr + gsl_matrix_set(curr_cov, 2, 2, cov_curr_curr_zero); // snap_curr vs snap_curr + + // NOTE: Currently Cov(curr,prev) == Cov(prev,curr) == Cov(prev,curr)^T == Cov(curr,prev)^T + gsl_matrix *matrix_buf = gsl_matrix_alloc(3, 3); + + // Cholesky factorization of Cov(prev) = L L^T to do implicit inversion + gsl_linalg_cholesky_decomp1(prev_cov); // holds L + + // Compute the conditional covariance matrix Cov(curr|prev) = Cov(curr) - Cov(curr,prev) + // Cov(prev)^-1 Cov(prev,curr) + gsl_matrix_memcpy(matrix_buf, cross_cov); + // L^-1 Cov(curr,prev) + gsl_blas_dtrsm(CblasLeft, CblasLower, CblasNoTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); + + // compute Cov(curr) - BUF*BUF^T == Cov(curr) - Cov(curr|prev) Cov(prev)^-1 Cov(prev,curr) + // NOTE, curr_cov is symmetric here + gsl_blas_dsyrk(CblasLower, CblasTrans, -1.0, matrix_buf, 1.0, curr_cov); + // The lower triangle of curr_cov now holds Cov(curr|prev) + + // Perform Cholesky decomposition (only uses lower triangle) + gsl_linalg_cholesky_decomp1(curr_cov); + gsl_matrix_memcpy(out_chol_cov, curr_cov); + + // Now Compute the mean correction term, since BUF was preserved from the rank-k above + // Compute Cov(prev,curr) L^-T^-1 L^-1 = Cov(curr,prev) Cov(prev)^-1 + gsl_matrix_transpose(matrix_buf); + gsl_blas_dtrsm(CblasRight, CblasLower, CblasNoTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); + + // Since, for zero mean, E[X|Y] = Cov(X,Y) Cov(Y)^-1 Y + gsl_matrix_memcpy(out_mean_correction, matrix_buf); // store for output + + gsl_matrix_free(prev_cov); + gsl_matrix_free(curr_cov); + gsl_matrix_free(cross_cov); + gsl_matrix_free(matrix_buf); +} + +void free_sfh_correlation() { + free_RGTable1D(sfh_corr.corr_10_10); + free_RGTable1D(sfh_corr.corr_10_100); + free_RGTable1D(sfh_corr.corr_100_100); + free_RGTable1D(sfh_corr.corr_10_curr); + free_RGTable1D(sfh_corr.corr_100_curr); + free_RGTable1D(sfh_corr.corr_curr_curr); + free_RGTable1D(sfh_corr.corr_10_prev); + free_RGTable1D(sfh_corr.corr_100_prev); + free_RGTable1D(sfh_corr.corr_curr_prev); + free_RGTable1D(sfh_corr.corr_prev_prev); +} diff --git a/src/py21cmfast/src/correlated_sfh.h b/src/py21cmfast/src/correlated_sfh.h new file mode 100644 index 000000000..afaf47282 --- /dev/null +++ b/src/py21cmfast/src/correlated_sfh.h @@ -0,0 +1,11 @@ + +#include + +#ifndef CORRELATED_SFH_H +#define CORRELATED_SFH_H + +void initialise_sfh_correlation(double z, double z_prev, double z_prev_2); +void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean_correction); +void free_sfh_correlation(); + +#endif diff --git a/src/py21cmfast/src/scaling_relations.c b/src/py21cmfast/src/scaling_relations.c index 40e076c1e..e7dd288fa 100644 --- a/src/py21cmfast/src/scaling_relations.c +++ b/src/py21cmfast/src/scaling_relations.c @@ -4,8 +4,6 @@ #include #include -#include -#include #include #include #include @@ -107,298 +105,6 @@ void set_scaling_constants(double redshift, ScalingConstants *consts, bool use_p } } -// Carvajal-Bohorquez et al. 2025 form -// Normalised to 1 for later multiplication -double psd_sfh_powerlaw(double w) { - return 1.0 / (1 + pow(w * astro_params_global->tau_SFH, astro_params_global->b_SFH)); -} - -#define MAX_TAU (double)(100) // Myr -#define N_TAU_SFH (double)(1000) // number of time bins in SFH -#define N_FREQ_SFH (int)(N_TAU_SFH / 2 + 1) // number of frequency bins in SFH - -// We want a singleton struct which holds the SFH correlation functions -// We need the crosses between four timescales: 10 Myr, 100 Myr, snapshot interval, previous -// snapshot interval -typedef struct SFH_Correlation { - RGTable1D *corr_10_10; - RGTable1D *corr_10_100; - RGTable1D *corr_100_100; - RGTable1D *corr_10_curr; - RGTable1D *corr_100_curr; - RGTable1D *corr_curr_curr; - RGTable1D *corr_10_prev; - RGTable1D *corr_100_prev; - RGTable1D *corr_curr_prev; - RGTable1D *corr_prev_prev; -} SFH_Correlation; - -static SFH_Correlation sfh_corr; - -// Make a union type for easy initialisation -typedef union sfh_c_u { - SFH_Correlation sfh_c_s; - RGTable1D *sfh_c_a[10]; -} sfh_c_u; - -fftwf_complex shifted_tophat_1d(double wt) { - // fourier transform of a real space tophat shift in the positive direction by R/2 - // We use this in the SFH model to get SFR between now and R Myr ago - fftwf_complex result; - if (wt < 1e-4) - return 1.0 - 0.5 * I * wt - wt * wt / 6.0; // second order taylor expansion around kR==0 - return I / wt * (exp(-I * wt) - 1.); -} - -void initialise_sfh_correlation(double z, double z_prev, double z_prev_2) { - sfh_c_u sfh_corr_u; - RGTable1D *table_ptr; - sfh_corr_u.sfh_c_s = sfh_corr; - - for (int i = 0; i < 9; i++) { - table_ptr = sfh_corr_u.sfh_c_a[i]; - allocate_RGTable1D(N_FREQ_SFH, table_ptr); - table_ptr->x_min = 0.; - table_ptr->x_width = MAX_TAU / (N_TAU_SFH - 1); - table_ptr->n_bin = N_TAU_SFH; - } - - double w_arr[N_FREQ_SFH]; - for (int i = 0; i < N_FREQ_SFH; i++) { - w_arr[i] = 2 * M_PI * i / MAX_TAU; - } - - double t_snap = time_between_z(z, z_prev) / (physconst.s_per_yr * 1e6); // Myr - double t_snap_prev = time_between_z(z_prev, z_prev_2) / (physconst.s_per_yr * 1e6); // Myr - - // determine the SFH correlation functions - fftwf_complex W_10[N_FREQ_SFH], W_100[N_FREQ_SFH], W_curr[N_FREQ_SFH], W_prev[N_FREQ_SFH]; - fftwf_complex psd_unfiltered[N_FREQ_SFH], psd_filtered[N_FREQ_SFH]; - - fftwf_complex *in; - float *out; - in = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * N_FREQ_SFH); - out = (float *)calloc(N_FREQ_SFH, sizeof(float)); - fftwf_plan p = fftwf_plan_dft_c2r_1d(N_TAU_SFH, in, out, FFTW_ESTIMATE); - - // get the filters - for (int i = 0; i < N_FREQ_SFH; i++) { - psd_unfiltered[i] = psd_sfh_powerlaw(w_arr[i]); - W_10[i] = shifted_tophat_1d(w_arr[i] * 10.); - W_100[i] = shifted_tophat_1d(w_arr[i] * 100.); - W_curr[i] = shifted_tophat_1d(w_arr[i] * t_snap); - W_prev[i] = shifted_tophat_1d(w_arr[i] * t_snap_prev); - } - - // TODO: check that there N_TAU_SFH normalization on the iFFT - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_10[i] * conj(W_10[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_10_10->y_arr[i] = out[i] / N_TAU_SFH; - } - - // 10Myr X 100Myr - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_100[i] * conj(W_10[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_10_100->y_arr[i] = out[i] / N_TAU_SFH; - } - - // 100Myr X 100Myr - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_100[i] * conj(W_100[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_100_100->y_arr[i] = out[i] / N_TAU_SFH; - } - - // 10Myr X Current snapshot length - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_10[i] * conj(W_curr[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_10_curr->y_arr[i] = out[i] / N_TAU_SFH; - } - - // 100Myr X Current snapshot length - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_100[i] * conj(W_curr[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_100_curr->y_arr[i] = out[i] / N_TAU_SFH; - } - - // Current snapshot length X Current snapshot length - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_curr[i] * conj(W_curr[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_curr_curr->y_arr[i] = out[i] / N_TAU_SFH; - } - - // 10Myr X Previous snapshot length - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_10[i] * conj(W_prev[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_10_prev->y_arr[i] = out[i] / N_TAU_SFH; - } - - // 100Myr X Previous snapshot length - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_100[i] * conj(W_prev[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_100_prev->y_arr[i] = out[i] / N_TAU_SFH; - } - - // Current snapshot length X Previous snapshot length - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_curr[i] * conj(W_prev[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_curr_prev->y_arr[i] = out[i] / N_TAU_SFH; - } - - // Previous snapshot length X Previous snapshot length - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_prev[i] * conj(W_prev[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_prev_prev->y_arr[i] = out[i] / N_TAU_SFH; - } - - fftwf_destroy_plan(p); - fftwf_free(in); - fftwf_free(out); -} - -void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean_correction) { - /* Evaluate the SFH covariance matrix at a given time step tau - - Outputs are the two matrices required for sampling the SFR correctly - - out_chol_cov = Cholesky factor of Cov(curr|prev), multiplies standard normal vector - to get correlated & conditioned SFRs. NOTE: The upper triangle is - - out_mean_correction = multiplies the condition vector, to be added to the correlated samples. - */ - - // Interpolate correlation functions at lag tau - double cov_10_10_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_10); - double cov_10_100_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_100); - double cov_10_curr_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_curr); - double cov_10_prev_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_prev); - double cov_100_100_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_100); - double cov_100_curr_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_curr); - double cov_100_prev_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_prev); - double cov_curr_prev_tau = EvaluateRGTable1D(tau, sfh_corr.corr_curr_prev); - - // Get zero-lag correlations - double cov_10_10_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_10); - double cov_10_100_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_100); - double cov_10_curr_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_curr); - double cov_10_prev_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_prev); - double cov_100_100_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_100); - double cov_100_curr_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_curr); - double cov_100_prev_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_prev); - double cov_curr_curr_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_curr_curr); - double cov_prev_prev_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_prev_prev); - - // Previous Snapshot covariance matrix - gsl_matrix *prev_cov = gsl_matrix_alloc(3, 3); - gsl_matrix_set(prev_cov, 0, 0, cov_10_10_zero); // 10_prev vs 10_prev - gsl_matrix_set(prev_cov, 0, 1, cov_10_100_zero); // 10_prev vs 100_prev - gsl_matrix_set(prev_cov, 0, 2, cov_10_prev_zero); // 10_prev vs snap_prev - gsl_matrix_set(prev_cov, 1, 0, cov_10_100_zero); // 100_prev vs 10_prev - gsl_matrix_set(prev_cov, 1, 1, cov_100_100_zero); // 100_prev vs 100_prev - gsl_matrix_set(prev_cov, 1, 2, cov_100_prev_zero); // 100_prev vs snap_prev - gsl_matrix_set(prev_cov, 2, 0, cov_10_prev_zero); // snap_prev vs 10_prev - gsl_matrix_set(prev_cov, 2, 1, cov_100_prev_zero); // snap_prev vs 100_prev - gsl_matrix_set(prev_cov, 2, 2, cov_prev_prev_zero); // snap_prev vs snap_prev - - // Lower left corner Covariance is Cov(curr,prev) == Cov(prev,curr)^T - gsl_matrix *cross_cov = gsl_matrix_alloc(3, 3); - gsl_matrix_set(cross_cov, 0, 0, cov_10_10_tau); // 10_prev vs 10_curr - gsl_matrix_set(cross_cov, 0, 1, cov_10_100_tau); // 10_prev vs 100_curr - gsl_matrix_set(cross_cov, 0, 2, cov_10_prev_tau); // 10_prev vs snap_curr - gsl_matrix_set(cross_cov, 1, 0, cov_10_100_tau); // 100_prev vs 10_curr - gsl_matrix_set(cross_cov, 1, 1, cov_100_100_tau); // 100_prev vs 100_curr - gsl_matrix_set(cross_cov, 1, 2, cov_100_prev_tau); // 100_prev vs snap_curr - gsl_matrix_set(cross_cov, 2, 0, cov_10_curr_tau); // snap_prev vs 10_curr - gsl_matrix_set(cross_cov, 2, 1, cov_100_curr_tau); // snap_prev vs 100_curr - gsl_matrix_set(cross_cov, 2, 2, cov_curr_prev_tau); // snap_prev vs snap_curr - - // Current Snapshot covariance matrix - gsl_matrix *curr_cov = gsl_matrix_alloc(3, 3); - // NOTE: Since the snapshot lengths are different, the snap variances are different - gsl_matrix_memcpy(curr_cov, prev_cov); - gsl_matrix_set(curr_cov, 0, 2, cov_10_curr_zero); // 10_curr vs 10_curr - gsl_matrix_set(curr_cov, 1, 2, cov_100_curr_zero); // 100_curr vs 100_curr - gsl_matrix_set(curr_cov, 2, 0, cov_10_curr_zero); // 10_curr vs 10_curr - gsl_matrix_set(curr_cov, 2, 1, cov_100_curr_zero); // 100_curr vs 100_curr - gsl_matrix_set(curr_cov, 2, 2, cov_curr_curr_zero); // snap_curr vs snap_curr - - // NOTE: Currently Cov(curr,prev) == Cov(prev,curr) == Cov(prev,curr)^T == Cov(curr,prev)^T - gsl_matrix *matrix_buf = gsl_matrix_alloc(3, 3); - - // Cholesky factorization of Cov(prev) = L L^T to do implicit inversion - gsl_linalg_cholesky_decomp1(prev_cov); // holds L - - // Compute the conditional covariance matrix Cov(curr|prev) = Cov(curr) - Cov(curr,prev) - // Cov(prev)^-1 Cov(prev,curr) - gsl_matrix_memcpy(matrix_buf, cross_cov); - // L^-1 Cov(curr,prev) - gsl_blas_dtrsm(CblasLeft, CblasLower, CblasNoTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); - - // compute Cov(curr) - BUF*BUF^T == Cov(curr) - Cov(curr|prev) Cov(prev)^-1 Cov(prev,curr) - // NOTE, curr_cov is symmetric here - gsl_blas_dsyrk(CblasLower, CblasTrans, -1.0, matrix_buf, 1.0, curr_cov); - // The lower triangle of curr_cov now holds Cov(curr|prev) - - // Perform Cholesky decomposition (only uses lower triangle) - gsl_linalg_cholesky_decomp1(curr_cov); - gsl_matrix_memcpy(out_chol_cov, curr_cov); - - // Now Compute the mean correction term, since BUF was preserved from the rank-k above - // Compute Cov(prev,curr) L^-T^-1 L^-1 = Cov(curr,prev) Cov(prev)^-1 - gsl_matrix_transpose(matrix_buf); - gsl_blas_dtrsm(CblasRight, CblasLower, CblasNoTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); - - // Since, for zero mean, E[X|Y] = Cov(X,Y) Cov(Y)^-1 Y - gsl_matrix_memcpy(out_mean_correction, matrix_buf); // store for output - - gsl_matrix_free(prev_cov); - gsl_matrix_free(curr_cov); - gsl_matrix_free(cross_cov); - gsl_matrix_free(matrix_buf); -} - -void free_sfh_correlation() { - free_RGTable1D(sfh_corr.corr_10_10); - free_RGTable1D(sfh_corr.corr_10_100); - free_RGTable1D(sfh_corr.corr_100_100); - free_RGTable1D(sfh_corr.corr_10_curr); - free_RGTable1D(sfh_corr.corr_100_curr); - free_RGTable1D(sfh_corr.corr_curr_curr); - free_RGTable1D(sfh_corr.corr_10_prev); - free_RGTable1D(sfh_corr.corr_100_prev); - free_RGTable1D(sfh_corr.corr_curr_prev); - free_RGTable1D(sfh_corr.corr_prev_prev); -} - // It's often useful to create a copy of scaling constants without F_ESC ScalingConstants evolve_scaling_constants_sfr(ScalingConstants *sc) { ScalingConstants sc_sfrd = *sc; From 5c8d9f2a7c6e73b2cf414954f31df2185dea8af3 Mon Sep 17 00:00:00 2001 From: James Davies Date: Wed, 22 Oct 2025 15:41:47 +1100 Subject: [PATCH 05/18] fix some comments --- src/py21cmfast/src/correlated_sfh.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/py21cmfast/src/correlated_sfh.c b/src/py21cmfast/src/correlated_sfh.c index 964e9a1fe..19fe57ba2 100644 --- a/src/py21cmfast/src/correlated_sfh.c +++ b/src/py21cmfast/src/correlated_sfh.c @@ -47,6 +47,7 @@ double psd_sfh_powerlaw(double w) { return 1.0 / (1 + pow(w * astro_params_global->tau_SFH, astro_params_global->b_SFH)); } +// NOTE: Since two filters can be shifted by different amounts we keep it general here fftwf_complex shifted_tophat_1d(double wt) { // fourier transform of a real space tophat shift in the positive direction by R/2 // We use this in the SFH model to get SFR between now and R Myr ago @@ -269,7 +270,7 @@ void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean // L^-1 Cov(curr,prev) gsl_blas_dtrsm(CblasLeft, CblasLower, CblasNoTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); - // compute Cov(curr) - BUF*BUF^T == Cov(curr) - Cov(curr|prev) Cov(prev)^-1 Cov(prev,curr) + // compute Cov(curr) - BUF^T*BUF == Cov(curr) - Cov(curr|prev) Cov(prev)^-1 Cov(prev,curr) // NOTE, curr_cov is symmetric here gsl_blas_dsyrk(CblasLower, CblasTrans, -1.0, matrix_buf, 1.0, curr_cov); // The lower triangle of curr_cov now holds Cov(curr|prev) From 6ebfb30bfa5e7daeec3e1ab0d3687b1a19b48f84 Mon Sep 17 00:00:00 2001 From: James Davies Date: Wed, 22 Oct 2025 16:54:30 +1100 Subject: [PATCH 06/18] add the sampling function --- src/py21cmfast/src/correlated_sfh.c | 40 +++++++++++++++++++++++++++++ src/py21cmfast/src/correlated_sfh.h | 2 ++ 2 files changed, 42 insertions(+) diff --git a/src/py21cmfast/src/correlated_sfh.c b/src/py21cmfast/src/correlated_sfh.c index 19fe57ba2..8df21b823 100644 --- a/src/py21cmfast/src/correlated_sfh.c +++ b/src/py21cmfast/src/correlated_sfh.c @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include "Constants.h" @@ -293,6 +295,44 @@ void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean gsl_matrix_free(matrix_buf); } +void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], gsl_matrix *L_cov, + gsl_matrix *mean_corr, double out_values[3]) { + /* Sample correlated SFH values given previous values + + Inputs: + prev_values: array of previous SFR values [SFR_10Myr, SFR_100Myr, SFR_snapshot_prev] + L_cov: Cholesky factor of Cov(curr|prev), from eval_sfh_moments, L L^T = Cov(curr|prev) + mean_corr: Mean correction matrix from eval_sfh_moments + + Outputs: + out_values: array of sampled current SFR values [SFR_10Myr, SFR_100Myr, SFR_snapshot_curr] + */ + + // Generate standard normal random variables + gsl_vector *cov_term = gsl_vector_alloc(3); + for (int i = 0; i < 3; i++) { + gsl_vector_set(cov_term, i, gsl_ran_ugaussian(rng)); + } + + // Multiply by Cholesky factor to get correlated samples + gsl_blas_dtrmv(CblasLower, CblasNoTrans, CblasNonUnit, L_cov, cov_term); + + // Create a vector for the conditioned samples + gsl_vector *cond_term = gsl_vector_alloc(3); + for (int i = 0; i < 3; i++) { + gsl_vector_set(cond_term, i, prev_values[i]); + } + // Add the mean correction term + gsl_blas_dgemv(CblasNoTrans, 1.0, mean_corr, cond_term, 1.0, cov_term); + + // Copy to output + for (int i = 0; i < 3; i++) { + out_values[i] = gsl_vector_get(cov_term, i); + } + gsl_vector_free(cov_term); + gsl_vector_free(cond_term); +} + void free_sfh_correlation() { free_RGTable1D(sfh_corr.corr_10_10); free_RGTable1D(sfh_corr.corr_10_100); diff --git a/src/py21cmfast/src/correlated_sfh.h b/src/py21cmfast/src/correlated_sfh.h index afaf47282..f7bb2e323 100644 --- a/src/py21cmfast/src/correlated_sfh.h +++ b/src/py21cmfast/src/correlated_sfh.h @@ -6,6 +6,8 @@ void initialise_sfh_correlation(double z, double z_prev, double z_prev_2); void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean_correction); +void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], gsl_matrix *L_cov, + gsl_matrix *mean_corr, double out_values[3]); void free_sfh_correlation(); #endif From 83431854244f5da2459b9d642c1ac680b1f07462 Mon Sep 17 00:00:00 2001 From: James Davies Date: Thu, 23 Oct 2025 10:21:03 +1100 Subject: [PATCH 07/18] add parameters --- src/py21cmfast/src/_inputparams_wrapper.h | 4 ++++ src/py21cmfast/src/correlated_sfh.c | 6 ++++-- src/py21cmfast/wrapper/inputs.py | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/py21cmfast/src/_inputparams_wrapper.h b/src/py21cmfast/src/_inputparams_wrapper.h index 89b20fb28..8ca7e353f 100644 --- a/src/py21cmfast/src/_inputparams_wrapper.h +++ b/src/py21cmfast/src/_inputparams_wrapper.h @@ -75,6 +75,10 @@ typedef struct MatterOptions { typedef struct AstroParams { float HII_EFF_FACTOR; + // SFH + double SFH_TAU; + double SFH_INDEX; + // SHMR float F_STAR10; float ALPHA_STAR; diff --git a/src/py21cmfast/src/correlated_sfh.c b/src/py21cmfast/src/correlated_sfh.c index 8df21b823..a51cd81b6 100644 --- a/src/py21cmfast/src/correlated_sfh.c +++ b/src/py21cmfast/src/correlated_sfh.c @@ -46,10 +46,12 @@ typedef union sfh_c_u { // Carvajal-Bohorquez et al. 2025 form // Normalised to 1 for later multiplication double psd_sfh_powerlaw(double w) { - return 1.0 / (1 + pow(w * astro_params_global->tau_SFH, astro_params_global->b_SFH)); + return 1.0 / (1 + pow(w * astro_params_global->SFH_TAU, astro_params_global->SFH_INDEX)); } -// NOTE: Since two filters can be shifted by different amounts we keep it general here +// NOTE: This only differs from a tophat in phase. While we only use filter squared in the +// correlation functions, Since two filters can be shifted by different amounts (due to width) +// we keep the shifting fftwf_complex shifted_tophat_1d(double wt) { // fourier transform of a real space tophat shift in the positive direction by R/2 // We use this in the SFH model to get SFR between now and R Myr ago diff --git a/src/py21cmfast/wrapper/inputs.py b/src/py21cmfast/wrapper/inputs.py index e98106410..9bcda07f9 100644 --- a/src/py21cmfast/wrapper/inputs.py +++ b/src/py21cmfast/wrapper/inputs.py @@ -1152,6 +1152,10 @@ class AstroParams(InputStruct): The maximum frequency of the X-ray band used to calculate the X-ray Luminosity. NU_X_MAX: float, optional The maximum frequency of the integrals over nu for the x-ray heating/ionisation rates. + SFH_TAU: float, optional + The time-scale in Myr of at which the PSD of star formation rate fluctuations decay. + SFH_INDEX: float, optional + The power-law index of the PSD of star formation rate fluctuations. """ HII_EFF_FACTOR: float = field( @@ -1265,6 +1269,10 @@ class AstroParams(InputStruct): default=10000.0, converter=float, validator=validators.gt(0) ) + # TODO: default values pending, taken from the CIGALE / Carvahal-Bohorquez et al. 2025 + SFH_TAU: float = field(default=150.0, converter=float, validator=validators.gt(0)) + SFH_INDEX: float = field(default=2.0, converter=float) + # set the default of the minihalo scalings to continue the same PL @F_STAR7_MINI.default def _F_STAR7_MINI_default(self): From 65c766290f6ff5b8f2c97493a7b8b168b093e992 Mon Sep 17 00:00:00 2001 From: James Davies Date: Tue, 28 Oct 2025 11:45:06 +1100 Subject: [PATCH 08/18] correlation functions match between methods --- .../src/_functionprototypes_wrapper.h | 1 + src/py21cmfast/src/correlated_sfh.c | 508 ++++++++++++------ src/py21cmfast/src/correlated_sfh.h | 10 +- src/py21cmfast/src/cosmology.c | 7 +- src/py21cmfast/src/cosmology.h | 2 +- src/py21cmfast/wrapper/cfuncs.py | 28 + 6 files changed, 371 insertions(+), 185 deletions(-) diff --git a/src/py21cmfast/src/_functionprototypes_wrapper.h b/src/py21cmfast/src/_functionprototypes_wrapper.h index fda88407d..cd091adfc 100644 --- a/src/py21cmfast/src/_functionprototypes_wrapper.h +++ b/src/py21cmfast/src/_functionprototypes_wrapper.h @@ -117,6 +117,7 @@ int test_halo_props(double redshift, float *vcb_grid, float *J21_LW_grid, float float *Gamma12_ion_grid, int n_halos, float *halo_masses, float *halo_coords, float *star_rng, float *sfr_rnd, float *xray_rng, float *halo_props_out); int test_filter(float *input_box, double R, double R_param, int filter_flag, double *result); +int test_sfh_corr(double z0, double z1, double z2); /* Functions required to access cosmology & mass functions directly */ double dicke(double z); diff --git a/src/py21cmfast/src/correlated_sfh.c b/src/py21cmfast/src/correlated_sfh.c index a51cd81b6..5b5b91966 100644 --- a/src/py21cmfast/src/correlated_sfh.c +++ b/src/py21cmfast/src/correlated_sfh.c @@ -7,229 +7,277 @@ #include #include #include +#include #include #include #include #include "Constants.h" #include "InputParameters.h" +#include "cosmology.h" +#include "exceptions.h" #include "interpolation.h" +#include "logger.h" -#define MAX_TAU (double)(100) // Myr -#define N_TAU_SFH (double)(1000) // number of time bins in SFH +#define MAX_TAU (double)(2000) // Myr +#define N_TAU_SFH (int)(10000) // number of time bins in SFH #define N_FREQ_SFH (int)(N_TAU_SFH / 2 + 1) // number of frequency bins in SFH // We want a singleton struct which holds the SFH correlation functions // We need the crosses between four timescales: 10 Myr, 100 Myr, snapshot interval, previous // snapshot interval typedef struct SFH_Correlation { - RGTable1D *corr_10_10; - RGTable1D *corr_10_100; - RGTable1D *corr_100_100; - RGTable1D *corr_10_curr; - RGTable1D *corr_100_curr; - RGTable1D *corr_curr_curr; - RGTable1D *corr_10_prev; - RGTable1D *corr_100_prev; - RGTable1D *corr_curr_prev; - RGTable1D *corr_prev_prev; + RGTable1D corr_10_10; + RGTable1D corr_10_100; + RGTable1D corr_100_100; + RGTable1D corr_10_curr; + RGTable1D corr_100_curr; + RGTable1D corr_curr_curr; + RGTable1D corr_10_prev; + RGTable1D corr_100_prev; + RGTable1D corr_curr_prev; + RGTable1D corr_prev_prev; + RGTable1D corr_zero; // debug table } SFH_Correlation; static SFH_Correlation sfh_corr; -// Make a union type for easy initialisation -typedef union sfh_c_u { - SFH_Correlation sfh_c_s; - RGTable1D *sfh_c_a[10]; -} sfh_c_u; - // Carvajal-Bohorquez et al. 2025 form -// Normalised to 1 for later multiplication double psd_sfh_powerlaw(double w) { - return 1.0 / (1 + pow(w * astro_params_global->SFH_TAU, astro_params_global->SFH_INDEX)); + // re-using SIGMA_STAR here for the variance normalisation + // (FFT of un-smoothed auto-variance at zero lag) + // Our normalisation is such that integral of PSD over all frequencies = variance + double norm = astro_params_global->SIGMA_STAR * astro_params_global->SIGMA_STAR * + astro_params_global->SFH_TAU * 2.; + + // TODO: the x2 factor at the end is matching the expected normalisation, but my derivation + // gives sqrt(2/pi) instead. Need to double check. the difference of sqrt(2pi) is likely + // a fourier convention thing but I need to verify. + + return norm / (1 + pow(w * astro_params_global->SFH_TAU, astro_params_global->SFH_INDEX)); } -// NOTE: This only differs from a tophat in phase. While we only use filter squared in the -// correlation functions, Since two filters can be shifted by different amounts (due to width) -// we keep the shifting -fftwf_complex shifted_tophat_1d(double wt) { - // fourier transform of a real space tophat shift in the positive direction by R/2 - // We use this in the SFH model to get SFR between now and R Myr ago - fftwf_complex result; - if (wt < 1e-4) - return 1.0 - 0.5 * I * wt - wt * wt / 6.0; // second order taylor expansion around kR==0 - return I / wt * (exp(-I * wt) - 1.); +double integral_expfunc_pos(double s_min, double s_max, double t0, double A, double B) { + // Integral of (A + Bs)*exp(-s/t0) from s_min to s_max + // used to build the integral of (A + Bs)*exp(-|s|/t0) when s is positive + double exp_min = exp(-s_min / t0); + double exp_max = exp(-s_max / t0); + return t0 * (exp_min * (A + B * (s_min + t0)) - exp_max * (A + B * (s_max + t0))); } -void initialise_sfh_correlation(double z, double z_prev, double z_prev_2) { - sfh_c_u sfh_corr_u; - RGTable1D *table_ptr; - sfh_corr_u.sfh_c_s = sfh_corr; - - for (int i = 0; i < 9; i++) { - table_ptr = sfh_corr_u.sfh_c_a[i]; - allocate_RGTable1D(N_FREQ_SFH, table_ptr); - table_ptr->x_min = 0.; - table_ptr->x_width = MAX_TAU / (N_TAU_SFH - 1); - table_ptr->n_bin = N_TAU_SFH; - } +double integral_expfunc_neg(double s_min, double s_max, double t0, double A, double B) { + // Integral of (A + Bs)*exp(s/t0) from s_min to s_max + // used to build the integral of (A + Bs)*exp(-|s|/t0) when s is negative + double exp_min = exp(s_min / t0); + double exp_max = exp(s_max / t0); + return -t0 * (exp_min * (A + B * (s_min - t0)) - exp_max * (A + B * (s_max - t0))); +} - double w_arr[N_FREQ_SFH]; - for (int i = 0; i < N_FREQ_SFH; i++) { - w_arr[i] = 2 * M_PI * i / MAX_TAU; +double integral_expfunc_mod(double s_min, double s_max, double t0, double A, double B) { + // Integral of (A + Bs)*exp(-|s|/t0) from s_min to s_max + // used to build the integral of (A + Bs)*exp(-|s|/t0) + double result = 0.0; + if (s_max < 0.0) { + // entirely negative + result = integral_expfunc_neg(s_min, s_max, t0, A, B); + } else if (s_min > 0.0) { + // entirely positive + result = integral_expfunc_pos(s_min, s_max, t0, A, B); + } else { + // crosses zero + result = + integral_expfunc_neg(s_min, 0.0, t0, A, B) + integral_expfunc_pos(0.0, s_max, t0, A, B); } + return result; +} - double t_snap = time_between_z(z, z_prev) / (physconst.s_per_yr * 1e6); // Myr - double t_snap_prev = time_between_z(z_prev, z_prev_2) / (physconst.s_per_yr * 1e6); // Myr - - // determine the SFH correlation functions - fftwf_complex W_10[N_FREQ_SFH], W_100[N_FREQ_SFH], W_curr[N_FREQ_SFH], W_prev[N_FREQ_SFH]; - fftwf_complex psd_unfiltered[N_FREQ_SFH], psd_filtered[N_FREQ_SFH]; +double smoothed_correlation_func(double tau, double t1, double t2) { + // Analytic SFH correlation function for two smoothed exponentials + // with timescales t1 and t2 respectively, at lag tau + double t0 = astro_params_global->SFH_TAU; + double sigma = astro_params_global->SIGMA_STAR; // re-using this parameter for now + double result = 0.0; - fftwf_complex *in; - float *out; - in = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * N_FREQ_SFH); - out = (float *)calloc(N_FREQ_SFH, sizeof(float)); - fftwf_plan p = fftwf_plan_dft_c2r_1d(N_TAU_SFH, in, out, FFTW_ESTIMATE); + double first_boundary = fmin(tau, tau + t1 - t2); + double second_boundary = fmax(tau, tau + t1 - t2); - // get the filters - for (int i = 0; i < N_FREQ_SFH; i++) { - psd_unfiltered[i] = psd_sfh_powerlaw(w_arr[i]); - W_10[i] = shifted_tophat_1d(w_arr[i] * 10.); - W_100[i] = shifted_tophat_1d(w_arr[i] * 100.); - W_curr[i] = shifted_tophat_1d(w_arr[i] * t_snap); - W_prev[i] = shifted_tophat_1d(w_arr[i] * t_snap_prev); + // The first and second regions *may* have negative components + // Region 1 + if (tau - t2 < first_boundary) { + result += integral_expfunc_mod(tau - t2, first_boundary, t0, t2 - tau, 1.0); } - // TODO: check that there N_TAU_SFH normalization on the iFFT - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_10[i] * conj(W_10[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_10_10->y_arr[i] = out[i] / N_TAU_SFH; + // Region 2 + if (first_boundary < second_boundary) { + result += integral_expfunc_mod(first_boundary, second_boundary, t0, fmin(t1, t2), 0.0); } - // 10Myr X 100Myr - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_100[i] * conj(W_10[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_10_100->y_arr[i] = out[i] / N_TAU_SFH; + // Region 3 (second boundary is always positive) + if (second_boundary < tau + t1) { + result += integral_expfunc_pos(second_boundary, tau + t1, t0, t1 + tau, -1.0); } - // 100Myr X 100Myr - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_100[i] * conj(W_100[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_100_100->y_arr[i] = out[i] / N_TAU_SFH; - } + return sigma * sigma * result / (t1 * t2); +} - // 10Myr X Current snapshot length - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_10[i] * conj(W_curr[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_10_curr->y_arr[i] = out[i] / N_TAU_SFH; - } +void fill_covar_analytic(double tau, double tau_prev, gsl_matrix *curr_cov, gsl_matrix *prev_cov, + gsl_matrix *cross_cov) { + /* Fill the covariance matrices required for sampling the SFR correctly */ + double cov_10_10_tau = smoothed_correlation_func(tau, 10.0, 10.0); + double cov_10_100_tau = smoothed_correlation_func(tau, 10.0, 100.0); + double cov_10_curr_tau = smoothed_correlation_func(tau, 10.0, tau); + double cov_10_prev_tau = smoothed_correlation_func(tau, 10.0, tau_prev); + double cov_100_100_tau = smoothed_correlation_func(tau, 100.0, 100.0); + double cov_100_curr_tau = smoothed_correlation_func(tau, 100.0, tau); + double cov_100_prev_tau = smoothed_correlation_func(tau, 100.0, tau_prev); + double cov_curr_prev_tau = smoothed_correlation_func(tau, tau, tau_prev); - // 100Myr X Current snapshot length - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_100[i] * conj(W_curr[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_100_curr->y_arr[i] = out[i] / N_TAU_SFH; - } + // Get zero-lag correlations + double cov_10_10_zero = smoothed_correlation_func(0.0, 10.0, 10.0); + double cov_10_100_zero = smoothed_correlation_func(0.0, 10.0, 100.0); + double cov_10_curr_zero = smoothed_correlation_func(0.0, 10.0, tau); + double cov_10_prev_zero = smoothed_correlation_func(0.0, 10.0, tau_prev); + double cov_100_100_zero = smoothed_correlation_func(0.0, 100.0, 100.0); + double cov_100_curr_zero = smoothed_correlation_func(0.0, 100.0, tau); + double cov_100_prev_zero = smoothed_correlation_func(0.0, 100.0, tau_prev); + double cov_curr_curr_zero = smoothed_correlation_func(0.0, tau, tau); + double cov_prev_prev_zero = smoothed_correlation_func(0.0, tau_prev, tau_prev); - // Current snapshot length X Current snapshot length - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_curr[i] * conj(W_curr[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_curr_curr->y_arr[i] = out[i] / N_TAU_SFH; - } + // Previous Snapshot covariance matrix + gsl_matrix_set(prev_cov, 0, 0, cov_10_10_zero); // 10_prev vs 10_prev + gsl_matrix_set(prev_cov, 0, 1, cov_10_100_zero); // 10_prev vs 100_prev + gsl_matrix_set(prev_cov, 0, 2, cov_10_prev_zero); // 10_prev vs snap_prev + gsl_matrix_set(prev_cov, 1, 0, cov_10_100_zero); // 100_prev vs 10_prev + gsl_matrix_set(prev_cov, 1, 1, cov_100_100_zero); // 100_prev vs 100_prev + gsl_matrix_set(prev_cov, 1, 2, cov_100_prev_zero); // 100_prev vs snap_prev + gsl_matrix_set(prev_cov, 2, 0, cov_10_prev_zero); // snap_prev vs 10_prev + gsl_matrix_set(prev_cov, 2, 1, cov_100_prev_zero); // snap_prev vs 100_prev + gsl_matrix_set(prev_cov, 2, 2, cov_prev_prev_zero); // snap_prev vs snap_prev - // 10Myr X Previous snapshot length - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_10[i] * conj(W_prev[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_10_prev->y_arr[i] = out[i] / N_TAU_SFH; - } + // Lower left corner Covariance is Cov(curr,prev) == Cov(prev,curr)^T + gsl_matrix_set(cross_cov, 0, 0, cov_10_10_tau); // 10_prev vs 10_curr + gsl_matrix_set(cross_cov, 0, 1, cov_10_100_tau); // 10_prev vs 100_curr + gsl_matrix_set(cross_cov, 0, 2, cov_10_prev_tau); // 10_prev vs snap_curr + gsl_matrix_set(cross_cov, 1, 0, cov_10_100_tau); // 100_prev vs 10_curr + gsl_matrix_set(cross_cov, 1, 1, cov_100_100_tau); // 100_prev vs 100_curr + gsl_matrix_set(cross_cov, 1, 2, cov_100_prev_tau); // 100_prev vs snap_curr + gsl_matrix_set(cross_cov, 2, 0, cov_10_curr_tau); // snap_prev vs 10_curr + gsl_matrix_set(cross_cov, 2, 1, cov_100_curr_tau); // snap_prev vs 100_curr + gsl_matrix_set(cross_cov, 2, 2, cov_curr_prev_tau); // snap_prev vs snap_curr - // 100Myr X Previous snapshot length - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_100[i] * conj(W_prev[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_100_prev->y_arr[i] = out[i] / N_TAU_SFH; - } + // Current Snapshot covariance matrix + // NOTE: Since the snapshot lengths are different, the snap variances are different + gsl_matrix_memcpy(curr_cov, prev_cov); + gsl_matrix_set(curr_cov, 0, 2, cov_10_curr_zero); // 10_curr vs 10_curr + gsl_matrix_set(curr_cov, 1, 2, cov_100_curr_zero); // 100_curr vs 100_curr + gsl_matrix_set(curr_cov, 2, 0, cov_10_curr_zero); // 10_curr vs 10_curr + gsl_matrix_set(curr_cov, 2, 1, cov_100_curr_zero); // 100_curr vs 100_curr + gsl_matrix_set(curr_cov, 2, 2, cov_curr_curr_zero); // snap_curr vs snap_curr +} - // Current snapshot length X Previous snapshot length - for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_curr[i] * conj(W_prev[i]); - } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_curr_prev->y_arr[i] = out[i] / N_TAU_SFH; +// NOTE: This only differs from a tophat in phase. While we only use filter squared in the +// correlation functions, Since two filters can be shifted by different amounts (due to width) +// we keep the shifting +fftwf_complex shifted_tophat_1d(double wt) { + // fourier transform of a real space tophat shift in the positive direction by R/2 + // We use this in the SFH model to get SFR between now and R Myr ago + fftwf_complex result; + if (wt < 1e-4) + result = 1.0 + 0.5 * I * wt - wt * wt / 6.0; // second order taylor expansion around kR==0 + else + result = -I * (cexp(I * wt) - 1.) / wt; + return result; +} + +// TODO: I only really need a single tau per snapshot, BUT since there is no analyic iFFT for the +// PSD +// filtered by the shifted tophat, I *think* we need to do the full iFFT anyway +// to get the correlation function, even at a single tau +void initialise_psd_corrfunc_tables(double tau, double tau_prev) { + // For easy initialisation, we set arrays of the tables and delays used + RGTable1D *table_ptrs[11] = { + &sfh_corr.corr_10_10, &sfh_corr.corr_10_100, &sfh_corr.corr_10_curr, + &sfh_corr.corr_10_prev, &sfh_corr.corr_100_100, &sfh_corr.corr_100_curr, + &sfh_corr.corr_100_prev, &sfh_corr.corr_curr_curr, &sfh_corr.corr_curr_prev, + &sfh_corr.corr_prev_prev, &sfh_corr.corr_zero}; + + double delays[11][2] = {{10., 10.}, {10., 100.}, {10., tau}, + {10., tau_prev}, {100., 100.}, {100., tau}, + {100., tau_prev}, {tau, tau}, {tau, tau_prev}, + {tau_prev, tau_prev}, {0., 0.}}; + + for (int i = 0; i < 11; i++) { + allocate_RGTable1D(N_TAU_SFH, table_ptrs[i]); + table_ptrs[i]->x_min = 0.; + table_ptrs[i]->x_width = MAX_TAU / (N_TAU_SFH - 1); } - // Previous snapshot length X Previous snapshot length + double w_arr[N_FREQ_SFH]; for (int i = 0; i < N_FREQ_SFH; i++) { - in[i] = psd_unfiltered[i] * W_prev[i] * conj(W_prev[i]); + w_arr[i] = 2 * M_PI * i / MAX_TAU; } - fftwf_execute(p); - for (int i = 0; i < N_TAU_SFH; i++) { - sfh_corr.corr_prev_prev->y_arr[i] = out[i] / N_TAU_SFH; + + fftwf_complex *in; + float *out; + in = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * N_FREQ_SFH); + out = (float *)calloc(N_TAU_SFH, sizeof(float)); + fftwf_plan p = fftwf_plan_dft_c2r_1d(N_TAU_SFH, in, out, FFTW_ESTIMATE); + + // NOTE: it would be more efficient to store all the window functions in arrays prior + // i.e calculate them once each (4) instead of once per table (20) + for (int i = 0; i < 11; i++) { + double sum = 0.0; + double t1 = delays[i][0]; + double t2 = delays[i][1]; + RGTable1D *curr_table = table_ptrs[i]; + for (int j = 0; j < N_FREQ_SFH; j++) { + double w = w_arr[j]; + in[j] = psd_sfh_powerlaw(w) * shifted_tophat_1d(w * t1) * + conj(shifted_tophat_1d(w * t2)); // FFTW unnormalised convention + + sum += creal(in[j]); + if (j > 0) { + sum += creal(in[j]); // account for negative frequencies + } + } + fftwf_execute(p); + sum = 0.0; + for (int j = 0; j < N_TAU_SFH; j++) { + curr_table->y_arr[j] = out[j] / MAX_TAU; + sum += curr_table->y_arr[j]; + } } fftwf_destroy_plan(p); fftwf_free(in); - fftwf_free(out); + free(out); } -void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean_correction) { - /* Evaluate the SFH covariance matrix at a given time step tau - - Outputs are the two matrices required for sampling the SFR correctly - - out_chol_cov = Cholesky factor of Cov(curr|prev), multiplies standard normal vector - to get correlated & conditioned SFRs. NOTE: The upper triangle is garbage - - out_mean_correction = multiplies the condition vector, to be added to the correlated samples. - */ - +void fill_covar_from_tables(double tau, gsl_matrix *curr_cov, gsl_matrix *prev_cov, + gsl_matrix *cross_cov) { + /* Fill the covariance matrices required for sampling the SFR correctly */ // Interpolate correlation functions at lag tau - double cov_10_10_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_10); - double cov_10_100_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_100); - double cov_10_curr_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_curr); - double cov_10_prev_tau = EvaluateRGTable1D(tau, sfh_corr.corr_10_prev); - double cov_100_100_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_100); - double cov_100_curr_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_curr); - double cov_100_prev_tau = EvaluateRGTable1D(tau, sfh_corr.corr_100_prev); - double cov_curr_prev_tau = EvaluateRGTable1D(tau, sfh_corr.corr_curr_prev); + double cov_10_10_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_10_10); + double cov_10_100_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_10_100); + double cov_10_curr_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_10_curr); + double cov_10_prev_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_10_prev); + double cov_100_100_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_100_100); + double cov_100_curr_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_100_curr); + double cov_100_prev_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_100_prev); + double cov_curr_prev_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_curr_prev); // Get zero-lag correlations - double cov_10_10_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_10); - double cov_10_100_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_100); - double cov_10_curr_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_curr); - double cov_10_prev_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_10_prev); - double cov_100_100_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_100); - double cov_100_curr_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_curr); - double cov_100_prev_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_100_prev); - double cov_curr_curr_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_curr_curr); - double cov_prev_prev_zero = EvaluateRGTable1D(0.0, sfh_corr.corr_prev_prev); + double cov_10_10_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_10_10); + double cov_10_100_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_10_100); + double cov_10_curr_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_10_curr); + double cov_10_prev_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_10_prev); + double cov_100_100_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_100_100); + double cov_100_curr_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_100_curr); + double cov_100_prev_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_100_prev); + double cov_curr_curr_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_curr_curr); + double cov_prev_prev_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_prev_prev); // Previous Snapshot covariance matrix - gsl_matrix *prev_cov = gsl_matrix_alloc(3, 3); gsl_matrix_set(prev_cov, 0, 0, cov_10_10_zero); // 10_prev vs 10_prev gsl_matrix_set(prev_cov, 0, 1, cov_10_100_zero); // 10_prev vs 100_prev gsl_matrix_set(prev_cov, 0, 2, cov_10_prev_zero); // 10_prev vs snap_prev @@ -241,7 +289,6 @@ void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean gsl_matrix_set(prev_cov, 2, 2, cov_prev_prev_zero); // snap_prev vs snap_prev // Lower left corner Covariance is Cov(curr,prev) == Cov(prev,curr)^T - gsl_matrix *cross_cov = gsl_matrix_alloc(3, 3); gsl_matrix_set(cross_cov, 0, 0, cov_10_10_tau); // 10_prev vs 10_curr gsl_matrix_set(cross_cov, 0, 1, cov_10_100_tau); // 10_prev vs 100_curr gsl_matrix_set(cross_cov, 0, 2, cov_10_prev_tau); // 10_prev vs snap_curr @@ -253,7 +300,6 @@ void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean gsl_matrix_set(cross_cov, 2, 2, cov_curr_prev_tau); // snap_prev vs snap_curr // Current Snapshot covariance matrix - gsl_matrix *curr_cov = gsl_matrix_alloc(3, 3); // NOTE: Since the snapshot lengths are different, the snap variances are different gsl_matrix_memcpy(curr_cov, prev_cov); gsl_matrix_set(curr_cov, 0, 2, cov_10_curr_zero); // 10_curr vs 10_curr @@ -261,7 +307,19 @@ void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean gsl_matrix_set(curr_cov, 2, 0, cov_10_curr_zero); // 10_curr vs 10_curr gsl_matrix_set(curr_cov, 2, 1, cov_100_curr_zero); // 100_curr vs 100_curr gsl_matrix_set(curr_cov, 2, 2, cov_curr_curr_zero); // snap_curr vs snap_curr +} + +void eval_sfh_moments(gsl_matrix *prev_cov, gsl_matrix *curr_cov, gsl_matrix *cross_cov, + gsl_matrix *out_chol_cov, gsl_matrix *out_mean_correction) { + /* Evaluate the SFH covariance matrix at a given time step tau + + Outputs are the two matrices required for sampling the SFR correctly + out_chol_cov = Cholesky factor of Cov(curr|prev), multiplies standard normal vector + to get correlated & conditioned SFRs. NOTE: The upper triangle is garbage + + out_mean_correction = multiplies the condition vector, to be added to the correlated samples. + */ // NOTE: Currently Cov(curr,prev) == Cov(prev,curr) == Cov(prev,curr)^T == Cov(curr,prev)^T gsl_matrix *matrix_buf = gsl_matrix_alloc(3, 3); @@ -336,14 +394,106 @@ void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], gsl_matrix *L_co } void free_sfh_correlation() { - free_RGTable1D(sfh_corr.corr_10_10); - free_RGTable1D(sfh_corr.corr_10_100); - free_RGTable1D(sfh_corr.corr_100_100); - free_RGTable1D(sfh_corr.corr_10_curr); - free_RGTable1D(sfh_corr.corr_100_curr); - free_RGTable1D(sfh_corr.corr_curr_curr); - free_RGTable1D(sfh_corr.corr_10_prev); - free_RGTable1D(sfh_corr.corr_100_prev); - free_RGTable1D(sfh_corr.corr_curr_prev); - free_RGTable1D(sfh_corr.corr_prev_prev); + free_RGTable1D(&sfh_corr.corr_10_10); + free_RGTable1D(&sfh_corr.corr_10_100); + free_RGTable1D(&sfh_corr.corr_100_100); + free_RGTable1D(&sfh_corr.corr_10_curr); + free_RGTable1D(&sfh_corr.corr_100_curr); + free_RGTable1D(&sfh_corr.corr_curr_curr); + free_RGTable1D(&sfh_corr.corr_10_prev); + free_RGTable1D(&sfh_corr.corr_100_prev); + free_RGTable1D(&sfh_corr.corr_curr_prev); + free_RGTable1D(&sfh_corr.corr_prev_prev); + free_RGTable1D(&sfh_corr.corr_zero); +} + +void print_gsl_matrix(gsl_matrix *mat, const char *label) { + int nrows = mat->size1; + int ncols = mat->size2; + fprintf(stdout, "%s\n", label); + for (int i = 0; i < nrows; i++) { + for (int j = 0; j < ncols; j++) { + fprintf(stdout, "%9.4f ", gsl_matrix_get(mat, i, j)); + } + fprintf(stdout, "\n"); + } +} + +int test_sfh_corr(double z0, double z1, double z2) { + fprintf(stdout, "Testing SFH matrices at z=%f, %f, %f\n", z0, z1, z2); + double tau = time_between_z(z0, z1) / (physconst.s_per_yr * 1e6); // Myr + double tau_prev = time_between_z(z1, z2) / (physconst.s_per_yr * 1e6); // Myr + initialise_psd_corrfunc_tables(tau, tau_prev); + + RGTable1D *table_ptrs[11] = { + &sfh_corr.corr_10_10, &sfh_corr.corr_10_100, &sfh_corr.corr_10_curr, + &sfh_corr.corr_10_prev, &sfh_corr.corr_100_100, &sfh_corr.corr_100_curr, + &sfh_corr.corr_100_prev, &sfh_corr.corr_curr_curr, &sfh_corr.corr_curr_prev, + &sfh_corr.corr_prev_prev, &sfh_corr.corr_zero}; + + double delays[11][2] = {{10., 10.}, {10., 100.}, {10., tau}, + {10., tau_prev}, {100., 100.}, {100., tau}, + {100., tau_prev}, {tau, tau}, {tau, tau_prev}, + {tau_prev, tau_prev}, {0., 0.}}; + + char names[11][20] = {"10Myr x 10Myr", "10Myr x 100Myr", "10Myr x Curr", "10Myr x Prev", + "100Myr x 100Myr", "100Myr x Curr", "100Myr x Prev", "Curr x Curr", + "Curr x Prev", "Prev x Prev", "Zero Lag"}; + + fprintf(stdout, "====== Correlation functions ======\n"); + for (int i = 0; i < 10; i++) { + fprintf(stdout, "%s (t1=%f, t2=%f):\n", names[i], delays[i][0], delays[i][1]); + for (double lag = 0.0; lag <= 500.0; lag += 50.0) { + double val_tables = EvaluateRGTable1D(lag, table_ptrs[i]); + double val_analytic = smoothed_correlation_func(lag, delays[i][0], delays[i][1]); + fprintf(stdout, " Lag %7.2f Tables: %9.4f Analytic: %9.4f Ratio: %9.4f\n", lag, + val_tables, val_analytic, val_tables / val_analytic); + } + } + fprintf(stdout, "===== zero smoothing =====\n"); + for (double lag = 0.0; lag <= 500.0; lag += 50.0) { + double val_tables = EvaluateRGTable1D(lag, &sfh_corr.corr_zero); + double val_analytic = exp(-fabs(lag) / astro_params_global->SFH_TAU) * + astro_params_global->SIGMA_STAR * astro_params_global->SIGMA_STAR; + fprintf(stdout, " Lag %7.2f Tables: %9.4f Analytic: %9.4f Ratio: %9.4f\n", lag, val_tables, + val_analytic, val_tables / val_analytic); + } + + fprintf(stdout, "====== PSD Covariance Matrices ======\n"); + gsl_matrix *curr_cov = gsl_matrix_alloc(3, 3); + gsl_matrix *prev_cov = gsl_matrix_alloc(3, 3); + gsl_matrix *cross_cov = gsl_matrix_alloc(3, 3); + fill_covar_from_tables(tau, curr_cov, prev_cov, cross_cov); + print_gsl_matrix(prev_cov, "Previous Covariance Matrix:"); + print_gsl_matrix(cross_cov, "Cross Covariance Matrix:"); + print_gsl_matrix(curr_cov, "Current Covariance Matrix:"); + + fprintf(stdout, "====== Analytic Covariance Matrices ======\n"); + gsl_matrix *curr_cov_2 = gsl_matrix_alloc(3, 3); + gsl_matrix *prev_cov_2 = gsl_matrix_alloc(3, 3); + gsl_matrix *cross_cov_2 = gsl_matrix_alloc(3, 3); + fill_covar_analytic(tau, tau_prev, curr_cov_2, prev_cov_2, cross_cov_2); + print_gsl_matrix(prev_cov_2, "Previous Covariance Matrix:"); + print_gsl_matrix(cross_cov_2, "Cross Covariance Matrix:"); + print_gsl_matrix(curr_cov_2, "Current Covariance Matrix:"); + + gsl_matrix_div_elements(prev_cov_2, prev_cov); + gsl_matrix_div_elements(cross_cov_2, cross_cov); + gsl_matrix_div_elements(curr_cov_2, curr_cov); + print_gsl_matrix(prev_cov_2, "Previous Covariance Matrix Ratio:"); + print_gsl_matrix(cross_cov_2, "Cross Covariance Matrix Ratio:"); + print_gsl_matrix(curr_cov_2, "Current Covariance Matrix Ratio:"); + + gsl_matrix *L_cov = gsl_matrix_alloc(3, 3); + gsl_matrix *mean_corr = gsl_matrix_alloc(3, 3); + eval_sfh_moments(prev_cov, curr_cov, cross_cov, L_cov, mean_corr); + print_gsl_matrix(mean_corr, "Mean Correction Matrix:"); + print_gsl_matrix(L_cov, "Cholesky Factor of Conditioned Covariance Matrix:"); + + // get the conditioned covariance matrix + gsl_blas_dtrmm(CblasRight, CblasLower, CblasTrans, CblasNonUnit, 1.0, L_cov, L_cov); + print_gsl_matrix(L_cov, "Conditioned Covariance Matrix:"); + + free_sfh_correlation(); + return 0; } diff --git a/src/py21cmfast/src/correlated_sfh.h b/src/py21cmfast/src/correlated_sfh.h index f7bb2e323..14d3b72e4 100644 --- a/src/py21cmfast/src/correlated_sfh.h +++ b/src/py21cmfast/src/correlated_sfh.h @@ -1,11 +1,17 @@ #include +#include #ifndef CORRELATED_SFH_H #define CORRELATED_SFH_H -void initialise_sfh_correlation(double z, double z_prev, double z_prev_2); -void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean_correction); +void initialise_psd_corrfunc_tables(double tau, double tau_prev); +void fill_covar_from_tables(double tau, gsl_matrix *curr_cov, gsl_matrix *prev_cov, + gsl_matrix *cross_cov); +void fill_covar_analytic(double tau, double tau_prev, gsl_matrix *curr_cov, gsl_matrix *prev_cov, + gsl_matrix *cross_cov); +void eval_sfh_moments(gsl_matrix *prev_cov, gsl_matrix *curr_cov, gsl_matrix *cross_cov, + gsl_matrix *out_chol_cov, gsl_matrix *out_mean_correction); void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], gsl_matrix *L_cov, gsl_matrix *mean_corr, double out_values[3]); void free_sfh_correlation(); diff --git a/src/py21cmfast/src/cosmology.c b/src/py21cmfast/src/cosmology.c index 1ff27c0b9..542dc8c0a 100644 --- a/src/py21cmfast/src/cosmology.c +++ b/src/py21cmfast/src/cosmology.c @@ -692,7 +692,8 @@ double dicke(double z) { } /* function DTDZ returns the value of dt/dz at the redshift parameter z. */ -double dtdz(float z) { +// TODO: This is equivalent to 1/(1+z) * H(z), figure out why it's like this +double dtdz(double z) { double x, dxdz, const1, denom, numer; x = sqrt(cosmo_params_global->OMl / cosmo_params_global->OMm) * pow(1 + z, -3.0 / 2.0); dxdz = sqrt(cosmo_params_global->OMl / cosmo_params_global->OMm) * pow(1 + z, -5.0 / 2.0) * @@ -767,13 +768,13 @@ double time_between_z(double z_low, double z_high) { gsl_function F; double rel_tol = 1e-4; //<- relative tolerance int w_size = 1000; - gsl_integration_workspace* w = gsl_integration_workspace_alloc(w_size); + gsl_integration_workspace *w = gsl_integration_workspace_alloc(w_size); int status; F.function = &dtdz; gsl_set_error_handler_off(); - status = gsl_integration_qag(&F, z_low, z_high, 0, rel_tol, w_size, GSL_INTEG_GAUSS61, w, + status = gsl_integration_qag(&F, z_high, z_low, 0, rel_tol, w_size, GSL_INTEG_GAUSS61, w, &result, &error); if (status != 0) { diff --git a/src/py21cmfast/src/cosmology.h b/src/py21cmfast/src/cosmology.h index adbecc4a1..dca8f1b15 100644 --- a/src/py21cmfast/src/cosmology.h +++ b/src/py21cmfast/src/cosmology.h @@ -26,7 +26,7 @@ double TtoM(double z, double T, double mu); double dicke(double z); double ddickedt(double z); double ddicke_dz(double z); -double dtdz(float z); +double dtdz(double z); double drdz(float z); /* comoving distance, (1+z)*C*dtdz(in cm) per unit z */ double hubble(float z); diff --git a/src/py21cmfast/wrapper/cfuncs.py b/src/py21cmfast/wrapper/cfuncs.py index 0099d7293..08855f477 100644 --- a/src/py21cmfast/wrapper/cfuncs.py +++ b/src/py21cmfast/wrapper/cfuncs.py @@ -992,3 +992,31 @@ def return_chmf_value( sigma[None, :, None], inputs.matter_options.cdict["HMF"], ) + + +@broadcast_params +def test_sfh_print( + *, + inputs: InputParameters, + z0: float, + z1: float, + z2: float, +): + """Test the SFH printing function from the backend. + + Parameters + ---------- + inputs : InputParameters + The input parameters defining the simulation run. + z0 : float + The current redshift. + z1 : float + The previous redshift. + z2 : float + The second previous redshift. + """ + lib.test_sfh_corr( + z0, + z1, + z2, + ) From 5fc9f229fffc771e077150d2a1898ccdcbe10ff4 Mon Sep 17 00:00:00 2001 From: James Davies Date: Wed, 29 Oct 2025 17:06:25 +1100 Subject: [PATCH 09/18] fix my misunderstandings of covariances --- src/py21cmfast/src/correlated_sfh.c | 478 +++++++++++++++++----------- src/py21cmfast/src/correlated_sfh.h | 14 +- 2 files changed, 299 insertions(+), 193 deletions(-) diff --git a/src/py21cmfast/src/correlated_sfh.c b/src/py21cmfast/src/correlated_sfh.c index 5b5b91966..4dbbdd371 100644 --- a/src/py21cmfast/src/correlated_sfh.c +++ b/src/py21cmfast/src/correlated_sfh.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -14,14 +15,13 @@ #include "Constants.h" #include "InputParameters.h" +#include "cexcept.h" #include "cosmology.h" #include "exceptions.h" #include "interpolation.h" #include "logger.h" -#define MAX_TAU (double)(2000) // Myr -#define N_TAU_SFH (int)(10000) // number of time bins in SFH -#define N_FREQ_SFH (int)(N_TAU_SFH / 2 + 1) // number of frequency bins in SFH +static const int N_TAU_SFH = 10000; // number of time bins in SFH // We want a singleton struct which holds the SFH correlation functions // We need the crosses between four timescales: 10 Myr, 100 Myr, snapshot interval, previous @@ -42,6 +42,57 @@ typedef struct SFH_Correlation { static SFH_Correlation sfh_corr; +typedef struct SFH_matrices { + gsl_matrix *curr_cov; + gsl_matrix *prev_cov; + gsl_matrix *pxc_cov; + gsl_matrix *L_cov; + gsl_matrix *mean_correction; +} SFH_matrices; + +static SFH_matrices sfh_mats; + +void print_gsl_matrix(gsl_matrix *mat, const char *label) { + int nrows = mat->size1; + int ncols = mat->size2; + fprintf(stdout, "%s\n", label); + for (int i = 0; i < nrows; i++) { + for (int j = 0; j < ncols; j++) { + fprintf(stdout, "%9.4f ", gsl_matrix_get(mat, i, j)); + } + fprintf(stdout, "\n"); + } +} + +void force_matrix_symmetric(gsl_matrix *mat) { + // Make a matrix symmetric by copying lower triangle to upper triangle + int nrows = mat->size1; + int ncols = mat->size2; + if (nrows != ncols) { + LOG_ERROR("Matrix is not square, cannot symmetrise!"); + Throw(ValueError); + } + for (int i = 0; i < nrows; i++) { + for (int j = i + 1; j < ncols; j++) { + gsl_matrix_set(mat, i, j, gsl_matrix_get(mat, j, i)); + } + } +} + +void force_matrix_lotri(gsl_matrix *mat) { + int nrows = mat->size1; + int ncols = mat->size2; + if (nrows != ncols) { + LOG_ERROR("Matrix is not square, cannot make lower triangular!"); + Throw(ValueError); + } + for (int i = 0; i < nrows; i++) { + for (int j = i + 1; j < ncols; j++) { + gsl_matrix_set(mat, i, j, 0.0); + } + } +} + // Carvajal-Bohorquez et al. 2025 form double psd_sfh_powerlaw(double w) { // re-using SIGMA_STAR here for the variance normalisation @@ -77,10 +128,10 @@ double integral_expfunc_mod(double s_min, double s_max, double t0, double A, dou // Integral of (A + Bs)*exp(-|s|/t0) from s_min to s_max // used to build the integral of (A + Bs)*exp(-|s|/t0) double result = 0.0; - if (s_max < 0.0) { + if (s_max <= 0.0) { // entirely negative result = integral_expfunc_neg(s_min, s_max, t0, A, B); - } else if (s_min > 0.0) { + } else if (s_min >= 0.0) { // entirely positive result = integral_expfunc_pos(s_min, s_max, t0, A, B); } else { @@ -101,7 +152,6 @@ double smoothed_correlation_func(double tau, double t1, double t2) { double first_boundary = fmin(tau, tau + t1 - t2); double second_boundary = fmax(tau, tau + t1 - t2); - // The first and second regions *may* have negative components // Region 1 if (tau - t2 < first_boundary) { result += integral_expfunc_mod(tau - t2, first_boundary, t0, t2 - tau, 1.0); @@ -112,9 +162,9 @@ double smoothed_correlation_func(double tau, double t1, double t2) { result += integral_expfunc_mod(first_boundary, second_boundary, t0, fmin(t1, t2), 0.0); } - // Region 3 (second boundary is always positive) + // Region 3 if (second_boundary < tau + t1) { - result += integral_expfunc_pos(second_boundary, tau + t1, t0, t1 + tau, -1.0); + result += integral_expfunc_mod(second_boundary, tau + t1, t0, t1 + tau, -1.0); } return sigma * sigma * result / (t1 * t2); @@ -123,56 +173,53 @@ double smoothed_correlation_func(double tau, double t1, double t2) { void fill_covar_analytic(double tau, double tau_prev, gsl_matrix *curr_cov, gsl_matrix *prev_cov, gsl_matrix *cross_cov) { /* Fill the covariance matrices required for sampling the SFR correctly */ - double cov_10_10_tau = smoothed_correlation_func(tau, 10.0, 10.0); - double cov_10_100_tau = smoothed_correlation_func(tau, 10.0, 100.0); - double cov_10_curr_tau = smoothed_correlation_func(tau, 10.0, tau); - double cov_10_prev_tau = smoothed_correlation_func(tau, 10.0, tau_prev); - double cov_100_100_tau = smoothed_correlation_func(tau, 100.0, 100.0); - double cov_100_curr_tau = smoothed_correlation_func(tau, 100.0, tau); - double cov_100_prev_tau = smoothed_correlation_func(tau, 100.0, tau_prev); - double cov_curr_prev_tau = smoothed_correlation_func(tau, tau, tau_prev); - - // Get zero-lag correlations - double cov_10_10_zero = smoothed_correlation_func(0.0, 10.0, 10.0); - double cov_10_100_zero = smoothed_correlation_func(0.0, 10.0, 100.0); - double cov_10_curr_zero = smoothed_correlation_func(0.0, 10.0, tau); - double cov_10_prev_zero = smoothed_correlation_func(0.0, 10.0, tau_prev); - double cov_100_100_zero = smoothed_correlation_func(0.0, 100.0, 100.0); - double cov_100_curr_zero = smoothed_correlation_func(0.0, 100.0, tau); - double cov_100_prev_zero = smoothed_correlation_func(0.0, 100.0, tau_prev); - double cov_curr_curr_zero = smoothed_correlation_func(0.0, tau, tau); - double cov_prev_prev_zero = smoothed_correlation_func(0.0, tau_prev, tau_prev); + /* A few notes on the covariance: + Due to the asymmetry of the filter, and the different snapshot lengths, + + rho(tau, t1, t2) = rho(-tau, t2, t1) So... + Cov(X_prev, Y_curr) == Cov(Y_curr, X_prev) != Cov(X_curr, Y_prev) == Cov(Y_prev, X_curr) + + The entire covariance matrix is symmetric and positive semi-definite, + The auto-correlation sub-matrices (top-left/bottom-right) are both symmetric as well + but the cross-covariance (top-right/bottom-left) is not. + */ // Previous Snapshot covariance matrix - gsl_matrix_set(prev_cov, 0, 0, cov_10_10_zero); // 10_prev vs 10_prev - gsl_matrix_set(prev_cov, 0, 1, cov_10_100_zero); // 10_prev vs 100_prev - gsl_matrix_set(prev_cov, 0, 2, cov_10_prev_zero); // 10_prev vs snap_prev - gsl_matrix_set(prev_cov, 1, 0, cov_10_100_zero); // 100_prev vs 10_prev - gsl_matrix_set(prev_cov, 1, 1, cov_100_100_zero); // 100_prev vs 100_prev - gsl_matrix_set(prev_cov, 1, 2, cov_100_prev_zero); // 100_prev vs snap_prev - gsl_matrix_set(prev_cov, 2, 0, cov_10_prev_zero); // snap_prev vs 10_prev - gsl_matrix_set(prev_cov, 2, 1, cov_100_prev_zero); // snap_prev vs 100_prev - gsl_matrix_set(prev_cov, 2, 2, cov_prev_prev_zero); // snap_prev vs snap_prev - - // Lower left corner Covariance is Cov(curr,prev) == Cov(prev,curr)^T - gsl_matrix_set(cross_cov, 0, 0, cov_10_10_tau); // 10_prev vs 10_curr - gsl_matrix_set(cross_cov, 0, 1, cov_10_100_tau); // 10_prev vs 100_curr - gsl_matrix_set(cross_cov, 0, 2, cov_10_prev_tau); // 10_prev vs snap_curr - gsl_matrix_set(cross_cov, 1, 0, cov_10_100_tau); // 100_prev vs 10_curr - gsl_matrix_set(cross_cov, 1, 1, cov_100_100_tau); // 100_prev vs 100_curr - gsl_matrix_set(cross_cov, 1, 2, cov_100_prev_tau); // 100_prev vs snap_curr - gsl_matrix_set(cross_cov, 2, 0, cov_10_curr_tau); // snap_prev vs 10_curr - gsl_matrix_set(cross_cov, 2, 1, cov_100_curr_tau); // snap_prev vs 100_curr - gsl_matrix_set(cross_cov, 2, 2, cov_curr_prev_tau); // snap_prev vs snap_curr + gsl_matrix_set(prev_cov, 0, 0, smoothed_correlation_func(0, 10., 10.)); + gsl_matrix_set(prev_cov, 1, 0, smoothed_correlation_func(0, 100., 10.)); + gsl_matrix_set(prev_cov, 1, 1, smoothed_correlation_func(0, 100., 100.)); + gsl_matrix_set(prev_cov, 2, 0, smoothed_correlation_func(0, tau_prev, 10.)); + gsl_matrix_set(prev_cov, 2, 1, smoothed_correlation_func(0, tau_prev, 100.)); + gsl_matrix_set(prev_cov, 2, 2, smoothed_correlation_func(0, tau_prev, tau_prev)); + force_matrix_symmetric(prev_cov); + + // FOR DEBUG: The forcing of symmetry should be equivalent to: + // gsl_matrix_set(prev_cov, 0, 1, smoothed_correlation_func(0,10.,100.)); + // gsl_matrix_set(prev_cov, 0, 2, smoothed_correlation_func(0,10.,tau_prev)); + // gsl_matrix_set(prev_cov, 1, 2, smoothed_correlation_func(0,100.,tau_prev)); + + // Upper right corner Covariance is Cov(prev,curr) == Cov(curr,prev)^T + gsl_matrix_set(cross_cov, 0, 0, smoothed_correlation_func(tau, 10., 10.)); + gsl_matrix_set(cross_cov, 0, 1, smoothed_correlation_func(tau, 10., 100.)); + gsl_matrix_set(cross_cov, 0, 2, smoothed_correlation_func(tau, 10., tau)); + gsl_matrix_set(cross_cov, 1, 0, smoothed_correlation_func(tau, 100., 10.)); // NB: != (0,1) + gsl_matrix_set(cross_cov, 1, 1, smoothed_correlation_func(tau, 100., 100.)); + gsl_matrix_set(cross_cov, 1, 2, smoothed_correlation_func(tau, 100., tau)); + gsl_matrix_set(cross_cov, 2, 0, smoothed_correlation_func(tau, tau_prev, 10.)); + gsl_matrix_set(cross_cov, 2, 1, smoothed_correlation_func(tau, tau_prev, 100.)); + gsl_matrix_set(cross_cov, 2, 2, smoothed_correlation_func(tau, tau_prev, tau)); // Current Snapshot covariance matrix // NOTE: Since the snapshot lengths are different, the snap variances are different gsl_matrix_memcpy(curr_cov, prev_cov); - gsl_matrix_set(curr_cov, 0, 2, cov_10_curr_zero); // 10_curr vs 10_curr - gsl_matrix_set(curr_cov, 1, 2, cov_100_curr_zero); // 100_curr vs 100_curr - gsl_matrix_set(curr_cov, 2, 0, cov_10_curr_zero); // 10_curr vs 10_curr - gsl_matrix_set(curr_cov, 2, 1, cov_100_curr_zero); // 100_curr vs 100_curr - gsl_matrix_set(curr_cov, 2, 2, cov_curr_curr_zero); // snap_curr vs snap_curr + gsl_matrix_set(curr_cov, 2, 0, smoothed_correlation_func(0, tau, 10.)); + gsl_matrix_set(curr_cov, 2, 1, smoothed_correlation_func(0, tau, 100.)); + gsl_matrix_set(curr_cov, 2, 2, smoothed_correlation_func(0, tau, tau)); + force_matrix_symmetric(curr_cov); + + // FOR DEBUG: The forcing of symmetry should be equivalent to: + // gsl_matrix_set(prev_cov, 0, 2, smoothed_correlation_func(0,10.,tau)); + // gsl_matrix_set(prev_cov, 1, 2, smoothed_correlation_func(0,100.,tau)); } // NOTE: This only differs from a tophat in phase. While we only use filter squared in the @@ -189,12 +236,11 @@ fftwf_complex shifted_tophat_1d(double wt) { return result; } -// TODO: I only really need a single tau per snapshot, BUT since there is no analyic iFFT for the -// PSD -// filtered by the shifted tophat, I *think* we need to do the full iFFT anyway -// to get the correlation function, even at a single tau void initialise_psd_corrfunc_tables(double tau, double tau_prev) { // For easy initialisation, we set arrays of the tables and delays used + const int N_FREQ_SFH = N_TAU_SFH / 2 + 1; // number of frequency bins in SFH + const double MAX_TAU = 2000; // Myr + RGTable1D *table_ptrs[11] = { &sfh_corr.corr_10_10, &sfh_corr.corr_10_100, &sfh_corr.corr_10_curr, &sfh_corr.corr_10_prev, &sfh_corr.corr_100_100, &sfh_corr.corr_100_curr, @@ -208,13 +254,13 @@ void initialise_psd_corrfunc_tables(double tau, double tau_prev) { for (int i = 0; i < 11; i++) { allocate_RGTable1D(N_TAU_SFH, table_ptrs[i]); - table_ptrs[i]->x_min = 0.; - table_ptrs[i]->x_width = MAX_TAU / (N_TAU_SFH - 1); + table_ptrs[i]->x_min = -MAX_TAU; + table_ptrs[i]->x_width = 2 * MAX_TAU / N_TAU_SFH; } double w_arr[N_FREQ_SFH]; for (int i = 0; i < N_FREQ_SFH; i++) { - w_arr[i] = 2 * M_PI * i / MAX_TAU; + w_arr[i] = M_PI * i / MAX_TAU; } fftwf_complex *in; @@ -232,19 +278,13 @@ void initialise_psd_corrfunc_tables(double tau, double tau_prev) { RGTable1D *curr_table = table_ptrs[i]; for (int j = 0; j < N_FREQ_SFH; j++) { double w = w_arr[j]; + double shift_term = cexp(-I * w * MAX_TAU); // shift to centre around zero lag in[j] = psd_sfh_powerlaw(w) * shifted_tophat_1d(w * t1) * - conj(shifted_tophat_1d(w * t2)); // FFTW unnormalised convention - - sum += creal(in[j]); - if (j > 0) { - sum += creal(in[j]); // account for negative frequencies - } + conj(shifted_tophat_1d(w * t2)) * shift_term; // FFTW unnormalised convention } fftwf_execute(p); - sum = 0.0; for (int j = 0; j < N_TAU_SFH; j++) { - curr_table->y_arr[j] = out[j] / MAX_TAU; - sum += curr_table->y_arr[j]; + curr_table->y_arr[j] = out[j] / (2 * MAX_TAU); } } @@ -256,57 +296,35 @@ void initialise_psd_corrfunc_tables(double tau, double tau_prev) { void fill_covar_from_tables(double tau, gsl_matrix *curr_cov, gsl_matrix *prev_cov, gsl_matrix *cross_cov) { /* Fill the covariance matrices required for sampling the SFR correctly */ - // Interpolate correlation functions at lag tau - double cov_10_10_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_10_10); - double cov_10_100_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_10_100); - double cov_10_curr_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_10_curr); - double cov_10_prev_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_10_prev); - double cov_100_100_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_100_100); - double cov_100_curr_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_100_curr); - double cov_100_prev_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_100_prev); - double cov_curr_prev_tau = EvaluateRGTable1D(tau, &sfh_corr.corr_curr_prev); - - // Get zero-lag correlations - double cov_10_10_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_10_10); - double cov_10_100_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_10_100); - double cov_10_curr_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_10_curr); - double cov_10_prev_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_10_prev); - double cov_100_100_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_100_100); - double cov_100_curr_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_100_curr); - double cov_100_prev_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_100_prev); - double cov_curr_curr_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_curr_curr); - double cov_prev_prev_zero = EvaluateRGTable1D(0.0, &sfh_corr.corr_prev_prev); - // Previous Snapshot covariance matrix - gsl_matrix_set(prev_cov, 0, 0, cov_10_10_zero); // 10_prev vs 10_prev - gsl_matrix_set(prev_cov, 0, 1, cov_10_100_zero); // 10_prev vs 100_prev - gsl_matrix_set(prev_cov, 0, 2, cov_10_prev_zero); // 10_prev vs snap_prev - gsl_matrix_set(prev_cov, 1, 0, cov_10_100_zero); // 100_prev vs 10_prev - gsl_matrix_set(prev_cov, 1, 1, cov_100_100_zero); // 100_prev vs 100_prev - gsl_matrix_set(prev_cov, 1, 2, cov_100_prev_zero); // 100_prev vs snap_prev - gsl_matrix_set(prev_cov, 2, 0, cov_10_prev_zero); // snap_prev vs 10_prev - gsl_matrix_set(prev_cov, 2, 1, cov_100_prev_zero); // snap_prev vs 100_prev - gsl_matrix_set(prev_cov, 2, 2, cov_prev_prev_zero); // snap_prev vs snap_prev - - // Lower left corner Covariance is Cov(curr,prev) == Cov(prev,curr)^T - gsl_matrix_set(cross_cov, 0, 0, cov_10_10_tau); // 10_prev vs 10_curr - gsl_matrix_set(cross_cov, 0, 1, cov_10_100_tau); // 10_prev vs 100_curr - gsl_matrix_set(cross_cov, 0, 2, cov_10_prev_tau); // 10_prev vs snap_curr - gsl_matrix_set(cross_cov, 1, 0, cov_10_100_tau); // 100_prev vs 10_curr - gsl_matrix_set(cross_cov, 1, 1, cov_100_100_tau); // 100_prev vs 100_curr - gsl_matrix_set(cross_cov, 1, 2, cov_100_prev_tau); // 100_prev vs snap_curr - gsl_matrix_set(cross_cov, 2, 0, cov_10_curr_tau); // snap_prev vs 10_curr - gsl_matrix_set(cross_cov, 2, 1, cov_100_curr_tau); // snap_prev vs 100_curr - gsl_matrix_set(cross_cov, 2, 2, cov_curr_prev_tau); // snap_prev vs snap_curr + gsl_matrix_set(prev_cov, 0, 0, EvaluateRGTable1D(0, &sfh_corr.corr_10_10)); + gsl_matrix_set(prev_cov, 1, 0, EvaluateRGTable1D(0, &sfh_corr.corr_10_100)); //-0 + gsl_matrix_set(prev_cov, 1, 1, EvaluateRGTable1D(0, &sfh_corr.corr_100_100)); + gsl_matrix_set(prev_cov, 2, 0, EvaluateRGTable1D(0, &sfh_corr.corr_10_prev)); //-0 + gsl_matrix_set(prev_cov, 2, 1, EvaluateRGTable1D(0, &sfh_corr.corr_100_prev)); //-0 + gsl_matrix_set(prev_cov, 2, 2, EvaluateRGTable1D(0, &sfh_corr.corr_prev_prev)); + force_matrix_symmetric(prev_cov); + + // Upper right corner Covariance is Cov(prev,curr) == Cov(prev,curr)^T + // The -tau is due to the asymmetry of the filter, rho(tau,t1,t2) == rho(-tau,t2,t1) + // We use -tau instead of defining tables for both directions + gsl_matrix_set(cross_cov, 0, 0, EvaluateRGTable1D(tau, &sfh_corr.corr_10_10)); + gsl_matrix_set(cross_cov, 0, 1, EvaluateRGTable1D(tau, &sfh_corr.corr_10_100)); + gsl_matrix_set(cross_cov, 0, 2, EvaluateRGTable1D(tau, &sfh_corr.corr_10_curr)); + gsl_matrix_set(cross_cov, 1, 0, EvaluateRGTable1D(-tau, &sfh_corr.corr_10_100)); // 100p x 10c + gsl_matrix_set(cross_cov, 1, 1, EvaluateRGTable1D(tau, &sfh_corr.corr_100_100)); + gsl_matrix_set(cross_cov, 1, 2, EvaluateRGTable1D(tau, &sfh_corr.corr_100_curr)); + gsl_matrix_set(cross_cov, 2, 0, EvaluateRGTable1D(-tau, &sfh_corr.corr_10_prev)); // tp x 10c + gsl_matrix_set(cross_cov, 2, 1, EvaluateRGTable1D(-tau, &sfh_corr.corr_100_prev)); // tp x 100c + gsl_matrix_set(cross_cov, 2, 2, EvaluateRGTable1D(-tau, &sfh_corr.corr_curr_prev)); // tp x tc // Current Snapshot covariance matrix // NOTE: Since the snapshot lengths are different, the snap variances are different gsl_matrix_memcpy(curr_cov, prev_cov); - gsl_matrix_set(curr_cov, 0, 2, cov_10_curr_zero); // 10_curr vs 10_curr - gsl_matrix_set(curr_cov, 1, 2, cov_100_curr_zero); // 100_curr vs 100_curr - gsl_matrix_set(curr_cov, 2, 0, cov_10_curr_zero); // 10_curr vs 10_curr - gsl_matrix_set(curr_cov, 2, 1, cov_100_curr_zero); // 100_curr vs 100_curr - gsl_matrix_set(curr_cov, 2, 2, cov_curr_curr_zero); // snap_curr vs snap_curr + gsl_matrix_set(curr_cov, 2, 0, EvaluateRGTable1D(0, &sfh_corr.corr_10_curr)); + gsl_matrix_set(curr_cov, 2, 1, EvaluateRGTable1D(0, &sfh_corr.corr_100_curr)); + gsl_matrix_set(curr_cov, 2, 2, EvaluateRGTable1D(0, &sfh_corr.corr_curr_curr)); + force_matrix_symmetric(curr_cov); } void eval_sfh_moments(gsl_matrix *prev_cov, gsl_matrix *curr_cov, gsl_matrix *cross_cov, @@ -321,42 +339,93 @@ void eval_sfh_moments(gsl_matrix *prev_cov, gsl_matrix *curr_cov, gsl_matrix *cr out_mean_correction = multiplies the condition vector, to be added to the correlated samples. */ // NOTE: Currently Cov(curr,prev) == Cov(prev,curr) == Cov(prev,curr)^T == Cov(curr,prev)^T - gsl_matrix *matrix_buf = gsl_matrix_alloc(3, 3); + gsl_matrix *partial_buf = gsl_matrix_alloc(3, 3); + gsl_matrix *covar_buf = gsl_matrix_alloc(3, 3); + gsl_matrix *L_buf = gsl_matrix_alloc(3, 3); + + print_gsl_matrix(prev_cov, "Previous Covariance Matrix:"); + print_gsl_matrix(curr_cov, "Current Covariance Matrix:"); + print_gsl_matrix(cross_cov, "Cross Covariance Matrix:"); + + gsl_matrix_memcpy(L_buf, prev_cov); // preserve for Cholesky + gsl_matrix_memcpy(partial_buf, cross_cov); // will hold L^-1 Cov(prev,curr) + gsl_matrix_memcpy(covar_buf, curr_cov); // will hold the full conditional covariance // Cholesky factorization of Cov(prev) = L L^T to do implicit inversion - gsl_linalg_cholesky_decomp1(prev_cov); // holds L + gsl_linalg_cholesky_decomp1(L_buf); // holds L // Compute the conditional covariance matrix Cov(curr|prev) = Cov(curr) - Cov(curr,prev) // Cov(prev)^-1 Cov(prev,curr) - gsl_matrix_memcpy(matrix_buf, cross_cov); - // L^-1 Cov(curr,prev) - gsl_blas_dtrsm(CblasLeft, CblasLower, CblasNoTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); + // First get L^-1 Cov(prev,curr) + gsl_blas_dtrsm(CblasLeft, CblasLower, CblasNoTrans, CblasNonUnit, 1.0, L_buf, partial_buf); - // compute Cov(curr) - BUF^T*BUF == Cov(curr) - Cov(curr|prev) Cov(prev)^-1 Cov(prev,curr) - // NOTE, curr_cov is symmetric here - gsl_blas_dsyrk(CblasLower, CblasTrans, -1.0, matrix_buf, 1.0, curr_cov); + // compute Cov(curr) - BUF^T*BUF == Cov(curr) - Cov(curr|prev) L^T^-1 L^-1 Cov(prev,curr) + // == Cov(curr) - Cov(curr,prev) Cov(prev)^-1 Cov(prev,curr) + gsl_blas_dsyrk(CblasLower, CblasTrans, -1.0, partial_buf, 1.0, covar_buf); // The lower triangle of curr_cov now holds Cov(curr|prev) // Perform Cholesky decomposition (only uses lower triangle) - gsl_linalg_cholesky_decomp1(curr_cov); - gsl_matrix_memcpy(out_chol_cov, curr_cov); + gsl_linalg_cholesky_decomp1(covar_buf); + + // Not necessary, but it makes it clearer that only the lower triangle is valid + force_matrix_lotri(covar_buf); // zero upper triangle + + gsl_matrix_memcpy(out_chol_cov, covar_buf); // Now Compute the mean correction term, since BUF was preserved from the rank-k above // Compute Cov(prev,curr) L^-T^-1 L^-1 = Cov(curr,prev) Cov(prev)^-1 - gsl_matrix_transpose(matrix_buf); - gsl_blas_dtrsm(CblasRight, CblasLower, CblasNoTrans, CblasNonUnit, 1.0, prev_cov, matrix_buf); + gsl_matrix_transpose(partial_buf); + gsl_blas_dtrsm(CblasRight, CblasLower, CblasNoTrans, CblasNonUnit, 1.0, L_buf, partial_buf); // Since, for zero mean, E[X|Y] = Cov(X,Y) Cov(Y)^-1 Y - gsl_matrix_memcpy(out_mean_correction, matrix_buf); // store for output + gsl_matrix_memcpy(out_mean_correction, partial_buf); // store for output + + gsl_matrix_free(covar_buf); + gsl_matrix_free(L_buf); + gsl_matrix_free(partial_buf); +} + +void initialise_sfh_structs(double z0, double z1, double z2) { + sfh_mats.curr_cov = gsl_matrix_alloc(3, 3); + sfh_mats.prev_cov = gsl_matrix_alloc(3, 3); + sfh_mats.pxc_cov = gsl_matrix_alloc(3, 3); + sfh_mats.L_cov = gsl_matrix_alloc(3, 3); + sfh_mats.mean_correction = gsl_matrix_alloc(3, 3); + + double tau = time_between_z(z0, z1) / (physconst.s_per_yr * 1e6); // Myr + double tau_prev = time_between_z(z1, z2) / (physconst.s_per_yr * 1e6); // Myr + + // initialise_psd_corrfunc_tables(tau, tau_prev); + // fill_covar_from_tables(tau, sfh_mats.curr_cov, sfh_mats.prev_cov, sfh_mats.pxc_cov); + fill_covar_analytic(tau, tau_prev, sfh_mats.curr_cov, sfh_mats.prev_cov, sfh_mats.pxc_cov); + eval_sfh_moments(sfh_mats.prev_cov, sfh_mats.curr_cov, sfh_mats.pxc_cov, sfh_mats.L_cov, + sfh_mats.mean_correction); +} + +void cleanup_sfh_structs() { + gsl_matrix_free(sfh_mats.curr_cov); + gsl_matrix_free(sfh_mats.prev_cov); + gsl_matrix_free(sfh_mats.pxc_cov); + gsl_matrix_free(sfh_mats.L_cov); + gsl_matrix_free(sfh_mats.mean_correction); + // free_sfh_correlation(); +} - gsl_matrix_free(prev_cov); - gsl_matrix_free(curr_cov); - gsl_matrix_free(cross_cov); - gsl_matrix_free(matrix_buf); +void free_sfh_correlation() { + free_RGTable1D(&sfh_corr.corr_10_10); + free_RGTable1D(&sfh_corr.corr_10_100); + free_RGTable1D(&sfh_corr.corr_100_100); + free_RGTable1D(&sfh_corr.corr_10_curr); + free_RGTable1D(&sfh_corr.corr_100_curr); + free_RGTable1D(&sfh_corr.corr_curr_curr); + free_RGTable1D(&sfh_corr.corr_10_prev); + free_RGTable1D(&sfh_corr.corr_100_prev); + free_RGTable1D(&sfh_corr.corr_curr_prev); + free_RGTable1D(&sfh_corr.corr_prev_prev); + free_RGTable1D(&sfh_corr.corr_zero); } -void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], gsl_matrix *L_cov, - gsl_matrix *mean_corr, double out_values[3]) { +void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], double out_values[3]) { /* Sample correlated SFH values given previous values Inputs: @@ -375,7 +444,7 @@ void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], gsl_matrix *L_co } // Multiply by Cholesky factor to get correlated samples - gsl_blas_dtrmv(CblasLower, CblasNoTrans, CblasNonUnit, L_cov, cov_term); + gsl_blas_dtrmv(CblasLower, CblasNoTrans, CblasNonUnit, sfh_mats.L_cov, cov_term); // Create a vector for the conditioned samples gsl_vector *cond_term = gsl_vector_alloc(3); @@ -383,7 +452,7 @@ void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], gsl_matrix *L_co gsl_vector_set(cond_term, i, prev_values[i]); } // Add the mean correction term - gsl_blas_dgemv(CblasNoTrans, 1.0, mean_corr, cond_term, 1.0, cov_term); + gsl_blas_dgemv(CblasNoTrans, 1.0, sfh_mats.mean_correction, cond_term, 1.0, cov_term); // Copy to output for (int i = 0; i < 3; i++) { @@ -393,34 +462,19 @@ void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], gsl_matrix *L_co gsl_vector_free(cond_term); } -void free_sfh_correlation() { - free_RGTable1D(&sfh_corr.corr_10_10); - free_RGTable1D(&sfh_corr.corr_10_100); - free_RGTable1D(&sfh_corr.corr_100_100); - free_RGTable1D(&sfh_corr.corr_10_curr); - free_RGTable1D(&sfh_corr.corr_100_curr); - free_RGTable1D(&sfh_corr.corr_curr_curr); - free_RGTable1D(&sfh_corr.corr_10_prev); - free_RGTable1D(&sfh_corr.corr_100_prev); - free_RGTable1D(&sfh_corr.corr_curr_prev); - free_RGTable1D(&sfh_corr.corr_prev_prev); - free_RGTable1D(&sfh_corr.corr_zero); -} +/* TESTING FUNCTIONS */ -void print_gsl_matrix(gsl_matrix *mat, const char *label) { - int nrows = mat->size1; - int ncols = mat->size2; - fprintf(stdout, "%s\n", label); - for (int i = 0; i < nrows; i++) { - for (int j = 0; j < ncols; j++) { - fprintf(stdout, "%9.4f ", gsl_matrix_get(mat, i, j)); - } - fprintf(stdout, "\n"); +void print_corrfunc(RGTable1D *ptr, const char *name, int skip_lines) { + fprintf(stdout, "Correlation Function Table: %s", name); + for (int i = 0; i < N_TAU_SFH / skip_lines; i += skip_lines) { + double lag = ptr->x_min + i * ptr->x_width; + double val = ptr->y_arr[i]; + fprintf(stdout, "Lag: %.2f Value: %.6f \n", lag, val); } } int test_sfh_corr(double z0, double z1, double z2) { - fprintf(stdout, "Testing SFH matrices at z=%f, %f, %f\n", z0, z1, z2); + double rel_tol = 1e-2; double tau = time_between_z(z0, z1) / (physconst.s_per_yr * 1e6); // Myr double tau_prev = time_between_z(z1, z2) / (physconst.s_per_yr * 1e6); // Myr initialise_psd_corrfunc_tables(tau, tau_prev); @@ -440,59 +494,117 @@ int test_sfh_corr(double z0, double z1, double z2) { "100Myr x 100Myr", "100Myr x Curr", "100Myr x Prev", "Curr x Curr", "Curr x Prev", "Prev x Prev", "Zero Lag"}; - fprintf(stdout, "====== Correlation functions ======\n"); for (int i = 0; i < 10; i++) { - fprintf(stdout, "%s (t1=%f, t2=%f):\n", names[i], delays[i][0], delays[i][1]); for (double lag = 0.0; lag <= 500.0; lag += 50.0) { double val_tables = EvaluateRGTable1D(lag, table_ptrs[i]); double val_analytic = smoothed_correlation_func(lag, delays[i][0], delays[i][1]); - fprintf(stdout, " Lag %7.2f Tables: %9.4f Analytic: %9.4f Ratio: %9.4f\n", lag, - val_tables, val_analytic, val_tables / val_analytic); + if (fabs(val_tables / val_analytic - 1.0) > rel_tol) { + LOG_ERROR("Test Failed for %s at lag %f: Tables %f Analytic %f Ratio %f", names[i], + lag, val_tables, val_analytic, val_tables / val_analytic); + print_corrfunc(table_ptrs[i], names[i], 10); + Throw(TableGenerationError); + } } } - fprintf(stdout, "===== zero smoothing =====\n"); + for (double lag = 0.0; lag <= 500.0; lag += 50.0) { double val_tables = EvaluateRGTable1D(lag, &sfh_corr.corr_zero); double val_analytic = exp(-fabs(lag) / astro_params_global->SFH_TAU) * astro_params_global->SIGMA_STAR * astro_params_global->SIGMA_STAR; - fprintf(stdout, " Lag %7.2f Tables: %9.4f Analytic: %9.4f Ratio: %9.4f\n", lag, val_tables, - val_analytic, val_tables / val_analytic); + if (fabs(val_tables / val_analytic - 1.0) > rel_tol) { + LOG_ERROR("Test Failed for %s at lag %f: Tables %f Analytic %f Ratio %f", names[10], + lag, val_tables, val_analytic, val_tables / val_analytic); + print_corrfunc(table_ptrs[10], names[10], 50); + Throw(TableGenerationError); + } } - fprintf(stdout, "====== PSD Covariance Matrices ======\n"); + fprintf(stdout, "sigma = %f, sq = %f", astro_params_global->SIGMA_STAR, + astro_params_global->SIGMA_STAR * astro_params_global->SIGMA_STAR); + gsl_matrix *curr_cov = gsl_matrix_alloc(3, 3); gsl_matrix *prev_cov = gsl_matrix_alloc(3, 3); gsl_matrix *cross_cov = gsl_matrix_alloc(3, 3); fill_covar_from_tables(tau, curr_cov, prev_cov, cross_cov); - print_gsl_matrix(prev_cov, "Previous Covariance Matrix:"); - print_gsl_matrix(cross_cov, "Cross Covariance Matrix:"); - print_gsl_matrix(curr_cov, "Current Covariance Matrix:"); - - fprintf(stdout, "====== Analytic Covariance Matrices ======\n"); gsl_matrix *curr_cov_2 = gsl_matrix_alloc(3, 3); gsl_matrix *prev_cov_2 = gsl_matrix_alloc(3, 3); gsl_matrix *cross_cov_2 = gsl_matrix_alloc(3, 3); fill_covar_analytic(tau, tau_prev, curr_cov_2, prev_cov_2, cross_cov_2); - print_gsl_matrix(prev_cov_2, "Previous Covariance Matrix:"); - print_gsl_matrix(cross_cov_2, "Cross Covariance Matrix:"); - print_gsl_matrix(curr_cov_2, "Current Covariance Matrix:"); - gsl_matrix_div_elements(prev_cov_2, prev_cov); - gsl_matrix_div_elements(cross_cov_2, cross_cov); - gsl_matrix_div_elements(curr_cov_2, curr_cov); - print_gsl_matrix(prev_cov_2, "Previous Covariance Matrix Ratio:"); - print_gsl_matrix(cross_cov_2, "Cross Covariance Matrix Ratio:"); - print_gsl_matrix(curr_cov_2, "Current Covariance Matrix Ratio:"); + gsl_matrix *corrcoev = gsl_matrix_alloc(6, 6); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + gsl_matrix_set(corrcoev, i, j, gsl_matrix_get(prev_cov, i, j)); + gsl_matrix_set(corrcoev, i, j + 3, gsl_matrix_get(cross_cov, i, j)); + gsl_matrix_set(corrcoev, i + 3, j, gsl_matrix_get(cross_cov, j, i)); + gsl_matrix_set(corrcoev, i + 3, j + 3, gsl_matrix_get(curr_cov, i, j)); + } + } + print_gsl_matrix(corrcoev, "Covariance Matrix:"); + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 6; j++) { + gsl_matrix_set( + corrcoev, i, j, + sqrt(gsl_matrix_get(corrcoev, i, j) / + sqrt(gsl_matrix_get(corrcoev, i, i) * gsl_matrix_get(corrcoev, j, j)))); + } + } + print_gsl_matrix(corrcoev, "Correlation Coefficient Matrix:"); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + double val_1 = gsl_matrix_get(prev_cov, i, j); + double val_2 = gsl_matrix_get(prev_cov_2, i, j); + if (fabs(val_1 / val_2 - 1.0) > rel_tol) { + LOG_ERROR( + "Test Failed for Previous Covariance Matrix at (%d,%d): Tables %f Analytic %f " + "Ratio %f", + i, j, val_1, val_2, val_1 / val_2); + print_gsl_matrix(prev_cov, "Previous Covariance Matrix from Tables:"); + print_gsl_matrix(prev_cov_2, "Previous Covariance Matrix from Analytic:"); + Throw(TableGenerationError); + } + val_1 = gsl_matrix_get(cross_cov, i, j); + val_2 = gsl_matrix_get(cross_cov_2, i, j); + if (fabs(val_1 / val_2 - 1.0) > rel_tol) { + LOG_ERROR( + "Test Failed for Cross Covariance Matrix at (%d,%d): Tables %f Analytic %f " + "Ratio %f", + i, j, val_1, val_2, val_1 / val_2); + print_gsl_matrix(cross_cov, "Cross Covariance Matrix from Tables:"); + print_gsl_matrix(cross_cov_2, "Cross Covariance Matrix from Analytic:"); + Throw(TableGenerationError); + } + val_1 = gsl_matrix_get(curr_cov, i, j); + val_2 = gsl_matrix_get(curr_cov_2, i, j); + if (fabs(val_1 / val_2 - 1.0) > rel_tol) { + LOG_ERROR( + "Test Failed for Current Covariance Matrix at (%d,%d): Tables %f Analytic %f " + "Ratio %f", + i, j, val_1, val_2, val_1 / val_2); + print_gsl_matrix(curr_cov, "Current Covariance Matrix from Tables:"); + print_gsl_matrix(curr_cov_2, "Current Covariance Matrix from Analytic:"); + Throw(TableGenerationError); + } + } + } gsl_matrix *L_cov = gsl_matrix_alloc(3, 3); gsl_matrix *mean_corr = gsl_matrix_alloc(3, 3); - eval_sfh_moments(prev_cov, curr_cov, cross_cov, L_cov, mean_corr); + eval_sfh_moments(prev_cov_2, curr_cov_2, cross_cov_2, L_cov, mean_corr); print_gsl_matrix(mean_corr, "Mean Correction Matrix:"); print_gsl_matrix(L_cov, "Cholesky Factor of Conditioned Covariance Matrix:"); // get the conditioned covariance matrix - gsl_blas_dtrmm(CblasRight, CblasLower, CblasTrans, CblasNonUnit, 1.0, L_cov, L_cov); - print_gsl_matrix(L_cov, "Conditioned Covariance Matrix:"); + gsl_matrix_set(L_cov, 0, 1, 0.0); + gsl_matrix_set(L_cov, 0, 2, 0.0); + gsl_matrix_set(L_cov, 1, 2, 0.0); + gsl_matrix_memcpy(curr_cov, L_cov); // using curr_cov as buffer + gsl_matrix_set(curr_cov, 0, 1, 0.0); + gsl_matrix_set(curr_cov, 0, 2, 0.0); + gsl_matrix_set(curr_cov, 1, 2, 0.0); + gsl_blas_dtrmm(CblasRight, CblasLower, CblasTrans, CblasNonUnit, 1.0, L_cov, curr_cov); + print_gsl_matrix(curr_cov, "Conditioned Covariance Matrix:"); free_sfh_correlation(); return 0; diff --git a/src/py21cmfast/src/correlated_sfh.h b/src/py21cmfast/src/correlated_sfh.h index 14d3b72e4..005e6f157 100644 --- a/src/py21cmfast/src/correlated_sfh.h +++ b/src/py21cmfast/src/correlated_sfh.h @@ -5,15 +5,9 @@ #ifndef CORRELATED_SFH_H #define CORRELATED_SFH_H -void initialise_psd_corrfunc_tables(double tau, double tau_prev); -void fill_covar_from_tables(double tau, gsl_matrix *curr_cov, gsl_matrix *prev_cov, - gsl_matrix *cross_cov); -void fill_covar_analytic(double tau, double tau_prev, gsl_matrix *curr_cov, gsl_matrix *prev_cov, - gsl_matrix *cross_cov); -void eval_sfh_moments(gsl_matrix *prev_cov, gsl_matrix *curr_cov, gsl_matrix *cross_cov, - gsl_matrix *out_chol_cov, gsl_matrix *out_mean_correction); -void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], gsl_matrix *L_cov, - gsl_matrix *mean_corr, double out_values[3]); -void free_sfh_correlation(); +void initialise_sfh_structs(double z0, double z1, double z2); +int test_sfh_corr(double z0, double z1, double z2); +void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], double out_values[3]); +void cleanup_sfh_structs(); #endif From b5db7cfda91079045dde5f0aa8dd306b272409a8 Mon Sep 17 00:00:00 2001 From: James Davies Date: Wed, 29 Oct 2025 17:22:28 +1100 Subject: [PATCH 10/18] verified that the matrices match --- src/py21cmfast/src/correlated_sfh.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/py21cmfast/src/correlated_sfh.c b/src/py21cmfast/src/correlated_sfh.c index 4dbbdd371..db88a9e6d 100644 --- a/src/py21cmfast/src/correlated_sfh.c +++ b/src/py21cmfast/src/correlated_sfh.c @@ -343,10 +343,6 @@ void eval_sfh_moments(gsl_matrix *prev_cov, gsl_matrix *curr_cov, gsl_matrix *cr gsl_matrix *covar_buf = gsl_matrix_alloc(3, 3); gsl_matrix *L_buf = gsl_matrix_alloc(3, 3); - print_gsl_matrix(prev_cov, "Previous Covariance Matrix:"); - print_gsl_matrix(curr_cov, "Current Covariance Matrix:"); - print_gsl_matrix(cross_cov, "Cross Covariance Matrix:"); - gsl_matrix_memcpy(L_buf, prev_cov); // preserve for Cholesky gsl_matrix_memcpy(partial_buf, cross_cov); // will hold L^-1 Cov(prev,curr) gsl_matrix_memcpy(covar_buf, curr_cov); // will hold the full conditional covariance @@ -477,6 +473,8 @@ int test_sfh_corr(double z0, double z1, double z2) { double rel_tol = 1e-2; double tau = time_between_z(z0, z1) / (physconst.s_per_yr * 1e6); // Myr double tau_prev = time_between_z(z1, z2) / (physconst.s_per_yr * 1e6); // Myr + fprintf(stdout, "Testing SFH Correlation Functions at tau = %f Myr, tau_prev = %f Myr\n", tau, + tau_prev); initialise_psd_corrfunc_tables(tau, tau_prev); RGTable1D *table_ptrs[11] = { @@ -519,7 +517,7 @@ int test_sfh_corr(double z0, double z1, double z2) { } } - fprintf(stdout, "sigma = %f, sq = %f", astro_params_global->SIGMA_STAR, + fprintf(stdout, "sigma = %f, sq = %f\n", astro_params_global->SIGMA_STAR, astro_params_global->SIGMA_STAR * astro_params_global->SIGMA_STAR); gsl_matrix *curr_cov = gsl_matrix_alloc(3, 3); @@ -531,6 +529,10 @@ int test_sfh_corr(double z0, double z1, double z2) { gsl_matrix *cross_cov_2 = gsl_matrix_alloc(3, 3); fill_covar_analytic(tau, tau_prev, curr_cov_2, prev_cov_2, cross_cov_2); + print_gsl_matrix(prev_cov, "Previous Covariance Matrix:"); + print_gsl_matrix(curr_cov, "Current Covariance Matrix:"); + print_gsl_matrix(cross_cov, "Cross Covariance Matrix:"); + gsl_matrix *corrcoev = gsl_matrix_alloc(6, 6); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { From e2e454c4538848eac7fbf1aa4a8c405546616ee7 Mon Sep 17 00:00:00 2001 From: James Davies Date: Fri, 21 Nov 2025 14:34:18 +1100 Subject: [PATCH 11/18] WIP --- src/py21cmfast/drivers/single_field.py | 13 ++ src/py21cmfast/src/HaloBox.c | 70 ++++++---- src/py21cmfast/src/HaloBox.h | 6 +- src/py21cmfast/src/HaloCatalog.c | 20 ++- src/py21cmfast/src/Stochasticity.c | 88 ++++-------- src/py21cmfast/src/Stochasticity.h | 6 +- src/py21cmfast/src/_outputstructs_wrapper.h | 18 ++- src/py21cmfast/src/correlated_sfh.c | 56 +++++++- src/py21cmfast/src/correlated_sfh.h | 4 +- src/py21cmfast/src/hmf.c | 2 +- src/py21cmfast/src/interp_tables.c | 18 +-- src/py21cmfast/src/map_mass.c | 23 ++-- src/py21cmfast/src/map_mass.h | 8 +- src/py21cmfast/src/scaling_relations.c | 145 +++++++++----------- src/py21cmfast/src/scaling_relations.h | 21 ++- src/py21cmfast/wrapper/outputs.py | 34 +++-- 16 files changed, 301 insertions(+), 231 deletions(-) diff --git a/src/py21cmfast/drivers/single_field.py b/src/py21cmfast/drivers/single_field.py index 32dbe3149..52fd27457 100644 --- a/src/py21cmfast/drivers/single_field.py +++ b/src/py21cmfast/drivers/single_field.py @@ -237,6 +237,7 @@ def compute_halo_grid( initial_conditions: InitialConditions, inputs: InputParameters | None = None, halo_catalog: HaloCatalog | None = None, + previous_halo_catalog: HaloCatalog | None = None, previous_spin_temp: TsBox | None = None, previous_ionize_box: IonizedBox | None = None, ) -> HaloBox: @@ -281,6 +282,17 @@ def compute_halo_grid( else: halo_catalog = HaloCatalog.dummy() + if previous_halo_catalog is None: + if ( + inputs.matter_options.has_discrete_halos + and redshift < inputs.simulation_options.Z_HEAT_MAX + ): + raise ValueError( + "You must provide previous_halo_catalog for discrete halo models below Z_HEAT_MAX" + ) + else: + previous_halo_catalog = HaloCatalog.dummy() + # NOTE: due to the order, we use the previous spin temp here, like spin_temperature, # but UNLIKE ionize_box, which uses the current box # TODO: think about the inconsistency here @@ -310,6 +322,7 @@ def compute_halo_grid( return box.compute( initial_conditions=initial_conditions, halo_catalog=halo_catalog, + previous_halo_catalog=previous_halo_catalog, previous_ionize_box=previous_ionize_box, previous_spin_temp=previous_spin_temp, ) diff --git a/src/py21cmfast/src/HaloBox.c b/src/py21cmfast/src/HaloBox.c index c38312bbf..9d078c6f2 100644 --- a/src/py21cmfast/src/HaloBox.c +++ b/src/py21cmfast/src/HaloBox.c @@ -53,25 +53,24 @@ void set_integral_constants(IntegralCondition *consts, double redshift, double M // we treat the minihalos as a shift in the mean, where each halo will have both components, // representing a smooth transition in halo mass from one set of SFR/emmissivity parameters to the // other. -void set_halo_properties(double halo_mass, double M_turn_a, double M_turn_m, - ScalingConstants *consts, double *input_rng, HaloProperties *output) { +void set_halo_properties(double halo_mass, double M_turn_a, double M_turn_m, double prog_hm, + double prog_sm[2], ScalingConstants *consts, double *input_rng, + HaloProperties *output) { double n_ion_sample, wsfr_sample; double fesc; double fesc_mini = 0.; - double stellar_mass, stellar_mass_mini; - get_halo_stellarmass(halo_mass, M_turn_a, M_turn_m, input_rng[0], consts, &stellar_mass, - &stellar_mass_mini); + double sfh[3], sfh_mini[3]; + get_halo_sfh(halo_mass, M_turn_a, M_turn_m, prog_hm, prog_sm, input_rng, consts, sfh, sfh_mini); - double sfr, sfr_mini; - get_halo_sfr(stellar_mass, stellar_mass_mini, input_rng[1], consts, &sfr, &sfr_mini); + // SFH holds SFR over 10Myr[0] and 100Myr[1], total formed stellar mass [2] double metallicity = 0; double xray_lum = 0; if (astro_options_global->USE_TS_FLUCT) { - get_halo_metallicity(sfr + sfr_mini, stellar_mass + stellar_mass_mini, consts->redshift, + get_halo_metallicity(sfh[2] + sfh_mini[2], sfh[0] + sfh_mini[0], consts->redshift, &metallicity); - get_halo_xray(sfr, sfr_mini, metallicity, input_rng[2], consts, &xray_lum); + get_halo_xray(sfh[0], sfh_mini[0], metallicity, input_rng[2], consts, &xray_lum); } // no rng for escape fraction yet @@ -79,15 +78,18 @@ void set_halo_properties(double halo_mass, double M_turn_a, double M_turn_m, if (astro_options_global->USE_MINI_HALOS) fesc_mini = fmin(consts->fesc_7 * pow(halo_mass / 1e7, consts->alpha_esc), 1); - n_ion_sample = - stellar_mass * consts->pop2_ion * fesc + stellar_mass_mini * consts->pop3_ion * fesc_mini; - wsfr_sample = sfr * consts->pop2_ion * fesc + sfr_mini * consts->pop3_ion * fesc_mini; + n_ion_sample = sfh[2] * consts->pop2_ion * fesc + sfh_mini[2] * consts->pop3_ion * fesc_mini; + + // for ionising/gamma12 + wsfr_sample = sfh[0] * consts->pop2_ion * fesc + sfh_mini[0] * consts->pop3_ion * fesc_mini; output->halo_mass = halo_mass; - output->stellar_mass = stellar_mass; - output->stellar_mass_mini = stellar_mass_mini; - output->halo_sfr = sfr; - output->sfr_mini = sfr_mini; + output->stellar_mass = sfh[2]; + output->stellar_mass_mini = sfh_mini[2]; + output->sfr_10 = sfh[0]; + output->sfr_100 = sfh[1]; + output->sfr_10_mcg = sfh_mini[0]; + output->sfr_100_mcg = sfh_mini[1]; output->fescweighted_sfr = wsfr_sample; output->n_ion = n_ion_sample; output->metallicity = metallicity; @@ -504,13 +506,25 @@ void get_log10_turnovers(InitialConditions *ini_boxes, TsBox *previous_spin_temp } void sum_halos_onto_grid(double redshift, InitialConditions *ini_boxes, HaloCatalog *halos, - float *mturn_a_grid, float *mturn_m_grid, ScalingConstants *consts, - HaloBox *grids) { + HaloCatalog *halos_prev, float *mturn_a_grid, float *mturn_m_grid, + ScalingConstants *consts, HaloBox *grids) { float *vel_pointers[3]; float *vel_pointers_2LPT[3]; int vel_dim[3]; int out_dim[3] = {simulation_options_global->HII_DIM, simulation_options_global->HII_DIM, HII_D_PARA}; // always output to lowres grid + + // TODO: these are large allocations, try to find a better way + float *progenitor_hm = calloc(halos->n_halos, sizeof(float)); + float *progenitor_sm = calloc(halos->n_halos, sizeof(float)); + float *progenitor_sm_mini = calloc(halos->n_halos, sizeof(float)); + for (unsigned long long int i = 0; i < halos_prev->n_halos; i++) { + int index = halos->descendant_index[i]; + progenitor_hm[index] += halos_prev->halo_masses[i]; + progenitor_sm[index] += halos_prev->stellar_mass[i]; + progenitor_sm_mini[index] += halos_prev->sfr_100[i]; // TODO: naming + } + if (matter_options_global->PERTURB_ON_HIGH_RES) { vel_dim[0] = simulation_options_global->DIM; vel_dim[1] = simulation_options_global->DIM; @@ -532,8 +546,9 @@ void sum_halos_onto_grid(double redshift, InitialConditions *ini_boxes, HaloCata vel_pointers_2LPT[1] = ini_boxes->lowres_vy_2LPT; vel_pointers_2LPT[2] = ini_boxes->lowres_vz_2LPT; } - move_halo_galprops(redshift, halos, vel_pointers, vel_pointers_2LPT, vel_dim, mturn_a_grid, - mturn_m_grid, grids, out_dim, consts); + move_halo_galprops(redshift, halos, progenitor_hm, progenitor_sm, vel_pointers, + vel_pointers_2LPT, vel_dim, mturn_a_grid, mturn_m_grid, grids, out_dim, + consts); LOG_SUPER_DEBUG("Cell 0 Totals: SF: %.2e NI: %.2e", grids->halo_sfr[0], grids->n_ion[0]); if (astro_options_global->INHOMO_RECO) { @@ -549,10 +564,17 @@ void sum_halos_onto_grid(double redshift, InitialConditions *ini_boxes, HaloCata // We grid a PERTURBED halofield into the necessary quantities for calculating radiative backgrounds int ComputeHaloBox(double redshift, InitialConditions *ini_boxes, HaloCatalog *halos, - TsBox *previous_spin_temp, IonizedBox *previous_ionize_box, HaloBox *grids) { + HaloCatalog *halos_prev, TsBox *previous_spin_temp, + IonizedBox *previous_ionize_box, HaloBox *grids) { int status; Try { // get parameters + if (halos->sfh_computed || !halos_prev->sfh_computed) { + LOG_ERROR( + "previous Halo catalogues must have SFRs computed before calling ComputeHaloBox." + "And current catalogues must not have SFRs computed."); + Throw(ValueError); + } #if LOG_LEVEL >= SUPER_DEBUG_LEVEL writeSimulationOptions(simulation_options_global); @@ -561,9 +583,6 @@ int ComputeHaloBox(double redshift, InitialConditions *ini_boxes, HaloCatalog *h writeAstroParams(astro_params_global); writeAstroOptions(astro_options_global); #endif - - LOG_DEBUG("Resetting halobox dim %d %llu %llu", simulation_options_global->HII_DIM, - HII_D_PARA, HII_TOT_NUM_PIXELS); unsigned long long int idx; #pragma omp parallel for num_threads(simulation_options_global->N_THREADS) private(idx) for (idx = 0; idx < HII_TOT_NUM_PIXELS; idx++) { @@ -633,6 +652,8 @@ int ComputeHaloBox(double redshift, InitialConditions *ini_boxes, HaloCatalog *h } halobox_debug_print_avg(grids, &hbox_consts, M_min, M_MAX_INTEGRAL); + halos->sfr_computed = true; + if (astro_options_global->USE_MINI_HALOS) { free(mturn_a_grid); free(mturn_m_grid); @@ -820,7 +841,6 @@ int convert_halo_props(double redshift, InitialConditions *ics, TsBox *prev_ts, continue; } - // the coordinates are already done in PerturbedHaloCatalog halo_pos[0] = halo_catalog_out->halo_coords[3 * i_halo + 0] * box_to_lores_factor; halo_pos[1] = halo_catalog_out->halo_coords[3 * i_halo + 1] * box_to_lores_factor; halo_pos[2] = halo_catalog_out->halo_coords[3 * i_halo + 2] * box_to_lores_factor; diff --git a/src/py21cmfast/src/HaloBox.h b/src/py21cmfast/src/HaloBox.h index ced4732f7..2f3aaecb4 100644 --- a/src/py21cmfast/src/HaloBox.h +++ b/src/py21cmfast/src/HaloBox.h @@ -17,9 +17,11 @@ typedef struct HaloProperties { double count; // from integral double halo_mass; double stellar_mass; - double halo_sfr; double stellar_mass_mini; - double sfr_mini; + double sfr_10; + double sfr_100; + double sfr_10_mcg; + double sfr_100_mcg; double fescweighted_sfr; double n_ion; double halo_xray; diff --git a/src/py21cmfast/src/HaloCatalog.c b/src/py21cmfast/src/HaloCatalog.c index 052365dc8..3b26e4ba6 100644 --- a/src/py21cmfast/src/HaloCatalog.c +++ b/src/py21cmfast/src/HaloCatalog.c @@ -35,15 +35,23 @@ int pixel_in_halo(int grid_dim, int z_dim, int x, int x_index, int y, int y_inde int z_index, float Rsq_curr_index); void free_halo_catalog(HaloCatalog *halos); -int ComputeHaloCatalog(float redshift_desc, float redshift, InitialConditions *boxes, - unsigned long long int random_seed, HaloCatalog *halos_desc, - HaloCatalog *halos) { +int ComputeHaloCatalog(float redshift_desc2, float redshift_desc, float redshift, + InitialConditions *boxes, unsigned long long int random_seed, + HaloCatalog *halos_desc, HaloCatalog *halos) { int status; Try { // This Try brackets the whole function, so we don't indent. - + bool from_catalog = matter_options_global->SOURCE_MODEL == 4 && redshift_desc > 0; + if (halos->sfh_computed || halos_desc->sfh_computed) { + LOG_ERROR( + "You have passed a halo catalog with SFH already computed to the stochastic " + "sampler. " + "This is not allowed."); + Throw(ValueError); + } + initialise_sfh_structs(redshift, redshift_desc, redshift_desc2, from_catalog); // This happens if we are updating a halo field (no need to redo big halos) - if (matter_options_global->SOURCE_MODEL == 4 && redshift_desc > 0) { + if (from_catalog) { LOG_DEBUG("Halo sampling switched on, bypassing halo finder to update %llu halos...", halos_desc->n_halos); // this would hold the two boxes used in the halo sampler, but here we are taking the @@ -423,6 +431,8 @@ int ComputeHaloCatalog(float redshift_desc, float redshift, InitialConditions *b fftwf_free(density_field); fftwf_free(density_field_saved); + cleanup_sfh_structs(); + fftwf_cleanup_threads(); fftwf_cleanup(); fftwf_forget_wisdom(); diff --git a/src/py21cmfast/src/Stochasticity.c b/src/py21cmfast/src/Stochasticity.c index 6a2bd3fcc..c038cf497 100644 --- a/src/py21cmfast/src/Stochasticity.c +++ b/src/py21cmfast/src/Stochasticity.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -14,6 +15,7 @@ #include "InputParameters.h" #include "OutputStructs.h" #include "cexcept.h" +#include "correlated_sfh.h" #include "cosmology.h" #include "exceptions.h" #include "hmf.h" @@ -76,7 +78,7 @@ double sample_dndM_inverse(double condition, struct HaloSamplingConstants *hs_co // Set the constants that are calculated once per snapshot void stoc_set_consts_z(struct HaloSamplingConstants *const_struct, double redshift, - double redshift_desc) { + double redshift_desc, bool from_catalog) { if (redshift_desc > 0 && redshift < redshift_desc) { LOG_ERROR("you have passed a descendant redshift above the progenitor redshift"); Throw(ValueError); @@ -106,25 +108,8 @@ void stoc_set_consts_z(struct HaloSamplingConstants *const_struct, double redshi const_struct->sigma_min = EvaluateSigma(const_struct->lnM_min); - if (redshift_desc > 0) { - const_struct->growth_in = dicke(redshift_desc); - if (simulation_options_global->CORR_SFR > 0) - const_struct->corr_sfr = - exp(-(redshift - redshift_desc) / simulation_options_global->CORR_SFR); - else - const_struct->corr_sfr = 0; - if (simulation_options_global->CORR_STAR > 0) - const_struct->corr_star = - exp(-(redshift - redshift_desc) / simulation_options_global->CORR_STAR); - else - const_struct->corr_star = 0; - if (simulation_options_global->CORR_LX > 0) - const_struct->corr_xray = - exp(-(redshift - redshift_desc) / simulation_options_global->CORR_LX); - else - const_struct->corr_xray = 0; - - const_struct->from_catalog = 1; + if (from_catalog) { + const_struct->from_catalog = true; initialise_dNdM_tables(log(simulation_options_global->SAMPLER_MIN_MASS), const_struct->lnM_max_tb, const_struct->lnM_min, const_struct->lnM_max_tb, const_struct->growth_out, @@ -146,7 +131,7 @@ void stoc_set_consts_z(struct HaloSamplingConstants *const_struct, double redshi // for the table limits double delta_crit = get_delta_crit(matter_options_global->HMF, const_struct->sigma_cond, const_struct->growth_out); - const_struct->from_catalog = 0; + const_struct->from_catalog = false; // TODO: determine the minimum density in the field and pass it in (<-1 is fine for // Lagrangian) initialise_dNdM_tables(DELTA_MIN, MAX_DELTAC_FRAC * delta_crit, const_struct->lnM_min, @@ -213,29 +198,6 @@ void stoc_set_consts_cond(struct HaloSamplingConstants *const_struct, double con return; } -// This function adds stochastic halo properties to an existing halo -void set_prop_rng(gsl_rng *rng, bool from_catalog, double *interp, double *input, double *output) { - double rng_star, rng_sfr, rng_xray; - - // Correlate properties by interpolating between the sampled and descendant gaussians - rng_star = gsl_ran_ugaussian(rng); - rng_sfr = gsl_ran_ugaussian(rng); - rng_xray = gsl_ran_ugaussian(rng); - - if (from_catalog) { - // this transforms the sample to one from the multivariate Gaussian, - // conditioned on the first sample - rng_star = sqrt(1 - interp[0] * interp[0]) * rng_star + interp[0] * input[0]; - rng_sfr = sqrt(1 - interp[1] * interp[1]) * rng_sfr + interp[1] * input[1]; - rng_xray = sqrt(1 - interp[2] * interp[2]) * rng_xray + interp[2] * input[2]; - } - - output[0] = rng_star; - output[1] = rng_sfr; - output[2] = rng_xray; - return; -} - // This is the function called to assign halo properties to an entire catalogue, used for DexM halos int add_properties_cat(unsigned long long int seed, float redshift, HaloCatalog *halos) { // set up the rng @@ -247,13 +209,16 @@ int add_properties_cat(unsigned long long int seed, float redshift, HaloCatalog // loop through the halos and assign properties unsigned long long int i; double buf[3]; - double dummy[3]; // we don't need interpolation here + // Assume no correlation across snapshots for now, when this function is used + // This is currently only called in DexM halo assignment, where we don't have progenitor or + // descendant information + double prev_values[3] = {0., 0., 0.}; #pragma omp parallel for private(i, buf) for (i = 0; i < halos->n_halos; i++) { - set_prop_rng(rng_stoc[omp_get_thread_num()], false, dummy, dummy, buf); - halos->star_rng[i] = buf[0]; - halos->sfr_rng[i] = buf[1]; - halos->xray_rng[i] = buf[2]; + sample_correlated_sfh(rng_stoc[omp_get_thread_num()], prev_values, buf); + halos->sfr_10[i] = buf[0]; + halos->sfr_100[i] = buf[1]; + halos->stellar_mass[i] = buf[2]; } free_rng_threads(rng_stoc); @@ -801,7 +766,8 @@ int sample_halo_grids(gsl_rng **rng_arr, double redshift, float *dens_field, int nh_buf; double delta; - double prop_buf[3], prop_dummy[3]; + double prop_buf[3]; + double prop_dummy[3] = {0., 0., 0.}; double crd_hi[3]; double mass_defc; @@ -880,10 +846,10 @@ int sample_halo_grids(gsl_rng **rng_arr, double redshift, float *dens_field, halofield_out->halo_coords[3 * (istart + count) + 1] = crd_hi[1]; halofield_out->halo_coords[3 * (istart + count) + 2] = crd_hi[2]; - set_prop_rng(rng_arr[threadnum], false, prop_dummy, prop_dummy, prop_buf); - halofield_out->star_rng[istart + count] = prop_buf[0]; - halofield_out->sfr_rng[istart + count] = prop_buf[1]; - halofield_out->xray_rng[istart + count] = prop_buf[2]; + sample_correlated_sfh(rng_arr[threadnum], prop_dummy, prop_buf); + halofield_out->sfr_rng_10[istart + count] = prop_buf[0]; + halofield_out->sfr_rng_100[istart + count] = prop_buf[1]; + halofield_out->sfr_rng_snapshot[istart + count] = prop_buf[2]; count++; M_tot_cell += hm_buf[i]; @@ -1015,7 +981,7 @@ int sample_halo_progenitors(gsl_rng **rng_arr, double z_in, double z_out, HaloCa continue; } - set_prop_rng(rng_arr[threadnum], true, corr_arr, propbuf_in, propbuf_out); + sample_correlated_sfh(rng_arr[threadnum], propbuf_in, propbuf_out); halofield_out->halo_masses[istart + count] = prog_buf[jj]; @@ -1032,6 +998,7 @@ int sample_halo_progenitors(gsl_rng **rng_arr, double z_in, double z_out, HaloCa halofield_out->star_rng[istart + count] = propbuf_out[0]; halofield_out->sfr_rng[istart + count] = propbuf_out[1]; halofield_out->xray_rng[istart + count] = propbuf_out[2]; + halofield_out->descendant_index[istart + count] = ii; count++; if (ii == 0) { @@ -1078,14 +1045,15 @@ int sample_halo_progenitors(gsl_rng **rng_arr, double z_in, double z_out, HaloCa } // function that talks between the structures (Python objects) and the sampling functions -int stochastic_halofield(unsigned long long int seed, float redshift_desc, float redshift, - float *dens_field, float *halo_overlap_box, HaloCatalog *halos_desc, - HaloCatalog *halos) { +int stochastic_halofield(unsigned long long int seed, float redshift_desc2, float redshift_desc, + float redshift, float *dens_field, float *halo_overlap_box, + HaloCatalog *halos_desc, HaloCatalog *halos) { if (redshift_desc > 0 && halos_desc->n_halos == 0) { LOG_DEBUG("No halos to sample from redshifts %.2f to %.2f, continuing...", redshift_desc, redshift); return 0; } + bool from_catalog = (redshift_desc > 0.); // set up the rng gsl_rng *rng_stoc[simulation_options_global->N_THREADS]; @@ -1093,11 +1061,11 @@ int stochastic_halofield(unsigned long long int seed, float redshift_desc, float seed_rng_threads_fast(rng_stoc, seed + seed_fac); struct HaloSamplingConstants hs_constants; - stoc_set_consts_z(&hs_constants, redshift, redshift_desc); + stoc_set_consts_z(&hs_constants, redshift, redshift_desc, from_catalog); // Fill them // NOTE:Halos prev in the first box corresponds to the large DexM halos - if (redshift_desc <= 0.) { + if (!from_catalog) { LOG_DEBUG("building first halo field at z=%.1f", redshift); sample_halo_grids(rng_stoc, redshift, dens_field, halo_overlap_box, halos_desc, halos, &hs_constants); diff --git a/src/py21cmfast/src/Stochasticity.h b/src/py21cmfast/src/Stochasticity.h index 667ab8b15..090ce79df 100644 --- a/src/py21cmfast/src/Stochasticity.h +++ b/src/py21cmfast/src/Stochasticity.h @@ -1,6 +1,8 @@ #ifndef _STOCHASTICITY_H #define _STOCHASTICITY_H +#include + #include "InputParameters.h" #include "OutputStructs.h" @@ -10,7 +12,7 @@ // can be set with differing frequencies depending on the condition type struct HaloSamplingConstants { // calculated per redshift - int from_catalog; // flag for first box or updating halos + bool from_catalog; // flag for first box or updating halos double corr_sfr; double corr_star; double corr_xray; @@ -56,7 +58,7 @@ double expected_nhalo(double redshift); int add_properties_cat(unsigned long long int seed, float redshift, HaloCatalog *halos); void stoc_set_consts_z(struct HaloSamplingConstants *const_struct, double redshift, - double redshift_desc); + double redshift_desc, bool from_catalog); void stoc_set_consts_cond(struct HaloSamplingConstants *const_struct, double cond_val); #endif diff --git a/src/py21cmfast/src/_outputstructs_wrapper.h b/src/py21cmfast/src/_outputstructs_wrapper.h index a2dd765bb..6cfa26558 100644 --- a/src/py21cmfast/src/_outputstructs_wrapper.h +++ b/src/py21cmfast/src/_outputstructs_wrapper.h @@ -22,9 +22,21 @@ typedef struct HaloCatalog { float *halo_coords; // Halo properties for stochastic model - float *star_rng; - float *sfr_rng; - float *xray_rng; + // before the forward loop (sfh_computed==false) these are + // sfr_10: Stochastic component for SFR averaged over 10 Myr + // sfr_100: Stochastic component for SFR averaged over 100 Myr + // stellar_mass: Stochastic component for SFR averaged over the last snapshot + // TODO: give these more generic names + float *sfr_10; + float *sfr_100; + float *stellar_mass; + // After the forward loop (sfh_computed == true), these are + // sfr_10: SFR averaged over 10 Myr + // sfr_100: total stellar mass in ACG + // stellar_mass: total stellar mass in MCG + + long long unsigned int *descendant_index; + bool sfh_computed; } HaloCatalog; typedef struct PerturbedHaloCatalog { diff --git a/src/py21cmfast/src/correlated_sfh.c b/src/py21cmfast/src/correlated_sfh.c index db88a9e6d..5a733e577 100644 --- a/src/py21cmfast/src/correlated_sfh.c +++ b/src/py21cmfast/src/correlated_sfh.c @@ -178,6 +178,9 @@ void fill_covar_analytic(double tau, double tau_prev, gsl_matrix *curr_cov, gsl_ rho(tau, t1, t2) = rho(-tau, t2, t1) So... Cov(X_prev, Y_curr) == Cov(Y_curr, X_prev) != Cov(X_curr, Y_prev) == Cov(Y_prev, X_curr) + Also note that positive tau means current snapshot is later than previous snapshot + and negative means the current snapshot is earlier. These functions should produce the + correct output for either case. The entire covariance matrix is symmetric and positive semi-definite, The auto-correlation sub-matrices (top-left/bottom-right) are both symmetric as well @@ -381,21 +384,57 @@ void eval_sfh_moments(gsl_matrix *prev_cov, gsl_matrix *curr_cov, gsl_matrix *cr gsl_matrix_free(partial_buf); } -void initialise_sfh_structs(double z0, double z1, double z2) { +void eval_sfh_unconditioned(gsl_matrix *curr_cov, gsl_matrix *out_chol_cov, + gsl_matrix *out_mean_correction) { + /* Evaluate the SFH covariance matrix for unconditioned sampling + + Outputs are the Cholesky factor of the current covariance matrix + which multiplies standard normal vector to get correlated SFRs. + + out_chol_cov = Cholesky factor of Cov(curr), NOTE: The upper triangle is garbage + */ + gsl_matrix *covar_buf = gsl_matrix_alloc(3, 3); + gsl_matrix_memcpy(covar_buf, curr_cov); + + // Perform Cholesky decomposition of Cov(curr) = L L^T to do implicit inversion + gsl_linalg_cholesky_decomp1(covar_buf); + + // Not necessary, but it makes it clearer that only the lower triangle is valid + force_matrix_lotri(covar_buf); // zero upper triangle + + gsl_matrix_memcpy(out_chol_cov, covar_buf); + + // set mean correction to zero matrix + gsl_matrix_set_zero(out_mean_correction); + + gsl_matrix_free(covar_buf); +} + +void initialise_sfh_structs(double z0, double z1, double z2, bool conditioned) { sfh_mats.curr_cov = gsl_matrix_alloc(3, 3); sfh_mats.prev_cov = gsl_matrix_alloc(3, 3); sfh_mats.pxc_cov = gsl_matrix_alloc(3, 3); sfh_mats.L_cov = gsl_matrix_alloc(3, 3); sfh_mats.mean_correction = gsl_matrix_alloc(3, 3); + if (z0 < 0. || conditioned && (z1 < 0. || z2 < 0.)) { + LOG_ERROR("You provided negative redshifts for SFH initialisation!"); + Throw(ValueError); + } + + // positive for z2 > z1 > z0 double tau = time_between_z(z0, z1) / (physconst.s_per_yr * 1e6); // Myr double tau_prev = time_between_z(z1, z2) / (physconst.s_per_yr * 1e6); // Myr // initialise_psd_corrfunc_tables(tau, tau_prev); // fill_covar_from_tables(tau, sfh_mats.curr_cov, sfh_mats.prev_cov, sfh_mats.pxc_cov); fill_covar_analytic(tau, tau_prev, sfh_mats.curr_cov, sfh_mats.prev_cov, sfh_mats.pxc_cov); - eval_sfh_moments(sfh_mats.prev_cov, sfh_mats.curr_cov, sfh_mats.pxc_cov, sfh_mats.L_cov, - sfh_mats.mean_correction); + if (conditioned) { + eval_sfh_moments(sfh_mats.prev_cov, sfh_mats.curr_cov, sfh_mats.pxc_cov, sfh_mats.L_cov, + sfh_mats.mean_correction); + } else { + eval_sfh_unconditioned(sfh_mats.curr_cov, sfh_mats.L_cov, sfh_mats.mean_correction); + } } void cleanup_sfh_structs() { @@ -429,6 +468,10 @@ void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], double out_value L_cov: Cholesky factor of Cov(curr|prev), from eval_sfh_moments, L L^T = Cov(curr|prev) mean_corr: Mean correction matrix from eval_sfh_moments + Method: + Calculates the produce out = L_cov * N(0,1) + mean_corr * prev_values + Assumes zero mean of all variables. + Outputs: out_values: array of sampled current SFR values [SFR_10Myr, SFR_100Myr, SFR_snapshot_curr] */ @@ -458,6 +501,13 @@ void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], double out_value gsl_vector_free(cond_term); } +void get_current_vars(double out[3]) { + /* Get the variances of the current SFR variables */ + for (int i = 0; i < 3; i++) { + out[i] = gsl_matrix_get(sfh_mats.curr_cov, i, i); + } +} + /* TESTING FUNCTIONS */ void print_corrfunc(RGTable1D *ptr, const char *name, int skip_lines) { diff --git a/src/py21cmfast/src/correlated_sfh.h b/src/py21cmfast/src/correlated_sfh.h index 005e6f157..e46f22e92 100644 --- a/src/py21cmfast/src/correlated_sfh.h +++ b/src/py21cmfast/src/correlated_sfh.h @@ -5,9 +5,9 @@ #ifndef CORRELATED_SFH_H #define CORRELATED_SFH_H -void initialise_sfh_structs(double z0, double z1, double z2); +void initialise_sfh_structs(double z0, double z1, double z2, bool conditioned); int test_sfh_corr(double z0, double z1, double z2); void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], double out_values[3]); -void cleanup_sfh_structs(); +void get_current_vars(double out[3]) void cleanup_sfh_structs(); #endif diff --git a/src/py21cmfast/src/hmf.c b/src/py21cmfast/src/hmf.c index 4d8391aa7..b784a0fdf 100644 --- a/src/py21cmfast/src/hmf.c +++ b/src/py21cmfast/src/hmf.c @@ -929,7 +929,7 @@ double Xray_General(double z, double lnM_Min, double lnM_Max, double mturn_acg, .HMF = matter_options_global->HMF, .l_x_norm = sc->l_x, .l_x_norm_mini = sc->l_x_mini, - .t_h = t_hubble(z), + .t_h = sc->t_h, .t_star = sc->t_star, .gamma_type = 5, }; diff --git a/src/py21cmfast/src/interp_tables.c b/src/py21cmfast/src/interp_tables.c index f8d0acb69..93b0c18e3 100644 --- a/src/py21cmfast/src/interp_tables.c +++ b/src/py21cmfast/src/interp_tables.c @@ -127,7 +127,9 @@ void initialise_SFRD_spline(int Nbin, float zmin, float zmax, ScalingConstants * for (i = 0; i < Nbin; i++) { z_val = SFRD_z_table.x_min + i * SFRD_z_table.x_width; // both tables will have the same values here - sc_sfrd = evolve_scaling_constants_to_redshift(z_val, &sc_sfrd, false); + sc_sfrd = evolve_scaling_constants_to_redshift(z_val, &sc_sfrd); + + // NOTE: minimum mass used for La, LW and xrays lnMmin = log(minimum_source_mass(z_val, true)); if (astro_options_global->USE_MINI_HALOS) { @@ -190,12 +192,12 @@ void initialise_Nion_Ts_spline(int Nbin, float zmin, float zmax, ScalingConstant double lnMmin; #pragma omp for for (i = 0; i < Nbin; i++) { - z_val = Nion_z_table.x_min + - i * Nion_z_table.x_width; // both tables will have the same values here - sc_z = evolve_scaling_constants_to_redshift(z_val, sc, false); - // Minor note: while this is called in xray, we use it to estimate ionised fraction, do - // we use ION_Tvir_MIN if applicable? - lnMmin = log(minimum_source_mass(z_val, true)); + // both tables will have the same values here + z_val = Nion_z_table.x_min + i * Nion_z_table.x_width; + sc_z = evolve_scaling_constants_to_redshift(z_val, sc); + + // Note: v3/v4b used to have true (used XRAY_TVIR_MIN in const zeta mode) + lnMmin = log(minimum_source_mass(z_val, false)); if (astro_options_global->USE_MINI_HALOS) { for (j = 0; j < NMTURN; j++) { mturn_mcg = pow(10, Nion_z_table_MINI.y_min + j * Nion_z_table_MINI.y_width); @@ -911,7 +913,7 @@ double EvaluateNionTs_MINI(double redshift, double log10_Mturn_LW_ave, ScalingCo if (matter_options_global->USE_INTERPOLATION_TABLES > 1) { return EvaluateRGTable2D(redshift, log10_Mturn_LW_ave, &Nion_z_table_MINI); } - double lnMmin = log(minimum_source_mass(redshift, true)); + double lnMmin = log(minimum_source_mass(redshift, false)); double lnMmax = log(M_MAX_INTEGRAL); ScalingConstants sc_z = evolve_scaling_constants_to_redshift(redshift, sc, false); diff --git a/src/py21cmfast/src/map_mass.c b/src/py21cmfast/src/map_mass.c index d80134dd4..ccfbce371 100644 --- a/src/py21cmfast/src/map_mass.c +++ b/src/py21cmfast/src/map_mass.c @@ -345,10 +345,10 @@ void move_grid_galprops(double redshift, float *dens_pointer, int dens_dim[3], } } -void move_halo_galprops(double redshift, HaloCatalog *halos, float *vel_pointers[3], - float *vel_pointers_2LPT[3], int vel_dim[3], float *mturn_a_grid, - float *mturn_m_grid, HaloBox *boxes, int out_dim[3], - ScalingConstants *consts) { +void move_halo_galprops(double redshift, HaloCatalog *halos, float *progenitor_hm, + float *progenitor_sm, float *vel_pointers[3], float *vel_pointers_2LPT[3], + int vel_dim[3], float *mturn_a_grid, float *mturn_m_grid, HaloBox *boxes, + int out_dim[3], ScalingConstants *consts) { // grid dimension constants double boxlen = simulation_options_global->BOX_LEN; double boxlen_z = boxlen * simulation_options_global->NON_CUBIC_FACTOR; @@ -413,16 +413,16 @@ void move_halo_galprops(double redshift, HaloCatalog *halos, float *vel_pointers M_turn_a = pow(10, cic_read_float(mturn_a_grid, pos, out_dim)); M_turn_m = pow(10, cic_read_float(mturn_m_grid, pos, out_dim)); } - halo_rng[0] = halos->star_rng[i]; - halo_rng[1] = halos->sfr_rng[i]; - halo_rng[2] = halos->xray_rng[i]; + halo_rng[0] = halos->sfr_10[i]; + halo_rng[1] = halos->sfr_100[i]; + halo_rng[2] = halos->stellar_mass[i]; // CIC interpolation set_halo_properties(hmass, M_turn_a, M_turn_m, consts, halo_rng, &properties); - do_cic_interpolation(boxes->halo_sfr, pos, out_dim, properties.halo_sfr); + do_cic_interpolation(boxes->halo_sfr, pos, out_dim, properties.sfr_10); do_cic_interpolation(boxes->n_ion, pos, out_dim, properties.n_ion); if (astro_options_global->USE_MINI_HALOS) { - do_cic_interpolation(boxes->halo_sfr_mini, pos, out_dim, properties.sfr_mini); + do_cic_interpolation(boxes->halo_sfr_mini, pos, out_dim, properties.sfr_10_mcg); } if (astro_options_global->USE_TS_FLUCT) { do_cic_interpolation(boxes->halo_xray, pos, out_dim, properties.halo_xray); @@ -439,6 +439,11 @@ void move_halo_galprops(double redshift, HaloCatalog *halos, float *vel_pointers } } + // feed back the halo properties we need to store onto the HaloCatalog + halos->sfr_10[i] = properties.sfr_10; // TODO: we don't need to store this really + halos->sfr_100[i] = properties.stellar_mass_mini; // TODO:naming is misleading here + halos->stellar_mass[i] = properties.stellar_mass; + #if LOG_LEVEL >= ULTRA_DEBUG_LEVEL if (i < 10) { LOG_ULTRA_DEBUG( diff --git a/src/py21cmfast/src/map_mass.h b/src/py21cmfast/src/map_mass.h index fd4a9368c..f4f3e98dc 100644 --- a/src/py21cmfast/src/map_mass.h +++ b/src/py21cmfast/src/map_mass.h @@ -13,9 +13,9 @@ void move_grid_galprops(double redshift, float *dens_pointer, int dens_dim[3], HaloBox *boxes, int out_dim[3], float *mturn_a_grid, float *mturn_m_grid, ScalingConstants *consts, IntegralCondition *integral_cond); -void move_halo_galprops(double redshift, HaloCatalog *halos, float *vel_pointers[3], - float *vel_pointers_2LPT[3], int vel_dim[3], float *mturn_a_grid, - float *mturn_m_grid, HaloBox *boxes, int out_dim[3], - ScalingConstants *consts); +void move_halo_galprops(double redshift, HaloCatalog *halos, float *progenitor_hm, + float *progenitor_sm, float *vel_pointers[3], float *vel_pointers_2LPT[3], + int vel_dim[3], float *mturn_a_grid, float *mturn_m_grid, HaloBox *boxes, + int out_dim[3], ScalingConstants *consts); double cic_read_float_wrapper(float *box, double pos[3], int box_dim[3]); diff --git a/src/py21cmfast/src/scaling_relations.c b/src/py21cmfast/src/scaling_relations.c index e7dd288fa..de1607528 100644 --- a/src/py21cmfast/src/scaling_relations.c +++ b/src/py21cmfast/src/scaling_relations.c @@ -36,17 +36,41 @@ void print_sc_consts(ScalingConstants *c) { return; } -void set_scaling_constants(double redshift, ScalingConstants *consts, bool use_photoncons) { +void set_scaling_constants(double redshift, double redshift_prev, ScalingConstants *consts, + bool use_photoncons) { consts->redshift = redshift; + if (redshift_prev > 0) + consts->snapshot_time = time_between_z(redshift, redshift_prev); + else + consts->snapshot_time = -1.0; // indicates single snapshot mode + // Set on for the fixed grid case since we are missing halos above the cell mass consts->fix_mean = matter_options_global->HMF == 2 || matter_options_global->HMF == 3; // whether to fix *integrated* (not sampled) galaxy properties to the expected mean consts->scaling_median = astro_options_global->HALO_SCALING_RELATIONS_MEDIAN; + // we need the sigmas for the current snapshot + if (matter_options_global->SOURCE_MODEL > 1) { + initialise_sfh_structs(redshift, -1, -1, false); + get_current_vars(consts->integral_mean_correction); + if (astro_options_global->HALO_SCALING_RELATIONS_MEDIAN) { + for (int i = 0; i < 3; i++) { + consts->integral_mean_correction[i] = + exp(0.5 * consts->integral_mean_correction[i]); + consts->sampled_mean_correction[i] = 1.0; + } + } else { + for (int i = 0; i < 3; i++) { + consts->sampled_mean_correction[i] = + exp(-0.5 * consts->integral_mean_correction[i]); + consts->integral_mean_correction[i] = 1.0; + } + } + } + consts->fstar_10 = astro_params_global->F_STAR10; consts->alpha_star = astro_params_global->ALPHA_STAR; - consts->sigma_star = astro_params_global->SIGMA_STAR; consts->alpha_upper = astro_params_global->UPPER_STELLAR_TURNOVER_INDEX; consts->pivot_upper = astro_params_global->UPPER_STELLAR_TURNOVER_MASS; @@ -56,14 +80,9 @@ void set_scaling_constants(double redshift, ScalingConstants *consts, bool use_p consts->fstar_7 = astro_params_global->F_STAR7_MINI; consts->alpha_star_mini = astro_params_global->ALPHA_STAR_MINI; - consts->t_h = t_hubble(redshift); - consts->t_star = astro_params_global->t_STAR; - consts->sigma_sfr_lim = astro_params_global->SIGMA_SFR_LIM; - consts->sigma_sfr_idx = astro_params_global->SIGMA_SFR_INDEX; // setting units to 1e38 erg s -1 so we can store in float consts->l_x = astro_params_global->L_X * 1e-38; consts->l_x_mini = astro_params_global->L_X_MINI * 1e-38; - consts->sigma_xray = astro_params_global->SIGMA_LX; consts->alpha_esc = astro_params_global->ALPHA_ESC; consts->fesc_10 = astro_params_global->F_ESC10; @@ -118,28 +137,10 @@ ScalingConstants evolve_scaling_constants_sfr(ScalingConstants *sc) { } // It's often useful to create a copy of scaling relations at a different z -ScalingConstants evolve_scaling_constants_to_redshift(double redshift, ScalingConstants *sc, - bool use_photoncons) { +// NOTE: Any alterations from f_esc from photoncons etc should be already set in 'sc' +ScalingConstants evolve_scaling_constants_to_redshift(double redshift, ScalingConstants *sc) { ScalingConstants sc_z = *sc; sc_z.redshift = redshift; - sc_z.t_h = t_hubble(redshift); - - if (use_photoncons) { - if (astro_options_global->PHOTON_CONS_TYPE == 2) - sc_z.alpha_esc = get_fesc_fit(redshift); - else if (astro_options_global->PHOTON_CONS_TYPE == 3) - sc_z.fesc_10 = get_fesc_fit(redshift); - - // if we altered the escape fraction, we need to recalculate the mass limits - sc_z.Mlim_Fesc = - Mass_limit_bisection(M_MIN_INTEGRAL, M_MAX_INTEGRAL, sc_z.alpha_esc, sc_z.fesc_10); - - if (astro_options_global->USE_MINI_HALOS) { - sc_z.Mlim_Fesc_mini = - Mass_limit_bisection(M_MIN_INTEGRAL, M_MAX_INTEGRAL, sc_z.alpha_esc, - sc_z.fesc_7 * pow(1e3, sc_z.alpha_esc)); - } - } sc_z.acg_thresh = atomic_cooling_threshold(redshift); sc_z.mturn_a_nofb = astro_params_global->M_TURN; @@ -311,12 +312,12 @@ double get_lx_on_sfr(double sfr, double metallicity, double lx_constant) { return lx_constant; } -void get_halo_stellarmass(double halo_mass, double mturn_acg, double mturn_mcg, double star_rng, - ScalingConstants *consts, double *star_acg, double *star_mcg) { +void get_halo_sfh(double halo_mass, double mturn_acg, double mturn_mcg, double prog_hm, + double prog_sm[2], double rng[3], ScalingConstants *consts, double sfr_out[3], + double sfr_out_mini[3]) { // low-mass ACG power-law parameters double f_10 = consts->fstar_10; double f_a = consts->alpha_star; - double sigma_star = consts->sigma_star; // high-mass ACG power-law parameters double fu_a = consts->alpha_upper; @@ -327,13 +328,9 @@ void get_halo_stellarmass(double halo_mass, double mturn_acg, double mturn_mcg, double f_a_mini = consts->alpha_star_mini; // intermediates - double fstar_mean; - double f_sample, f_sample_mini; - double sm_sample, sm_sample_mini; - + double fstar_mean, fstar_mean_mini; + double mass_growth = halo_mass - prog_hm; double baryon_ratio = cosmo_params_global->OMb / cosmo_params_global->OMm; - // adjustment to the mean for lognormal scatter - double stoc_adjustment_term = consts->scaling_median ? 0 : sigma_star * sigma_star / 2.; // We don't want an upturn even with a negative ALPHA_STAR if (astro_options_global->USE_UPPER_STELLAR_TURNOVER && (f_a > fu_a)) { @@ -341,60 +338,46 @@ void get_halo_stellarmass(double halo_mass, double mturn_acg, double mturn_mcg, } else { fstar_mean = scaling_single_PL(halo_mass, consts->alpha_star, 1e10); // PL term } + fstar_mean = f_10 * fstar_mean; // 1e10 normalisation of stellar mass - f_sample = f_10 * fstar_mean * - exp(-mturn_acg / halo_mass + star_rng * sigma_star - stoc_adjustment_term); - if (f_sample > 1.) f_sample = 1.; - - sm_sample = f_sample * halo_mass * baryon_ratio; - *star_acg = sm_sample; - if (!astro_options_global->USE_MINI_HALOS) { - *star_mcg = 0.; + // < 0 can happen occasionally due to the stochasticity of the sampler + if (mass_growth <= 0) { + for (int i = 0; i < 3; i++) { + sfr_out[i] = 0.; + sfr_out_mini[i] = 0.; + } return; } - f_sample_mini = scaling_single_PL(halo_mass, f_a_mini, 1e7) * f_7; - f_sample_mini *= exp(-mturn_mcg / halo_mass - halo_mass / consts->acg_thresh + - star_rng * sigma_star - stoc_adjustment_term); - if (f_sample_mini > 1.) f_sample_mini = 1.; - - sm_sample_mini = f_sample_mini * halo_mass * baryon_ratio; - *star_mcg = sm_sample_mini; -} - -void get_halo_sfr(double stellar_mass, double stellar_mass_mini, double sfr_rng, - ScalingConstants *consts, double *sfr, double *sfr_mini) { - double sfr_mean, sfr_mean_mini; - double sfr_sample, sfr_sample_mini; - - double sigma_sfr_lim = consts->sigma_sfr_lim; - double sigma_sfr_idx = consts->sigma_sfr_idx; - - // set the scatter based on the total Stellar mass - // We use the total stellar mass (MCG + ACG) NOTE: it might be better to separate later - double sigma_sfr = 0.; - - if (sigma_sfr_lim > 0.) { - sigma_sfr = - sigma_sfr_idx * log10((stellar_mass + stellar_mass_mini) / 1e10) + sigma_sfr_lim; - if (sigma_sfr < sigma_sfr_lim) sigma_sfr = sigma_sfr_lim; + for (int i = 0; i < 3; i++) { + // we move the mturn here to go from 4 exp calls to 3 + sfr_out[i] = max(1.0, fstar_mean * exp(-mturn_acg / halo_mass + rng[i])) * mass_growth * + baryon_ratio * consts->sampled_mean_correction[i]; } - sfr_mean = stellar_mass / (consts->t_star * consts->t_h); - // adjustment to the mean for lognormal scatter - double stoc_adjustment_term = consts->scaling_median ? 0 : sigma_sfr * sigma_sfr / 2.; - sfr_sample = sfr_mean * exp(sfr_rng * sigma_sfr - stoc_adjustment_term); - *sfr = sfr_sample; - - if (!astro_options_global->USE_MINI_HALOS) { - *sfr_mini = 0.; - return; + if (astro_options_global->USE_MINI_HALOS) { + fstar_mean_mini = scaling_single_PL(halo_mass, f_a_mini, 1e7) * f_7; + for (int i = 0; i < 3; i++) { + sfr_out_mini[i] = + max(1.0, fstar_mean_mini * exp(-mturn_mcg / halo_mass - + halo_mass / consts->acg_thresh + rng[i])) * + mass_growth * baryon_ratio * consts->sampled_mean_correction[i]; + } + } else { + for (int i = 0; i < 3; i++) { + sfr_out_mini[i] = 0.; + } } - sfr_mean_mini = stellar_mass_mini / (consts->t_star * consts->t_h); - sfr_sample_mini = sfr_mean_mini * exp(sfr_rng * sigma_sfr - stoc_adjustment_term); - *sfr_mini = sfr_sample_mini; + // divide by snapshot time + sfr_out[0] = sfr_out[0] / consts->snapshot_time; + sfr_out_mini[0] = sfr_out_mini[0] / consts->snapshot_time; + sfr_out[1] = sfr_out[1] / consts->snapshot_time; + sfr_out_mini[1] = sfr_out_mini[1] / consts->snapshot_time; + // Finally, sum the progenitor stellar masses into the third field + sfr_out[2] = sfr_out[2] + prog_sm[0]; + sfr_out_mini[2] = sfr_out_mini[2] + prog_sm[1]; } void get_halo_metallicity(double sfr, double stellar, double redshift, double *z_out) { diff --git a/src/py21cmfast/src/scaling_relations.h b/src/py21cmfast/src/scaling_relations.h index 4de4cf150..d96a37702 100644 --- a/src/py21cmfast/src/scaling_relations.h +++ b/src/py21cmfast/src/scaling_relations.h @@ -12,12 +12,12 @@ // unit changes typedef struct ScalingConstants { double redshift; + double snapshot_time; bool fix_mean; bool scaling_median; double fstar_10; double alpha_star; - double sigma_star; double alpha_upper; double pivot_upper; @@ -26,14 +26,11 @@ typedef struct ScalingConstants { double fstar_7; double alpha_star_mini; - double t_h; - double t_star; - double sigma_sfr_lim; - double sigma_sfr_idx; - double l_x; double l_x_mini; - double sigma_xray; + + double sampled_mean_correction[3]; + double integral_mean_correction[3]; double fesc_10; double alpha_esc; @@ -55,10 +52,9 @@ typedef struct ScalingConstants { void set_scaling_constants(double redshift, ScalingConstants *consts, bool use_photoncons); double get_lx_on_sfr(double sfr, double metallicity, double lx_constant); -void get_halo_stellarmass(double halo_mass, double mturn_acg, double mturn_mcg, double star_rng, - ScalingConstants *consts, double *star_acg, double *star_mcg); -void get_halo_sfr(double stellar_mass, double stellar_mass_mini, double sfr_rng, - ScalingConstants *consts, double *sfr, double *sfr_mini); +void get_halo_sfh(double halo_mass, double mturn_acg, double mturn_mcg, double prog_hm, + double prog_sm[2], double rng[3], ScalingConstants *consts, double sfr_out[3], + double sfr_out_mini[3]); void get_halo_metallicity(double sfr, double stellar, double redshift, double *z_out); void get_halo_xray(double sfr, double sfr_mini, double metallicity, double xray_rng, ScalingConstants *consts, double *xray_out); @@ -69,8 +65,7 @@ double log_scaling_PL_limit(double lnM, double ln_norm, double alpha, double ln_ double scaling_double_PL(double M, double alpha_lo, double pivot_ratio, double alpha_hi, double pivot_hi); ScalingConstants evolve_scaling_constants_sfr(ScalingConstants *sc); -ScalingConstants evolve_scaling_constants_to_redshift(double redshift, ScalingConstants *sc, - bool use_photoncons); +ScalingConstants evolve_scaling_constants_to_redshift(double redshift, ScalingConstants *sc); ScalingConstants mimic_scatter_in_consts(ScalingConstants *sc); void print_sc_consts(ScalingConstants *c); void initialise_sfh_correlation(double z, double z_prev); diff --git a/src/py21cmfast/wrapper/outputs.py b/src/py21cmfast/wrapper/outputs.py index 5fd78d645..c7aacc807 100644 --- a/src/py21cmfast/wrapper/outputs.py +++ b/src/py21cmfast/wrapper/outputs.py @@ -769,12 +769,14 @@ class HaloCatalog(OutputStructZ): _compat_hash = _HashType.zgrid halo_masses = _arrayfield() - star_rng = _arrayfield() - sfr_rng = _arrayfield() - xray_rng = _arrayfield() + sfr_10 = _arrayfield() + sfr_100 = _arrayfield() + stellar_mass = _arrayfield() + descendant_index = _arrayfield() halo_coords = _arrayfield() n_halos: int = attrs.field(default=None) buffer_size: int = attrs.field(default=None) + _sfh_computed: bool = attrs.field(init=False, default=False) @classmethod def new( @@ -811,9 +813,10 @@ def new( return cls( inputs=inputs, halo_masses=Array((buffer_size,), dtype=np.float32), - star_rng=Array((buffer_size,), dtype=np.float32), - sfr_rng=Array((buffer_size,), dtype=np.float32), - xray_rng=Array((buffer_size,), dtype=np.float32), + sfr_10=Array((buffer_size,), dtype=np.float32), + sfr_100=Array((buffer_size,), dtype=np.float32), + sfr_snapshot=Array((buffer_size,), dtype=np.float32), + descendant_index=Array((buffer_size,), dtype=np.int64), halo_coords=Array((buffer_size, 3), dtype=np.float32), redshift=redshift, buffer_size=buffer_size, @@ -1059,13 +1062,16 @@ def get_required_input_arrays(self, input_box: OutputStruct) -> list[str]: required = [] if isinstance(input_box, HaloCatalog): if self.matter_options.has_discrete_halos: - required += [ - "halo_coords", - "halo_masses", - "star_rng", - "sfr_rng", - "xray_rng", - ] + if input_box.redshift != self.redshift: + required += ["descendant_index", "halo_masses", "stellar_mass"] + else: + required += [ + "halo_coords", + "halo_masses", + "sfr_10", + "sfr_100", + "stellar_mass", + ] elif isinstance(input_box, TsBox): if self.astro_options.USE_MINI_HALOS: required += ["J_21_LW"] @@ -1092,6 +1098,7 @@ def compute( *, initial_conditions: InitialConditions, halo_catalog: HaloCatalog, + previous_halo_catalog: HaloCatalog, previous_spin_temp: TsBox, previous_ionize_box: IonizedBox, allow_already_computed: bool = False, @@ -1102,6 +1109,7 @@ def compute( self.redshift, initial_conditions, halo_catalog, + previous_halo_catalog, previous_spin_temp, previous_ionize_box, ) From 196ed4adb03cd56928e588fb8464d5dcb65aefdd Mon Sep 17 00:00:00 2001 From: James Davies Date: Sun, 8 Feb 2026 23:07:57 +1100 Subject: [PATCH 12/18] almost compiles --- src/py21cmfast/src/BrightnessTemperatureBox.c | 6 +- src/py21cmfast/src/BrightnessTemperatureBox.h | 4 +- src/py21cmfast/src/HaloBox.c | 132 ++++++++++-------- src/py21cmfast/src/HaloBox.h | 13 +- src/py21cmfast/src/HaloCatalog.c | 29 ++-- src/py21cmfast/src/HaloCatalog.h | 5 +- src/py21cmfast/src/InputParameters.c | 21 +++ src/py21cmfast/src/InputParameters.h | 7 + src/py21cmfast/src/IonisationBox.c | 10 +- src/py21cmfast/src/IonisationBox.h | 7 +- src/py21cmfast/src/PerturbedField.c | 4 +- src/py21cmfast/src/PerturbedField.h | 3 +- src/py21cmfast/src/PerturbedHaloCatalog.c | 10 +- src/py21cmfast/src/PerturbedHaloCatalog.h | 5 +- src/py21cmfast/src/SpinTemperatureBox.c | 39 +++--- src/py21cmfast/src/SpinTemperatureBox.h | 3 +- src/py21cmfast/src/Stochasticity.c | 51 ++++--- src/py21cmfast/src/Stochasticity.h | 5 +- .../src/_functionprototypes_wrapper.h | 33 ++--- src/py21cmfast/src/_inputparams_wrapper.h | 7 + src/py21cmfast/src/correlated_sfh.h | 9 +- src/py21cmfast/src/cosmology.h | 23 ++- src/py21cmfast/src/integral_wrappers.c | 6 +- src/py21cmfast/src/interp_tables.c | 8 +- src/py21cmfast/src/map_mass.c | 33 ++++- src/py21cmfast/src/map_mass.h | 9 +- src/py21cmfast/src/photoncons.c | 6 +- src/py21cmfast/src/scaling_relations.c | 59 ++------ src/py21cmfast/src/scaling_relations.h | 12 +- src/py21cmfast/wrapper/cfuncs.py | 10 +- 30 files changed, 305 insertions(+), 264 deletions(-) diff --git a/src/py21cmfast/src/BrightnessTemperatureBox.c b/src/py21cmfast/src/BrightnessTemperatureBox.c index 72e55146a..36ad1b318 100644 --- a/src/py21cmfast/src/BrightnessTemperatureBox.c +++ b/src/py21cmfast/src/BrightnessTemperatureBox.c @@ -19,8 +19,8 @@ #include "indexing.h" #include "logger.h" -int ComputeBrightnessTemp(float redshift, TsBox *spin_temp, IonizedBox *ionized_box, - PerturbedField *perturb_field, BrightnessTemp *box) { +int ComputeBrightnessTemp(TsBox *spin_temp, IonizedBox *ionized_box, PerturbedField *perturb_field, + BrightnessTemp *box) { int status; Try { // Try block around whole function. LOG_DEBUG("Starting Brightness Temperature calculation for redshift %f", redshift); @@ -30,6 +30,8 @@ int ComputeBrightnessTemp(float redshift, TsBox *spin_temp, IonizedBox *ionized_ int i, j, k; double ave; + double redshift = get_current_redshift(); + int box_dim[3] = { simulation_options_global->HII_DIM, simulation_options_global->HII_DIM, simulation_options_global->NON_CUBIC_FACTOR * simulation_options_global->HII_DIM}; diff --git a/src/py21cmfast/src/BrightnessTemperatureBox.h b/src/py21cmfast/src/BrightnessTemperatureBox.h index c4cf5b8a4..c916023d0 100644 --- a/src/py21cmfast/src/BrightnessTemperatureBox.h +++ b/src/py21cmfast/src/BrightnessTemperatureBox.h @@ -5,7 +5,7 @@ #include "InputParameters.h" #include "OutputStructs.h" -int ComputeBrightnessTemp(float redshift, TsBox *spin_temp, IonizedBox *ionized_box, - PerturbedField *perturb_field, BrightnessTemp *box); +int ComputeBrightnessTemp(TsBox *spin_temp, IonizedBox *ionized_box, PerturbedField *perturb_field, + BrightnessTemp *box); #endif diff --git a/src/py21cmfast/src/HaloBox.c b/src/py21cmfast/src/HaloBox.c index 9d078c6f2..3e9852c59 100644 --- a/src/py21cmfast/src/HaloBox.c +++ b/src/py21cmfast/src/HaloBox.c @@ -53,15 +53,16 @@ void set_integral_constants(IntegralCondition *consts, double redshift, double M // we treat the minihalos as a shift in the mean, where each halo will have both components, // representing a smooth transition in halo mass from one set of SFR/emmissivity parameters to the // other. -void set_halo_properties(double halo_mass, double M_turn_a, double M_turn_m, double prog_hm, - double prog_sm[2], ScalingConstants *consts, double *input_rng, - HaloProperties *output) { +void set_halo_properties(double snapshot_time, double halo_mass, double M_turn_a, double M_turn_m, + double prog_hm, double prog_sm[2], ScalingConstants *consts, + double *input_rng, HaloProperties *output) { double n_ion_sample, wsfr_sample; double fesc; double fesc_mini = 0.; double sfh[3], sfh_mini[3]; - get_halo_sfh(halo_mass, M_turn_a, M_turn_m, prog_hm, prog_sm, input_rng, consts, sfh, sfh_mini); + get_halo_sfh(snapshot_time, halo_mass, M_turn_a, M_turn_m, prog_hm, prog_sm, input_rng, consts, + sfh, sfh_mini); // SFH holds SFR over 10Myr[0] and 100Myr[1], total formed stellar mass [2] @@ -102,7 +103,8 @@ int get_uhmf_averages(double M_min, double M_max, double M_turn_a, double M_turn ScalingConstants *consts, HaloProperties *averages_out) { LOG_SUPER_DEBUG("Getting Box averages z=%.2f M [%.2e %.2e] Mt [%.2e %.2e]", consts->redshift, M_min, M_max, M_turn_a, M_turn_m); - double t_h = consts->t_h; + double t_h = t_hubble(consts->redshift); + double t_star = astro_params_global->t_STAR; double lnMmax = log(M_max); double lnMmin = log(M_min); @@ -111,8 +113,8 @@ int get_uhmf_averages(double M_min, double M_max, double M_turn_a, double M_turn double prefactor_stars_mini = RHOcrit * cosmo_params_global->OMb * consts->fstar_7; double prefactor_xray = RHOcrit * cosmo_params_global->OMm; - double prefactor_sfr = prefactor_stars / consts->t_star / t_h; - double prefactor_sfr_mini = prefactor_stars_mini / consts->t_star / t_h; + double prefactor_sfr = prefactor_stars / t_star / t_h; + double prefactor_sfr_mini = prefactor_stars_mini / t_star / t_h; double prefactor_nion = prefactor_stars * consts->fesc_10 * consts->pop2_ion; double prefactor_nion_mini = prefactor_stars_mini * consts->fesc_7 * consts->pop3_ion; double prefactor_wsfr = prefactor_sfr * consts->fesc_10 * consts->pop2_ion; @@ -141,9 +143,9 @@ int get_uhmf_averages(double M_min, double M_max, double M_turn_a, double M_turn averages_out->halo_mass = mass_intgrl * prefactor_mass; averages_out->stellar_mass = intgrl_stars_only * prefactor_stars; - averages_out->halo_sfr = intgrl_stars_only * prefactor_sfr; + averages_out->sfr_10 = intgrl_stars_only * prefactor_sfr; averages_out->stellar_mass_mini = intgrl_stars_only_mini * prefactor_stars_mini; - averages_out->sfr_mini = intgrl_stars_only_mini * prefactor_sfr_mini; + averages_out->sfr_10_mcg = intgrl_stars_only_mini * prefactor_sfr_mini; averages_out->n_ion = (intgrl_fesc_weighted * prefactor_nion) + (intgrl_fesc_weighted_mini * prefactor_nion_mini); averages_out->fescweighted_sfr = @@ -154,6 +156,7 @@ int get_uhmf_averages(double M_min, double M_max, double M_turn_a, double M_turn return 0; } + HaloProperties get_halobox_averages(HaloBox *grids) { int mean_count = 0; double mean_mass = 0., mean_stars = 0., mean_stars_mini = 0., mean_sfr = 0., mean_sfr_mini = 0.; @@ -185,8 +188,10 @@ HaloProperties get_halobox_averages(HaloBox *grids) { .halo_mass = mean_mass / HII_TOT_NUM_PIXELS, .stellar_mass = mean_stars / HII_TOT_NUM_PIXELS, .stellar_mass_mini = mean_stars_mini / HII_TOT_NUM_PIXELS, - .halo_sfr = mean_sfr / HII_TOT_NUM_PIXELS, - .sfr_mini = mean_sfr_mini / HII_TOT_NUM_PIXELS, + .sfr_10 = mean_sfr / HII_TOT_NUM_PIXELS, + .sfr_100 = mean_sfr / HII_TOT_NUM_PIXELS, + .sfr_10_mcg = mean_sfr_mini / HII_TOT_NUM_PIXELS, + .sfr_100_mcg = mean_sfr_mini / HII_TOT_NUM_PIXELS, .n_ion = mean_n_ion / HII_TOT_NUM_PIXELS, .halo_xray = mean_xray / HII_TOT_NUM_PIXELS, .fescweighted_sfr = mean_wsfr / HII_TOT_NUM_PIXELS, @@ -209,10 +214,10 @@ void mean_fix_grids(double M_min, double M_max, HaloBox *grids, ScalingConstants unsigned long long int idx; #pragma omp parallel for num_threads(simulation_options_global->N_THREADS) private(idx) for (idx = 0; idx < HII_TOT_NUM_PIXELS; idx++) { - grids->halo_sfr[idx] *= averages_global.halo_sfr / averages_hbox.halo_sfr; + grids->halo_sfr[idx] *= averages_global.sfr_10 / averages_hbox.sfr_10; grids->n_ion[idx] *= averages_global.n_ion / averages_hbox.n_ion; if (astro_options_global->USE_MINI_HALOS) { - grids->halo_sfr_mini[idx] *= averages_global.sfr_mini / averages_hbox.sfr_mini; + grids->halo_sfr_mini[idx] *= averages_global.sfr_10_mcg / averages_hbox.sfr_10_mcg; } if (astro_options_global->USE_TS_FLUCT) { grids->halo_xray[idx] *= averages_global.halo_xray / averages_hbox.halo_xray; @@ -286,16 +291,6 @@ void get_cell_integrals(double dens, double l10_mturn_a, double l10_mturn_m, int set_fixed_grids(double M_min, double M_max, InitialConditions *ini_boxes, float *mturn_a_grid, float *mturn_m_grid, ScalingConstants *consts, HaloBox *grids) { double M_cell; - // If our scaling relations define a median, the scatter will will increase the mean value - // due to the asymmetry of the lognormal distribution, we mimic this in the - // sub-sampler component. - ScalingConstants _ev_consts = *consts; - ScalingConstants *ev_consts = &_ev_consts; - - if (astro_options_global->HALO_SCALING_RELATIONS_MEDIAN) { - _ev_consts = mimic_scatter_in_consts(consts); - } - double growthf = dicke(ev_consts->redshift); // find grid limits for tables double min_density = 0.; @@ -341,7 +336,7 @@ int set_fixed_grids(double M_min, double M_max, InitialConditions *ini_boxes, fl } IntegralCondition integral_cond; - set_integral_constants(&integral_cond, ev_consts->redshift, M_min, M_max, M_cell); + set_integral_constants(&integral_cond, consts->redshift, M_min, M_max, M_cell); #pragma omp parallel num_threads(simulation_options_global->N_THREADS) { unsigned long long int i; @@ -353,8 +348,8 @@ int set_fixed_grids(double M_min, double M_max, InitialConditions *ini_boxes, fl if (dens < min_density) min_density = dens; } - double M_turn_m = ev_consts->mturn_m_nofb; - double M_turn_a = ev_consts->mturn_a_nofb; + double M_turn_m = consts->mturn_m_nofb; + double M_turn_a = consts->mturn_a_nofb; #pragma omp for reduction(min : min_log10_mturn_a, min_log10_mturn_m) \ reduction(max : max_log10_mturn_a, max_log10_mturn_m) for (i = 0; i < HII_TOT_NUM_PIXELS; i++) { @@ -385,25 +380,25 @@ int set_fixed_grids(double M_min, double M_max, InitialConditions *ini_boxes, fl initialise_GL(integral_cond.lnM_min, integral_cond.lnM_max); } // This table assumes no reionisation feedback - initialise_SFRD_Conditional_table(ev_consts->redshift, min_density, max_density, M_min, - M_max, M_cell, ev_consts); + initialise_SFRD_Conditional_table(consts->redshift, min_density, max_density, M_min, M_max, + M_cell, consts); // This table includes reionisation feedback, but takes the atomic turnover anyway for the // upper turnover - initialise_Nion_Conditional_spline(ev_consts->redshift, min_density, max_density, M_min, - M_max, M_cell, min_log10_mturn_a, max_log10_mturn_a, - min_log10_mturn_m, max_log10_mturn_m, ev_consts, false); + initialise_Nion_Conditional_spline(consts->redshift, min_density, max_density, M_min, M_max, + M_cell, min_log10_mturn_a, max_log10_mturn_a, + min_log10_mturn_m, max_log10_mturn_m, consts, false); initialise_dNdM_tables(min_density, max_density, integral_cond.lnM_min, integral_cond.lnM_max, integral_cond.growth_factor, integral_cond.lnM_cell, false); if (astro_options_global->USE_TS_FLUCT) { - initialise_Xray_Conditional_table(ev_consts->redshift, min_density, max_density, M_min, - M_max, M_cell, ev_consts); + initialise_Xray_Conditional_table(consts->redshift, min_density, max_density, M_min, + M_max, M_cell, consts); } } - move_grid_galprops(ev_consts->redshift, dens_pointer, grid_dim, vel_pointers, vel_pointers_2LPT, - grid_dim, grids, out_dim, mturn_a_grid, mturn_m_grid, ev_consts, + move_grid_galprops(consts->redshift, dens_pointer, grid_dim, vel_pointers, vel_pointers_2LPT, + grid_dim, grids, out_dim, mturn_a_grid, mturn_m_grid, consts, &integral_cond); LOG_ULTRA_DEBUG("Cell 0 Totals: SF: %.2e, NI: %.2e", grids->halo_sfr[0], grids->n_ion[0]); @@ -419,7 +414,7 @@ int set_fixed_grids(double M_min, double M_max, InitialConditions *ini_boxes, fl } free_conditional_tables(); - if (ev_consts->fix_mean) mean_fix_grids(M_min, M_max, grids, ev_consts); + if (consts->fix_mean) mean_fix_grids(M_min, M_max, grids, consts); return 0; } @@ -546,7 +541,7 @@ void sum_halos_onto_grid(double redshift, InitialConditions *ini_boxes, HaloCata vel_pointers_2LPT[1] = ini_boxes->lowres_vy_2LPT; vel_pointers_2LPT[2] = ini_boxes->lowres_vz_2LPT; } - move_halo_galprops(redshift, halos, progenitor_hm, progenitor_sm, vel_pointers, + move_halo_galprops(halos, progenitor_hm, progenitor_sm, progenitor_sm_mini, vel_pointers, vel_pointers_2LPT, vel_dim, mturn_a_grid, mturn_m_grid, grids, out_dim, consts); @@ -563,9 +558,8 @@ void sum_halos_onto_grid(double redshift, InitialConditions *ini_boxes, HaloCata } // We grid a PERTURBED halofield into the necessary quantities for calculating radiative backgrounds -int ComputeHaloBox(double redshift, InitialConditions *ini_boxes, HaloCatalog *halos, - HaloCatalog *halos_prev, TsBox *previous_spin_temp, - IonizedBox *previous_ionize_box, HaloBox *grids) { +int ComputeHaloBox(InitialConditions *ini_boxes, HaloCatalog *halos, HaloCatalog *halos_prev, + TsBox *previous_spin_temp, IonizedBox *previous_ionize_box, HaloBox *grids) { int status; Try { // get parameters @@ -575,6 +569,8 @@ int ComputeHaloBox(double redshift, InitialConditions *ini_boxes, HaloCatalog *h "And current catalogues must not have SFRs computed."); Throw(ValueError); } + double redshift = get_current_redshift(ini_boxes); + double redshift_prev = get_previous_redshift(ini_boxes); #if LOG_LEVEL >= SUPER_DEBUG_LEVEL writeSimulationOptions(simulation_options_global); @@ -633,7 +629,7 @@ int ComputeHaloBox(double redshift, InitialConditions *ini_boxes, HaloCatalog *h grids->log10_Mcrit_ACG_ave = mturn_averages[0]; grids->log10_Mcrit_MCG_ave = mturn_averages[1]; if (matter_options_global->SOURCE_MODEL > 2) { - sum_halos_onto_grid(redshift, ini_boxes, halos, mturn_a_grid, mturn_m_grid, + sum_halos_onto_grid(redshift, ini_boxes, halos, halos_prev, mturn_a_grid, mturn_m_grid, &hbox_consts, grids); } // set sub-catalogue properties @@ -652,7 +648,7 @@ int ComputeHaloBox(double redshift, InitialConditions *ini_boxes, HaloCatalog *h } halobox_debug_print_avg(grids, &hbox_consts, M_min, M_MAX_INTEGRAL); - halos->sfr_computed = true; + halos->sfh_computed = true; if (astro_options_global->USE_MINI_HALOS) { free(mturn_a_grid); @@ -674,10 +670,10 @@ int ComputeHaloBox(double redshift, InitialConditions *ini_boxes, HaloCatalog *h // test function for getting halo properties from the wrapper, can use a lot of memory for large // catalogs -int test_halo_props(double redshift, float *vcb_grid, float *J21_LW_grid, float *z_re_grid, - float *Gamma12_ion_grid, unsigned long long int n_halos, float *halo_masses, - float *halo_coords, float *star_rng, float *sfr_rng, float *xray_rng, - float *halo_props_out) { +int test_halo_props(double redshift, double redshift_prev, float *vcb_grid, float *J21_LW_grid, + float *z_re_grid, float *Gamma12_ion_grid, unsigned long long int n_halos, + float *halo_masses, float *halo_coords, float *sfr_10, float *sfr_100, + float *stellar_mass, float *halo_props_out) { int status; Try { // get parameters @@ -686,6 +682,8 @@ int test_halo_props(double redshift, float *vcb_grid, float *J21_LW_grid, float set_scaling_constants(redshift, &hbox_consts, true); print_sc_consts(&hbox_consts); + double snapshot_time = time_between_z(redshift_prev, redshift); + LOG_DEBUG("Getting props for %llu halos at z=%.2f", n_halos, redshift); double cell_length = @@ -757,22 +755,26 @@ int test_halo_props(double redshift, float *vcb_grid, float *J21_LW_grid, float } // these are the halo property RNG sequences - in_props[0] = star_rng[i_halo]; - in_props[1] = sfr_rng[i_halo]; - in_props[2] = xray_rng[i_halo]; + in_props[0] = sfr_10[i_halo]; + in_props[1] = sfr_100[i_halo]; + in_props[2] = stellar_mass[i_halo]; - set_halo_properties(m, M_turn_a, M_turn_m, &hbox_consts, in_props, &out_props); + // TODO: remove this placeholder before merge + double prog_sm[2] = {0., 0.}; + double prog_hm = 0.; + set_halo_properties(snapshot_time, m, M_turn_a, M_turn_m, prog_hm, prog_sm, + &hbox_consts, in_props, &out_props); halo_props_out[12 * i_halo + 0] = out_props.halo_mass; halo_props_out[12 * i_halo + 1] = out_props.stellar_mass; - halo_props_out[12 * i_halo + 2] = out_props.halo_sfr; + halo_props_out[12 * i_halo + 2] = out_props.sfr_10; halo_props_out[12 * i_halo + 3] = out_props.halo_xray; halo_props_out[12 * i_halo + 4] = out_props.n_ion; halo_props_out[12 * i_halo + 5] = out_props.fescweighted_sfr; halo_props_out[12 * i_halo + 6] = out_props.stellar_mass_mini; - halo_props_out[12 * i_halo + 7] = out_props.sfr_mini; + halo_props_out[12 * i_halo + 7] = out_props.sfr_10_mcg; halo_props_out[12 * i_halo + 8] = M_turn_a; halo_props_out[12 * i_halo + 9] = M_turn_m; @@ -798,10 +800,11 @@ int test_halo_props(double redshift, float *vcb_grid, float *J21_LW_grid, float return 0; } -int convert_halo_props(double redshift, InitialConditions *ics, TsBox *prev_ts, - IonizedBox *prev_ion, HaloCatalog *halo_catalog, - PerturbedHaloCatalog *halo_catalog_out) { +int convert_halo_props(InitialConditions *ics, TsBox *prev_ts, IonizedBox *prev_ion, + HaloCatalog *halo_catalog, PerturbedHaloCatalog *halo_catalog_out) { ScalingConstants hbox_consts; + double redshift = get_current_redshift(); + double snapshot_time = time_between_z(get_previous_redshift(), redshift); set_scaling_constants(redshift, &hbox_consts, true); // print_sc_consts(&hbox_consts); float *mturn_a_grid = NULL; @@ -853,24 +856,31 @@ int convert_halo_props(double redshift, InitialConditions *ics, TsBox *prev_ts, } // these are the halo property RNG sequences - in_props[0] = halo_catalog->star_rng[i_halo]; - in_props[1] = halo_catalog->sfr_rng[i_halo]; - in_props[2] = halo_catalog->xray_rng[i_halo]; + in_props[0] = halo_catalog->sfr_10[i_halo]; + in_props[1] = halo_catalog->sfr_100[i_halo]; + in_props[2] = halo_catalog->stellar_mass[i_halo]; LOG_ULTRA_DEBUG("Halo %llu mass %.2e Mturn_a %.2e Mturn_m %.2e", i_halo, m, M_turn_a, M_turn_m); LOG_ULTRA_DEBUG("RNG: STAR %.2e SFR %.2e XRAY %.2e", in_props[0], in_props[1], in_props[2]); - set_halo_properties(m, M_turn_a, M_turn_m, &hbox_consts, in_props, &out_props); + /// TODO: REMOVE THIS PLACEHOLDER BEFORE MERGE + double prog_sm[2] = {0., 0.}; + double prog_hm = 0.; + + set_halo_properties(snapshot_time, m, M_turn_a, M_turn_m, prog_hm, prog_sm, + &hbox_consts, in_props, &out_props); + + // TODO: set outputs properly halo_catalog_out->halo_masses[i_halo] = out_props.halo_mass; halo_catalog_out->stellar_masses[i_halo] = out_props.stellar_mass; - halo_catalog_out->sfr[i_halo] = out_props.halo_sfr; + halo_catalog_out->sfr[i_halo] = out_props.sfr_10; halo_catalog_out->ion_emissivity[i_halo] = out_props.n_ion; if (astro_options_global->USE_MINI_HALOS) { halo_catalog_out->stellar_mini[i_halo] = out_props.stellar_mass_mini; - halo_catalog_out->sfr_mini[i_halo] = out_props.sfr_mini; + halo_catalog_out->sfr_mini[i_halo] = out_props.sfr_10_mcg; } if (astro_options_global->INHOMO_RECO) { halo_catalog_out->fesc_sfr[i_halo] = out_props.fescweighted_sfr; diff --git a/src/py21cmfast/src/HaloBox.h b/src/py21cmfast/src/HaloBox.h index 2f3aaecb4..4d86d4074 100644 --- a/src/py21cmfast/src/HaloBox.h +++ b/src/py21cmfast/src/HaloBox.h @@ -49,16 +49,17 @@ typedef struct IntegralCondition { void set_integral_constants(IntegralCondition *consts, double redshift, double M_min, double M_max, double M_cell); -int ComputeHaloBox(double redshift, InitialConditions *ini_boxes, HaloCatalog *halos, +int ComputeHaloBox(InitialConditions *ini_boxes, HaloCatalog *halos, HaloCatalog *halos_prev, TsBox *previous_spin_temp, IonizedBox *previous_ionize_box, HaloBox *grids); void get_cell_integrals(double dens, double l10_mturn_a, double l10_mturn_m, ScalingConstants *consts, IntegralCondition *int_consts, HaloProperties *properties); -void set_halo_properties(double halo_mass, double M_turn_a, double M_turn_m, - ScalingConstants *consts, double *input_rng, HaloProperties *output); -int convert_halo_props(double redshift, InitialConditions *ics, TsBox *prev_ts, - IonizedBox *prev_ion, HaloCatalog *halo_catalog, - PerturbedHaloCatalog *halo_catalog_out); +void set_halo_properties(double snapshot_time, double halo_mass, double M_turn_a, double M_turn_m, + double prog_hm, double prog_sm[2], ScalingConstants *consts, + double *input_rng, HaloProperties *output); + +int convert_halo_props(InitialConditions *ics, TsBox *prev_ts, IonizedBox *prev_ion, + HaloCatalog *halo_catalog, PerturbedHaloCatalog *halo_catalog_out); #endif diff --git a/src/py21cmfast/src/HaloCatalog.c b/src/py21cmfast/src/HaloCatalog.c index 3b26e4ba6..668e4f6e6 100644 --- a/src/py21cmfast/src/HaloCatalog.c +++ b/src/py21cmfast/src/HaloCatalog.c @@ -35,13 +35,14 @@ int pixel_in_halo(int grid_dim, int z_dim, int x, int x_index, int y, int y_inde int z_index, float Rsq_curr_index); void free_halo_catalog(HaloCatalog *halos); -int ComputeHaloCatalog(float redshift_desc2, float redshift_desc, float redshift, - InitialConditions *boxes, unsigned long long int random_seed, +int ComputeHaloCatalog(InitialConditions *boxes, unsigned long long int random_seed, HaloCatalog *halos_desc, HaloCatalog *halos) { int status; Try { // This Try brackets the whole function, so we don't indent. - bool from_catalog = matter_options_global->SOURCE_MODEL == 4 && redshift_desc > 0; + double redshift = get_current_redshift(boxes); + bool from_catalog = + (matter_options_global->SOURCE_MODEL == 4 && get_descendant_redshift() > 0); if (halos->sfh_computed || halos_desc->sfh_computed) { LOG_ERROR( "You have passed a halo catalog with SFH already computed to the stochastic " @@ -49,7 +50,6 @@ int ComputeHaloCatalog(float redshift_desc2, float redshift_desc, float redshift "This is not allowed."); Throw(ValueError); } - initialise_sfh_structs(redshift, redshift_desc, redshift_desc2, from_catalog); // This happens if we are updating a halo field (no need to redo big halos) if (from_catalog) { LOG_DEBUG("Halo sampling switched on, bypassing halo finder to update %llu halos...", @@ -57,8 +57,7 @@ int ComputeHaloCatalog(float redshift_desc2, float redshift_desc, float redshift // this would hold the two boxes used in the halo sampler, but here we are taking the // sample from a catalogue so we define a dummy here float *dummy_box = NULL; - stochastic_halofield(random_seed, redshift_desc, redshift, dummy_box, dummy_box, - halos_desc, halos); + stochastic_halofield(random_seed, dummy_box, dummy_box, halos_desc, halos); return 0; } @@ -410,8 +409,8 @@ int ComputeHaloCatalog(float redshift_desc2, float redshift_desc, float redshift } } - stochastic_halofield(random_seed, redshift_desc, redshift, boxes->lowres_density, - halo_overlap_box, halos_dexm, halos); + stochastic_halofield(random_seed, boxes->lowres_density, halo_overlap_box, halos_dexm, + halos); // Here, halos_dexm is allocated in the C, so free it free_halo_catalog(halos_dexm); @@ -431,8 +430,6 @@ int ComputeHaloCatalog(float redshift_desc2, float redshift_desc, float redshift fftwf_free(density_field); fftwf_free(density_field_saved); - cleanup_sfh_structs(); - fftwf_cleanup_threads(); fftwf_cleanup(); fftwf_forget_wisdom(); @@ -549,18 +546,18 @@ void init_halo_coords(HaloCatalog *halos, long long unsigned int n_halos) { halos->halo_masses = (float *)calloc(alloc_size, sizeof(float)); halos->halo_coords = (float *)calloc(3 * alloc_size, sizeof(float)); - halos->star_rng = (float *)calloc(alloc_size, sizeof(float)); - halos->sfr_rng = (float *)calloc(alloc_size, sizeof(float)); - halos->xray_rng = (float *)calloc(alloc_size, sizeof(float)); + halos->sfr_10 = (float *)calloc(alloc_size, sizeof(float)); + halos->sfr_100 = (float *)calloc(alloc_size, sizeof(float)); + halos->stellar_mass = (float *)calloc(alloc_size, sizeof(float)); } void free_halo_catalog(HaloCatalog *halos) { LOG_DEBUG("Freeing HaloCatalog instance."); free(halos->halo_masses); free(halos->halo_coords); - free(halos->star_rng); - free(halos->sfr_rng); - free(halos->xray_rng); + free(halos->sfr_10); + free(halos->sfr_100); + free(halos->stellar_mass); halos->n_halos = 0; } diff --git a/src/py21cmfast/src/HaloCatalog.h b/src/py21cmfast/src/HaloCatalog.h index 04a591681..e48e65e92 100644 --- a/src/py21cmfast/src/HaloCatalog.h +++ b/src/py21cmfast/src/HaloCatalog.h @@ -5,8 +5,7 @@ #include "InputParameters.h" #include "OutputStructs.h" -int ComputeHaloCatalog(float redshift_desc, float redshift, InitialConditions *boxes, - unsigned long long int random_seed, HaloCatalog *halos_desc, - HaloCatalog *halos); +int ComputeHaloCatalog(InitialConditions *boxes, unsigned long long int random_seed, + HaloCatalog *halos_desc, HaloCatalog *halos); #endif diff --git a/src/py21cmfast/src/InputParameters.c b/src/py21cmfast/src/InputParameters.c index 574f6ca72..31d8a7da8 100644 --- a/src/py21cmfast/src/InputParameters.c +++ b/src/py21cmfast/src/InputParameters.c @@ -17,12 +17,33 @@ void Broadcast_struct_global_noastro(SimulationOptions *simulation_options, cosmo_params_global = cosmo_params; } +void Broadcast_snapshot_info(int n_nodes, double *node_redshifts, int curr_node) { + node_redshifts_global.n_nodes = n_nodes; + node_redshifts_global.node_redshifts = node_redshifts; + node_redshifts_global.curr_node = curr_node; +} + +double get_redshift_relative(int offset) { + int target_node = node_redshifts_global.curr_node + offset; + if (target_node >= 0 && target_node < node_redshifts_global.n_nodes) { + return node_redshifts_global.node_redshifts[target_node]; + } else { + return -1.0; // or some other sentinel value indicating out of bounds + } +} + +// some useful aliases +double get_current_redshift() { return get_redshift_relative(0); } +double get_previous_redshift() { return get_redshift_relative(-1); } +double get_descendant_redshift() { return get_redshift_relative(1); } + /*GLOBAL INPUT STRUCT DEFINITION*/ SimulationOptions *simulation_options_global; MatterOptions *matter_options_global; CosmoParams *cosmo_params_global; AstroParams *astro_params_global; AstroOptions *astro_options_global; +NodeRedshifts node_redshifts_global; // data paths, wisdoms, etc ConfigSettings config_settings; diff --git a/src/py21cmfast/src/InputParameters.h b/src/py21cmfast/src/InputParameters.h index ce30c5ae5..a7adc4aa8 100644 --- a/src/py21cmfast/src/InputParameters.h +++ b/src/py21cmfast/src/InputParameters.h @@ -12,4 +12,11 @@ void Broadcast_struct_global_all(SimulationOptions *simulation_options, void Broadcast_struct_global_noastro(SimulationOptions *simulation_options, MatterOptions *matter_options, CosmoParams *cosmo_params); +void Broadcast_snapshot_info(int n_nodes, double *node_redshifts, int curr_node); + +double get_redshift_relative(int offset); +double get_current_redshift(); +double get_previous_redshift(); +double get_descendant_redshift(); + #endif diff --git a/src/py21cmfast/src/IonisationBox.c b/src/py21cmfast/src/IonisationBox.c index aee309f7d..9fb8c8f84 100644 --- a/src/py21cmfast/src/IonisationBox.c +++ b/src/py21cmfast/src/IonisationBox.c @@ -1291,10 +1291,9 @@ void set_recombination_rates(IonizedBox *box, IonizedBox *previous_ionize_box, } } -int ComputeIonizedBox(float redshift, float prev_redshift, PerturbedField *perturbed_field, - PerturbedField *previous_perturbed_field, IonizedBox *previous_ionize_box, - TsBox *spin_temp, HaloBox *halos, InitialConditions *ini_boxes, - IonizedBox *box) { +int ComputeIonizedBox(PerturbedField *perturbed_field, PerturbedField *previous_perturbed_field, + IonizedBox *previous_ionize_box, TsBox *spin_temp, HaloBox *halos, + InitialConditions *ini_boxes, IonizedBox *box) { int status; Try { // This Try brackets the whole function, so we don't indent. @@ -1308,6 +1307,9 @@ int ComputeIonizedBox(float redshift, float prev_redshift, PerturbedField *pertu writeAstroOptions(astro_options_global); #endif + double redshift = get_current_redshift(); + double prev_redshift = get_previous_redshift(); + // Makes the parameter structs visible to a variety of functions/macros // Do each time to avoid Python garbage collection issues omp_set_num_threads(simulation_options_global->N_THREADS); diff --git a/src/py21cmfast/src/IonisationBox.h b/src/py21cmfast/src/IonisationBox.h index e3a12e0b7..a2c281a85 100644 --- a/src/py21cmfast/src/IonisationBox.h +++ b/src/py21cmfast/src/IonisationBox.h @@ -4,9 +4,8 @@ #include "InputParameters.h" #include "OutputStructs.h" -int ComputeIonizedBox(float redshift, float prev_redshift, PerturbedField *perturbed_field, - PerturbedField *previous_perturbed_field, IonizedBox *previous_ionize_box, - TsBox *spin_temp, HaloBox *halos, InitialConditions *ini_boxes, - IonizedBox *box); +int ComputeIonizedBox(PerturbedField *perturbed_field, PerturbedField *previous_perturbed_field, + IonizedBox *previous_ionize_box, TsBox *spin_temp, HaloBox *halos, + InitialConditions *ini_boxes, IonizedBox *box); #endif diff --git a/src/py21cmfast/src/PerturbedField.c b/src/py21cmfast/src/PerturbedField.c index 8157c2b6c..781f24125 100644 --- a/src/py21cmfast/src/PerturbedField.c +++ b/src/py21cmfast/src/PerturbedField.c @@ -382,8 +382,7 @@ void compute_perturbed_velocities(unsigned short axis, double redshift, simulation_options_global->HII_DIM, HII_D_PARA, " "); } -int ComputePerturbedField(float redshift, InitialConditions *boxes, - PerturbedField *perturbed_field) { +int ComputePerturbedField(InitialConditions *boxes, PerturbedField *perturbed_field) { /* ComputePerturbedField uses the first-order Langragian displacement field to move the masses in the cells of the density field. The high-res density field is extrapolated @@ -395,6 +394,7 @@ int ComputePerturbedField(float redshift, InitialConditions *boxes, int status; Try { // This Try{} wraps the whole function, so we don't indent. + double redshift = get_current_redshift(boxes); // Makes the parameter structs visible to a variety of functions/macros // Do each time to avoid Python garbage collection issues diff --git a/src/py21cmfast/src/PerturbedField.h b/src/py21cmfast/src/PerturbedField.h index b633b52b3..c5c209eee 100644 --- a/src/py21cmfast/src/PerturbedField.h +++ b/src/py21cmfast/src/PerturbedField.h @@ -4,7 +4,6 @@ #include "InputParameters.h" #include "OutputStructs.h" -int ComputePerturbedField(float redshift, InitialConditions *boxes, - PerturbedField *perturbed_field); +int ComputePerturbedField(InitialConditions *boxes, PerturbedField *perturbed_field); #endif diff --git a/src/py21cmfast/src/PerturbedHaloCatalog.c b/src/py21cmfast/src/PerturbedHaloCatalog.c index 88e02bdd4..283ea3718 100644 --- a/src/py21cmfast/src/PerturbedHaloCatalog.c +++ b/src/py21cmfast/src/PerturbedHaloCatalog.c @@ -22,13 +22,12 @@ #include "indexing.h" #include "logger.h" -int ComputePerturbedHaloCatalog(float redshift, InitialConditions *boxes, TsBox *prev_ts, - IonizedBox *prev_ion, HaloCatalog *halos, - PerturbedHaloCatalog *halos_perturbed) { +int ComputePerturbedHaloCatalog(InitialConditions *boxes, TsBox *prev_ts, IonizedBox *prev_ion, + HaloCatalog *halos, PerturbedHaloCatalog *halos_perturbed) { int status; Try { // This Try brackets the whole function, so we don't indent. - + double redshift = get_current_redshift(boxes); LOG_DEBUG("input value:"); LOG_DEBUG("redshift=%f", redshift); #if LOG_LEVEL >= SUPER_DEBUG_LEVEL @@ -134,8 +133,7 @@ int ComputePerturbedHaloCatalog(float redshift, InitialConditions *boxes, TsBox } LOG_DEBUG("starting haloprops"); - convert_halo_props(redshift, boxes, prev_ts, prev_ion, halos, halos_perturbed); - // Divide out multiplicative factor to return to pristine state + convert_halo_props(boxes, prev_ts, prev_ion, halos, halos_perturbed); LOG_SUPER_DEBUG("Number of halos exactly on the box edge = %llu of %llu", n_exact_dim, halos->n_halos); if (error_in_parallel) { diff --git a/src/py21cmfast/src/PerturbedHaloCatalog.h b/src/py21cmfast/src/PerturbedHaloCatalog.h index 3892a8b4a..4aa4403c9 100644 --- a/src/py21cmfast/src/PerturbedHaloCatalog.h +++ b/src/py21cmfast/src/PerturbedHaloCatalog.h @@ -4,8 +4,7 @@ #include "InputParameters.h" #include "OutputStructs.h" -int ComputePerturbedHaloCatalog(float redshift, InitialConditions *boxes, TsBox *prev_ts, - IonizedBox *prev_ion, HaloCatalog *halos, - PerturbedHaloCatalog *halos_perturbed); +int ComputePerturbedHaloCatalog(InitialConditions *boxes, TsBox *prev_ts, IonizedBox *prev_ion, + HaloCatalog *halos, PerturbedHaloCatalog *halos_perturbed); #endif diff --git a/src/py21cmfast/src/SpinTemperatureBox.c b/src/py21cmfast/src/SpinTemperatureBox.c index 193a60d8f..7d8092323 100644 --- a/src/py21cmfast/src/SpinTemperatureBox.c +++ b/src/py21cmfast/src/SpinTemperatureBox.c @@ -29,9 +29,8 @@ when the code is run with redshift poor resolution and very high X-ray heating efficiency */ #define MAX_TK (float)5e4 -void ts_main(float redshift, float prev_redshift, float perturbed_field_redshift, short cleanup, - PerturbedField *perturbed_field, XraySourceBox *source_box, TsBox *previous_spin_temp, - InitialConditions *ini_boxes, TsBox *this_spin_temp); +void ts_main(short cleanup, PerturbedField *perturbed_field, XraySourceBox *source_box, + TsBox *previous_spin_temp, InitialConditions *ini_boxes, TsBox *this_spin_temp); // Global arrays which have yet to be moved to structs // R x box arrays @@ -85,14 +84,12 @@ bool TsInterpArraysInitialised = false; // a debug flag for printing results from a single cell without passing cell number to the functions static int debug_printed; -int ComputeTsBox(float redshift, float prev_redshift, float perturbed_field_redshift, short cleanup, - PerturbedField *perturbed_field, XraySourceBox *source_box, +int ComputeTsBox(short cleanup, PerturbedField *perturbed_field, XraySourceBox *source_box, TsBox *previous_spin_temp, InitialConditions *ini_boxes, TsBox *this_spin_temp) { int status; Try { // This Try{} wraps the whole function. LOG_DEBUG("Spintemp input values:"); - LOG_DEBUG("redshift=%f, prev_redshift=%f perturbed_field_redshift=%f", redshift, - prev_redshift, perturbed_field_redshift); + LOG_DEBUG("redshift=%f, prev_redshift=%f", redshift, prev_redshift); #if LOG_LEVEL >= SUPER_DEBUG_LEVEL writeSimulationOptions(simulation_options_global); @@ -107,8 +104,8 @@ int ComputeTsBox(float redshift, float prev_redshift, float perturbed_field_reds omp_set_num_threads(simulation_options_global->N_THREADS); debug_printed = 0; - ts_main(redshift, prev_redshift, perturbed_field_redshift, cleanup, perturbed_field, - source_box, previous_spin_temp, ini_boxes, this_spin_temp); + ts_main(cleanup, perturbed_field, source_box, previous_spin_temp, ini_boxes, + this_spin_temp); destruct_heat(); @@ -859,18 +856,18 @@ void fill_freqint_tables(double zp, double x_e_ave, double filling_factor_of_HI_ // construct a Ts table above Z_HEAT_MAX, this can happen if we are computing the first box or if we // request a redshift above Z_HEAT_MAX -void init_first_Ts(TsBox *box, float *dens, float z, float zp, double *x_e_ave, double *Tk_ave) { +void init_first_Ts(TsBox *box, float *dens, float z, double *x_e_ave, double *Tk_ave) { unsigned long long int box_ct; // zp is the requested redshift, z is the perturbed field redshift float growth_factor_zp; float inverse_growth_factor_z; double xe, TK, cT_ad; - xe = xion_RECFAST(zp, 0); - TK = T_RECFAST(zp, 0); - cT_ad = cT_approx(zp); + xe = xion_RECFAST(z, 0); + TK = T_RECFAST(z, 0); + cT_ad = cT_approx(z); - growth_factor_zp = dicke(zp); + growth_factor_zp = dicke(z); inverse_growth_factor_z = 1 / dicke(z); *x_e_ave = xe; @@ -1333,9 +1330,8 @@ struct Ts_cell get_Ts_fast(float zp, float dzp, struct spintemp_from_sfr_prefact } // outer-level function for calculating Ts based on the Halo boxes -void ts_main(float redshift, float prev_redshift, float perturbed_field_redshift, short cleanup, - PerturbedField *perturbed_field, XraySourceBox *source_box, TsBox *previous_spin_temp, - InitialConditions *ini_boxes, TsBox *this_spin_temp) { +void ts_main(short cleanup, PerturbedField *perturbed_field, XraySourceBox *source_box, + TsBox *previous_spin_temp, InitialConditions *ini_boxes, TsBox *this_spin_temp) { int R_ct; unsigned long long int box_ct; double x_e_ave_p, Tk_ave_p; @@ -1351,9 +1347,13 @@ void ts_main(float redshift, float prev_redshift, float perturbed_field_redshift alloc_global_arrays(); } + // NOTE: If we re-allow other redshifts in perturbed_field, this will need to change + double redshift = get_current_redshift(); + double prev_redshift = get_previous_redshift(); + // NOTE: For the code to work, previous_spin_temp MUST be allocated & // calculated if redshift < Z_HEAT_MAX - growth_factor_z = dicke(perturbed_field_redshift); + growth_factor_z = dicke(redshift); inverse_growth_factor_z = 1. / growth_factor_z; growth_factor_zp = dicke(redshift); @@ -1384,8 +1384,7 @@ void ts_main(float redshift, float prev_redshift, float perturbed_field_redshift init_heat(); if (redshift >= simulation_options_global->Z_HEAT_MAX) { LOG_DEBUG("redshift greater than Z_HEAT_MAX"); - init_first_Ts(this_spin_temp, perturbed_field->density, perturbed_field_redshift, redshift, - &x_e_ave_p, &Tk_ave_p); + init_first_Ts(this_spin_temp, perturbed_field->density, redshift, &x_e_ave_p, &Tk_ave_p); return; } diff --git a/src/py21cmfast/src/SpinTemperatureBox.h b/src/py21cmfast/src/SpinTemperatureBox.h index 2c607ff38..bc815cd7d 100644 --- a/src/py21cmfast/src/SpinTemperatureBox.h +++ b/src/py21cmfast/src/SpinTemperatureBox.h @@ -4,8 +4,7 @@ #include "InputParameters.h" #include "OutputStructs.h" -int ComputeTsBox(float redshift, float prev_redshift, float perturbed_field_redshift, short cleanup, - PerturbedField *perturbed_field, XraySourceBox *source_box, +int ComputeTsBox(short cleanup, PerturbedField *perturbed_field, XraySourceBox *source_box, TsBox *previous_spin_temp, InitialConditions *ini_boxes, TsBox *this_spin_temp); int UpdateXraySourceBox(HaloBox *halobox, double R_inner, double R_outer, int R_ct, diff --git a/src/py21cmfast/src/Stochasticity.c b/src/py21cmfast/src/Stochasticity.c index c038cf497..0744534bc 100644 --- a/src/py21cmfast/src/Stochasticity.c +++ b/src/py21cmfast/src/Stochasticity.c @@ -23,6 +23,7 @@ #include "interp_tables.h" #include "logger.h" #include "rng.h" + // buffer size (per cell of arbitrary size) in the sampling function #define MAX_HALO_CELL (int)1e5 @@ -79,7 +80,7 @@ double sample_dndM_inverse(double condition, struct HaloSamplingConstants *hs_co // Set the constants that are calculated once per snapshot void stoc_set_consts_z(struct HaloSamplingConstants *const_struct, double redshift, double redshift_desc, bool from_catalog) { - if (redshift_desc > 0 && redshift < redshift_desc) { + if (from_catalog && redshift < redshift_desc) { LOG_ERROR("you have passed a descendant redshift above the progenitor redshift"); Throw(ValueError); } @@ -698,11 +699,11 @@ void condense_sparse_halolist(HaloCatalog *halofield, unsigned long long int *is for (i = 0; i < simulation_options_global->N_THREADS; i++) { memmove(&halofield->halo_masses[count_total], &halofield->halo_masses[istart_threads[i]], sizeof(float) * nhalo_threads[i]); - memmove(&halofield->star_rng[count_total], &halofield->star_rng[istart_threads[i]], + memmove(&halofield->sfr_10[count_total], &halofield->sfr_10[istart_threads[i]], sizeof(float) * nhalo_threads[i]); - memmove(&halofield->sfr_rng[count_total], &halofield->sfr_rng[istart_threads[i]], + memmove(&halofield->sfr_100[count_total], &halofield->sfr_100[istart_threads[i]], sizeof(float) * nhalo_threads[i]); - memmove(&halofield->xray_rng[count_total], &halofield->xray_rng[istart_threads[i]], + memmove(&halofield->stellar_mass[count_total], &halofield->stellar_mass[istart_threads[i]], sizeof(float) * nhalo_threads[i]); memmove(&halofield->halo_coords[3 * count_total], &halofield->halo_coords[3 * istart_threads[i]], @@ -718,11 +719,11 @@ void condense_sparse_halolist(HaloCatalog *halofield, unsigned long long int *is (halofield->buffer_size - count_total) * sizeof(float)); memset(&halofield->halo_coords[3 * count_total], 0, 3 * (halofield->buffer_size - count_total) * sizeof(float)); - memset(&halofield->star_rng[count_total], 0, + memset(&halofield->sfr_10[count_total], 0, (halofield->buffer_size - count_total) * sizeof(float)); - memset(&halofield->sfr_rng[count_total], 0, + memset(&halofield->sfr_100[count_total], 0, (halofield->buffer_size - count_total) * sizeof(float)); - memset(&halofield->xray_rng[count_total], 0, + memset(&halofield->stellar_mass[count_total], 0, (halofield->buffer_size - count_total) * sizeof(float)); LOG_SUPER_DEBUG("Set %llu elements beyond %llu to zero", halofield->buffer_size - count_total, count_total); @@ -788,9 +789,9 @@ int sample_halo_grids(gsl_rng **rng_arr, double redshift, float *dens_field, #pragma omp for reduction(+ : total_volume_dexm) for (halo_idx = 0; halo_idx < nhalo_in; halo_idx++) { halofield_out->halo_masses[istart + count] = halofield_large->halo_masses[halo_idx]; - halofield_out->star_rng[istart + count] = halofield_large->star_rng[halo_idx]; - halofield_out->sfr_rng[istart + count] = halofield_large->sfr_rng[halo_idx]; - halofield_out->xray_rng[istart + count] = halofield_large->xray_rng[halo_idx]; + halofield_out->sfr_10[istart + count] = halofield_large->sfr_10[halo_idx]; + halofield_out->sfr_100[istart + count] = halofield_large->sfr_100[halo_idx]; + halofield_out->stellar_mass[istart + count] = halofield_large->stellar_mass[halo_idx]; halofield_out->halo_coords[0 + 3 * (istart + count)] = halofield_large->halo_coords[0 + 3 * halo_idx]; halofield_out->halo_coords[1 + 3 * (istart + count)] = @@ -847,9 +848,9 @@ int sample_halo_grids(gsl_rng **rng_arr, double redshift, float *dens_field, halofield_out->halo_coords[3 * (istart + count) + 2] = crd_hi[2]; sample_correlated_sfh(rng_arr[threadnum], prop_dummy, prop_buf); - halofield_out->sfr_rng_10[istart + count] = prop_buf[0]; - halofield_out->sfr_rng_100[istart + count] = prop_buf[1]; - halofield_out->sfr_rng_snapshot[istart + count] = prop_buf[2]; + halofield_out->sfr_10[istart + count] = prop_buf[0]; + halofield_out->sfr_100[istart + count] = prop_buf[1]; + halofield_out->stellar_mass[istart + count] = prop_buf[2]; count++; M_tot_cell += hm_buf[i]; @@ -962,9 +963,9 @@ int sample_halo_progenitors(gsl_rng **rng_arr, double z_in, double z_out, HaloCa // Sample the CMF set by the descendant stoc_sample(&hs_constants_priv, rng_arr[threadnum], &n_prog, prog_buf); - propbuf_in[0] = halofield_in->star_rng[ii]; - propbuf_in[1] = halofield_in->sfr_rng[ii]; - propbuf_in[2] = halofield_in->xray_rng[ii]; + propbuf_in[0] = halofield_in->sfr_10[ii]; + propbuf_in[1] = halofield_in->sfr_100[ii]; + propbuf_in[2] = halofield_in->stellar_mass[ii]; pos_desc[0] = halofield_in->halo_coords[3 * ii + 0]; pos_desc[1] = halofield_in->halo_coords[3 * ii + 1]; pos_desc[2] = halofield_in->halo_coords[3 * ii + 2]; @@ -995,9 +996,9 @@ int sample_halo_progenitors(gsl_rng **rng_arr, double z_in, double z_out, HaloCa halofield_out->halo_coords[3 * (istart + count) + 0] = pos_prog[0]; halofield_out->halo_coords[3 * (istart + count) + 1] = pos_prog[1]; halofield_out->halo_coords[3 * (istart + count) + 2] = pos_prog[2]; - halofield_out->star_rng[istart + count] = propbuf_out[0]; - halofield_out->sfr_rng[istart + count] = propbuf_out[1]; - halofield_out->xray_rng[istart + count] = propbuf_out[2]; + halofield_out->sfr_10[istart + count] = propbuf_out[0]; + halofield_out->sfr_100[istart + count] = propbuf_out[1]; + halofield_out->stellar_mass[istart + count] = propbuf_out[2]; halofield_out->descendant_index[istart + count] = ii; count++; @@ -1045,9 +1046,12 @@ int sample_halo_progenitors(gsl_rng **rng_arr, double z_in, double z_out, HaloCa } // function that talks between the structures (Python objects) and the sampling functions -int stochastic_halofield(unsigned long long int seed, float redshift_desc2, float redshift_desc, - float redshift, float *dens_field, float *halo_overlap_box, +int stochastic_halofield(unsigned long long int seed, float *dens_field, float *halo_overlap_box, HaloCatalog *halos_desc, HaloCatalog *halos) { + double redshift = get_current_redshift(); + double redshift_desc = get_descendant_redshift(); + double redshift_desc2 = get_redshift_relative(-2); + if (redshift_desc > 0 && halos_desc->n_halos == 0) { LOG_DEBUG("No halos to sample from redshifts %.2f to %.2f, continuing...", redshift_desc, redshift); @@ -1067,11 +1071,13 @@ int stochastic_halofield(unsigned long long int seed, float redshift_desc2, floa // NOTE:Halos prev in the first box corresponds to the large DexM halos if (!from_catalog) { LOG_DEBUG("building first halo field at z=%.1f", redshift); + initialise_sfh_structs(redshift, -1.0, -1.0, false); sample_halo_grids(rng_stoc, redshift, dens_field, halo_overlap_box, halos_desc, halos, &hs_constants); } else { LOG_DEBUG("Calculating halo progenitors from z=%.1f to z=%.1f | %llu", redshift_desc, redshift, halos_desc->n_halos); + initialise_sfh_structs(redshift, redshift_desc, redshift_desc2, true); sample_halo_progenitors(rng_stoc, redshift_desc, redshift, halos_desc, halos, &hs_constants); } @@ -1095,6 +1101,7 @@ int stochastic_halofield(unsigned long long int seed, float redshift_desc2, floa free_dNdM_tables(); free_rng_threads(rng_stoc); + cleanup_sfh_structs(); LOG_DEBUG("Done."); return 0; } @@ -1128,7 +1135,7 @@ int single_test_sample(unsigned long long int seed, int n_condition, float *cond BOXLEN_PARA}; LOG_DEBUG("Setting z constants. %.3f %.3f", z_out, z_in); - stoc_set_consts_z(hs_constants, z_out, z_in); + stoc_set_consts_z(hs_constants, z_out, z_in, (z_in > 0.)); LOG_DEBUG("SINGLE SAMPLE: z = (%.2f,%.2f), Mmin = %.3e, cond(%d)=[%.2e,%.2e,%.2e...]", z_out, z_in, hs_constants->M_min, n_condition, conditions[0], conditions[1], diff --git a/src/py21cmfast/src/Stochasticity.h b/src/py21cmfast/src/Stochasticity.h index 090ce79df..d76ed2e28 100644 --- a/src/py21cmfast/src/Stochasticity.h +++ b/src/py21cmfast/src/Stochasticity.h @@ -41,9 +41,8 @@ struct HaloSamplingConstants { double expected_M; }; -int stochastic_halofield(unsigned long long int seed, float redshift_desc, float redshift, - float *dens_field, float *halo_overlap_box, HaloCatalog *halos_desc, - HaloCatalog *halos); +int stochastic_halofield(unsigned long long int seed, float *dens_field, float *halo_overlap_box, + HaloCatalog *halos_desc, HaloCatalog *halos); int single_test_sample(unsigned long long int seed, int n_condition, float *conditions, float *cond_crd, double z_out, double z_in, int *out_n_tot, int *out_n_cell, diff --git a/src/py21cmfast/src/_functionprototypes_wrapper.h b/src/py21cmfast/src/_functionprototypes_wrapper.h index cd091adfc..9944648bf 100644 --- a/src/py21cmfast/src/_functionprototypes_wrapper.h +++ b/src/py21cmfast/src/_functionprototypes_wrapper.h @@ -5,30 +5,24 @@ /* OutputStruct COMPUTE FUNCTIONS */ int ComputeInitialConditions(unsigned long long random_seed, InitialConditions *boxes); -int ComputePerturbedField(float redshift, InitialConditions *boxes, - PerturbedField *perturbed_field); +int ComputePerturbedField(InitialConditions *boxes, PerturbedField *perturbed_field); -int ComputeHaloCatalog(float redshift_desc, float redshift, InitialConditions *boxes, - unsigned long long int random_seed, HaloCatalog *halos_desc, - HaloCatalog *halos); +int ComputeHaloCatalog(InitialConditions *boxes, unsigned long long int random_seed, + HaloCatalog *halos_desc, HaloCatalog *halos); -int ComputePerturbedHaloCatalog(float redshift, InitialConditions *boxes, TsBox *prev_ts, - IonizedBox *prev_ion, HaloCatalog *halos, - PerturbedHaloCatalog *halos_perturbed); +int ComputePerturbedHaloCatalog(InitialConditions *boxes, TsBox *prev_ts, IonizedBox *prev_ion, + HaloCatalog *halos, PerturbedHaloCatalog *halos_perturbed); -int ComputeTsBox(float redshift, float prev_redshift, float perturbed_field_redshift, short cleanup, - PerturbedField *perturbed_field, XraySourceBox *source_box, - TsBox *previous_spin_temp, InitialConditions *ini_boxes, TsBox *this_spin_temp); +int ComputeTsBox(InitialConditions *boxes, TsBox *prev_ts, XraySourceBox *source_box, + TsBox *this_spin_temp); +int ComputeIonizedBox(PerturbedField *perturbed_field, PerturbedField *previous_perturbed_field, + IonizedBox *previous_ionize_box, TsBox *spin_temp, HaloBox *halos, + InitialConditions *ini_boxes, IonizedBox *box); -int ComputeIonizedBox(float redshift, float prev_redshift, PerturbedField *perturbed_field, - PerturbedField *previous_perturbed_field, IonizedBox *previous_ionize_box, - TsBox *spin_temp, HaloBox *halos, InitialConditions *ini_boxes, - IonizedBox *box); +int ComputeBrightnessTemp(TsBox *spin_temp, IonizedBox *ionized_box, PerturbedField *perturb_field, + BrightnessTemp *box); -int ComputeBrightnessTemp(float redshift, TsBox *spin_temp, IonizedBox *ionized_box, - PerturbedField *perturb_field, BrightnessTemp *box); - -int ComputeHaloBox(double redshift, InitialConditions *ini_boxes, HaloCatalog *halos, +int ComputeHaloBox(InitialConditions *ini_boxes, HaloCatalog *halos, HaloCatalog *halos_prev, TsBox *previous_spin_temp, IonizedBox *previous_ionize_box, HaloBox *grids); int UpdateXraySourceBox(HaloBox *halobox, double R_inner, double R_outer, int R_ct, @@ -73,6 +67,7 @@ void Broadcast_struct_global_noastro(SimulationOptions *simulation_options, void Broadcast_struct_global_all(SimulationOptions *simulation_options, MatterOptions *matter_options, CosmoParams *cosmo_params, AstroParams *astro_params, AstroOptions *astro_options); +void Broadcast_snapshot_info(int n_nodes, double *node_redshifts, int curr_node); void initialiseSigmaMInterpTable(float M_Min, float M_Max); void initialise_GL(double lnM_Min, double lnM_Max); /*---------------------------*/ diff --git a/src/py21cmfast/src/_inputparams_wrapper.h b/src/py21cmfast/src/_inputparams_wrapper.h index 8ca7e353f..ba1e4f292 100644 --- a/src/py21cmfast/src/_inputparams_wrapper.h +++ b/src/py21cmfast/src/_inputparams_wrapper.h @@ -163,6 +163,12 @@ typedef struct ConfigSettings { char *wisdoms_path; } ConfigSettings; +typedef struct NodeRedshifts { + int n_nodes; + double *node_redshifts; + int curr_node; +} NodeRedshifts; + /* Previously, we had a few structures spread throughout the code e.g simulation_options_ufunc which were all globally defined and separately broadcast at different times. Several of these were used across different files and some inside #defines (e.g indexing.h), so for now I've combined @@ -179,5 +185,6 @@ extern MatterOptions *matter_options_global; extern CosmoParams *cosmo_params_global; extern AstroParams *astro_params_global; extern AstroOptions *astro_options_global; +extern NodeRedshifts node_redshifts_global; extern ConfigSettings config_settings; diff --git a/src/py21cmfast/src/correlated_sfh.h b/src/py21cmfast/src/correlated_sfh.h index e46f22e92..d26c1469d 100644 --- a/src/py21cmfast/src/correlated_sfh.h +++ b/src/py21cmfast/src/correlated_sfh.h @@ -1,13 +1,14 @@ +#ifndef CORRELATED_SFH_H +#define CORRELATED_SFH_H #include #include - -#ifndef CORRELATED_SFH_H -#define CORRELATED_SFH_H +#include void initialise_sfh_structs(double z0, double z1, double z2, bool conditioned); int test_sfh_corr(double z0, double z1, double z2); void sample_correlated_sfh(gsl_rng *rng, double prev_values[3], double out_values[3]); -void get_current_vars(double out[3]) void cleanup_sfh_structs(); +void get_current_vars(double out[3]); +void cleanup_sfh_structs(); #endif diff --git a/src/py21cmfast/src/cosmology.h b/src/py21cmfast/src/cosmology.h index dca8f1b15..0aee89053 100644 --- a/src/py21cmfast/src/cosmology.h +++ b/src/py21cmfast/src/cosmology.h @@ -10,20 +10,19 @@ double MtoR(double M); double RtoM(double R); double TtoM(double z, double T, double mu); -void free_ps(); /* deallocates the gsl structures from init_ps */ -double power_in_k(double k); /* Returns the value of the linear power spectrum density (i.e. - <|delta_k|^2>/V) at a given k mode at z=0 */ +void free_ps(); /* deallocates the gsl structures from init_ps */ +/* Returns the value of the linear power spectrum density (i.e. + <|delta_k|^2>/V) at a given k mode at z=0 */ +double power_in_k(double k); -double TF_CLASS(double k, int flag_int, - int flag_dv); // transfer function of matter (flag_dv=0) and relative velocities - // (flag_dv=1) fluctuations from CLASS -double power_in_vcb(double k); /* Returns the value of the DM-b relative velocity power spectrum - density (i.e. <|delta_k|^2>/V) at a given k mode at z=0 */ +// transfer function of matter (flag_dv=0) and relative velocities +// (flag_dv=1) fluctuations from CLASS +double TF_CLASS(double k, int flag_int, int flag_dv); + +/* Returns the value of the DM-b relative velocity power spectrum + density (i.e. <|delta_k|^2>/V) at a given k mode at z=0 */ +double power_in_vcb(double k); -double MtoR(double M); -double RtoM(double R); -double TtoM(double z, double T, double mu); -double dicke(double z); double ddickedt(double z); double ddicke_dz(double z); double dtdz(double z); diff --git a/src/py21cmfast/src/integral_wrappers.c b/src/py21cmfast/src/integral_wrappers.c index a25f5c759..2a95034aa 100644 --- a/src/py21cmfast/src/integral_wrappers.c +++ b/src/py21cmfast/src/integral_wrappers.c @@ -42,7 +42,7 @@ void get_condition_integrals(double redshift, double z_prev, int n_conditions, d double *out_n_exp, double *out_m_exp) { struct HaloSamplingConstants hs_const_struct; // unneccessarily creates the inverse table (a few seconds) but much cleaner this way - stoc_set_consts_z(&hs_const_struct, redshift, z_prev); + stoc_set_consts_z(&hs_const_struct, redshift, z_prev, z_prev > 0.0); int i; for (i = 0; i < n_conditions; i++) { @@ -63,7 +63,7 @@ void get_halo_chmf_interval(double redshift, double z_prev, int n_conditions, do int n_masslim, double *lnM_lo, double *lnM_hi, double *out_n) { // unneccessarily creates tables if flags are set (a few seconds) struct HaloSamplingConstants hs_const_struct; - stoc_set_consts_z(&hs_const_struct, redshift, z_prev); + stoc_set_consts_z(&hs_const_struct, redshift, z_prev, z_prev > 0.0); // we're only using the HS constants here to do mass/sigma calculations // re-doing the sigma tables here lets us integrate below SAMPLER_MIN_MASS @@ -91,7 +91,7 @@ void get_halo_chmf_interval(double redshift, double z_prev, int n_conditions, do void get_halomass_at_probability(double redshift, double z_prev, int n_conditions, double *cond_values, double *probabilities, double *out_mass) { struct HaloSamplingConstants hs_const_struct; - stoc_set_consts_z(&hs_const_struct, redshift, z_prev); + stoc_set_consts_z(&hs_const_struct, redshift, z_prev, z_prev > 0.0); int i; bool out_of_bounds; diff --git a/src/py21cmfast/src/interp_tables.c b/src/py21cmfast/src/interp_tables.c index 93b0c18e3..b1c2d9ccd 100644 --- a/src/py21cmfast/src/interp_tables.c +++ b/src/py21cmfast/src/interp_tables.c @@ -900,7 +900,7 @@ double EvaluateNionTs(double redshift, ScalingConstants *sc) { double lnMmin = log(minimum_source_mass(redshift, true)); double lnMmax = log(M_MAX_INTEGRAL); - ScalingConstants sc_z = evolve_scaling_constants_to_redshift(redshift, sc, false); + ScalingConstants sc_z = evolve_scaling_constants_to_redshift(redshift, sc); // minihalos uses a different turnover mass if (matter_options_global->SOURCE_MODEL > 0) @@ -915,7 +915,7 @@ double EvaluateNionTs_MINI(double redshift, double log10_Mturn_LW_ave, ScalingCo } double lnMmin = log(minimum_source_mass(redshift, false)); double lnMmax = log(M_MAX_INTEGRAL); - ScalingConstants sc_z = evolve_scaling_constants_to_redshift(redshift, sc, false); + ScalingConstants sc_z = evolve_scaling_constants_to_redshift(redshift, sc); return Nion_General_MINI(redshift, lnMmin, lnMmax, pow(10., log10_Mturn_LW_ave), &sc_z); } @@ -936,7 +936,7 @@ double EvaluateSFRD(double redshift, ScalingConstants *sc) { // The SFRD calls the same function as N_ion but sets escape fractions to unity // NOTE: since this only occurs on integration, the struct copy shouldn't be a bottleneck ScalingConstants sc_sfrd = evolve_scaling_constants_sfr(sc); - sc_sfrd = evolve_scaling_constants_to_redshift(redshift, &sc_sfrd, false); + sc_sfrd = evolve_scaling_constants_to_redshift(redshift, &sc_sfrd); if (matter_options_global->SOURCE_MODEL > 0) return Nion_General(redshift, lnMmin, lnMmax, sc_sfrd.mturn_a_nofb, &sc_sfrd); @@ -952,7 +952,7 @@ double EvaluateSFRD_MINI(double redshift, double log10_Mturn_LW_ave, ScalingCons double lnMmax = log(M_MAX_INTEGRAL); ScalingConstants sc_sfrd = evolve_scaling_constants_sfr(sc); - sc_sfrd = evolve_scaling_constants_to_redshift(redshift, &sc_sfrd, false); + sc_sfrd = evolve_scaling_constants_to_redshift(redshift, &sc_sfrd); return Nion_General_MINI(redshift, lnMmin, lnMmax, pow(10., log10_Mturn_LW_ave), &sc_sfrd); } diff --git a/src/py21cmfast/src/map_mass.c b/src/py21cmfast/src/map_mass.c index ccfbce371..a8d693a78 100644 --- a/src/py21cmfast/src/map_mass.c +++ b/src/py21cmfast/src/map_mass.c @@ -238,6 +238,15 @@ void move_grid_galprops(double redshift, float *dens_pointer, int dens_dim[3], double prefactor_nion = prefactor_stars * consts->fesc_10 * consts->pop2_ion; double prefactor_nion_mini = prefactor_stars_mini * consts->fesc_7 * consts->pop3_ion; + // apply corrections + prefactor_stars = prefactor_stars * consts->sampled_mean_correction[2]; + prefactor_stars_mini = prefactor_stars_mini * consts->sampled_mean_correction[2]; + prefactor_xray = prefactor_xray * consts->sampled_mean_correction[0]; + prefactor_sfr = prefactor_sfr * consts->sampled_mean_correction[0]; + prefactor_sfr_mini = prefactor_sfr_mini * consts->sampled_mean_correction[0]; + prefactor_nion = prefactor_nion * consts->sampled_mean_correction[2]; + prefactor_nion_mini = prefactor_nion_mini * consts->sampled_mean_correction[2]; + // Setup IC velocity factors double growth_factor = dicke(redshift); double displacement_factor_2LPT = -(3.0 / 7.0) * growth_factor * growth_factor; // 2LPT eq. D8 @@ -337,7 +346,7 @@ void move_grid_galprops(double redshift, float *dens_pointer, int dens_dim[3], } } // Without stochasticity, these grids are the same to a constant - double prefactor_wsfr = 1 / consts->t_h / consts->t_star; + double prefactor_wsfr = 1 / consts->t_h / consts->t_star * consts->integral_mean_correction[1]; if (astro_options_global->INHOMO_RECO) { for (int i = 0; i < HII_TOT_NUM_PIXELS; i++) { boxes->whalo_sfr[i] = boxes->n_ion[i] * prefactor_wsfr; @@ -345,10 +354,11 @@ void move_grid_galprops(double redshift, float *dens_pointer, int dens_dim[3], } } -void move_halo_galprops(double redshift, HaloCatalog *halos, float *progenitor_hm, - float *progenitor_sm, float *vel_pointers[3], float *vel_pointers_2LPT[3], - int vel_dim[3], float *mturn_a_grid, float *mturn_m_grid, HaloBox *boxes, - int out_dim[3], ScalingConstants *consts) { +void move_halo_galprops(HaloCatalog *halos, float *progenitor_hm, float *progenitor_sm, + float *progenitor_sm_mini, float *vel_pointers[3], + float *vel_pointers_2LPT[3], int vel_dim[3], float *mturn_a_grid, + float *mturn_m_grid, HaloBox *boxes, int out_dim[3], + ScalingConstants *consts) { // grid dimension constants double boxlen = simulation_options_global->BOX_LEN; double boxlen_z = boxlen * simulation_options_global->NON_CUBIC_FACTOR; @@ -357,6 +367,9 @@ void move_halo_galprops(double redshift, HaloCatalog *halos, float *progenitor_h double cell_size_inv_o = out_dim[0] / simulation_options_global->BOX_LEN; double cell_vol_inv = cell_size_inv_o * cell_size_inv_o * cell_size_inv_o; + double redshift = get_current_redshift(); + double snapshot_time = time_between_z(get_previous_redshift(), redshift); + // Setup IC velocity factors double growth_factor = dicke(redshift); double displacement_factor_2LPT = -(3.0 / 7.0) * growth_factor * growth_factor; // 2LPT eq. D8 @@ -380,6 +393,8 @@ void move_halo_galprops(double redshift, HaloCatalog *halos, float *progenitor_h double M_turn_m = consts->mturn_m_nofb; double halo_rng[3]; double hmass; + double prog_hm; + double prog_sm[2]; #pragma omp for for (i = 0; i < halos->n_halos; i++) { hmass = halos->halo_masses[i]; @@ -418,7 +433,11 @@ void move_halo_galprops(double redshift, HaloCatalog *halos, float *progenitor_h halo_rng[2] = halos->stellar_mass[i]; // CIC interpolation - set_halo_properties(hmass, M_turn_a, M_turn_m, consts, halo_rng, &properties); + prog_hm = progenitor_hm[i]; + prog_sm[0] = progenitor_sm[i]; + prog_sm[1] = progenitor_sm_mini[i]; + set_halo_properties(snapshot_time, hmass, M_turn_a, M_turn_m, prog_hm, prog_sm, consts, + halo_rng, &properties); do_cic_interpolation(boxes->halo_sfr, pos, out_dim, properties.sfr_10); do_cic_interpolation(boxes->n_ion, pos, out_dim, properties.n_ion); if (astro_options_global->USE_MINI_HALOS) { @@ -440,6 +459,8 @@ void move_halo_galprops(double redshift, HaloCatalog *halos, float *progenitor_h } // feed back the halo properties we need to store onto the HaloCatalog + // NOTE: these probably won't be cached and are purged at the end of the snapshot + // TODO: make it very clear what's in the struct at what stage halos->sfr_10[i] = properties.sfr_10; // TODO: we don't need to store this really halos->sfr_100[i] = properties.stellar_mass_mini; // TODO:naming is misleading here halos->stellar_mass[i] = properties.stellar_mass; diff --git a/src/py21cmfast/src/map_mass.h b/src/py21cmfast/src/map_mass.h index f4f3e98dc..02ce10ed5 100644 --- a/src/py21cmfast/src/map_mass.h +++ b/src/py21cmfast/src/map_mass.h @@ -13,9 +13,10 @@ void move_grid_galprops(double redshift, float *dens_pointer, int dens_dim[3], HaloBox *boxes, int out_dim[3], float *mturn_a_grid, float *mturn_m_grid, ScalingConstants *consts, IntegralCondition *integral_cond); -void move_halo_galprops(double redshift, HaloCatalog *halos, float *progenitor_hm, - float *progenitor_sm, float *vel_pointers[3], float *vel_pointers_2LPT[3], - int vel_dim[3], float *mturn_a_grid, float *mturn_m_grid, HaloBox *boxes, - int out_dim[3], ScalingConstants *consts); +void move_halo_galprops(HaloCatalog *halos, float *progenitor_hm, float *progenitor_sm, + float *progenitor_sm_mini, float *vel_pointers[3], + float *vel_pointers_2LPT[3], int vel_dim[3], float *mturn_a_grid, + float *mturn_m_grid, HaloBox *boxes, int out_dim[3], + ScalingConstants *consts); double cic_read_float_wrapper(float *box, double pos[3], int box_dim[3]); diff --git a/src/py21cmfast/src/photoncons.c b/src/py21cmfast/src/photoncons.c index a7eb46e6a..6da718431 100644 --- a/src/py21cmfast/src/photoncons.c +++ b/src/py21cmfast/src/photoncons.c @@ -164,9 +164,9 @@ int InitialisePhotonCons() { z0 = 1. / (a + delta_a) - 1.; z1 = 1. / (a - delta_a) - 1.; - sc_i = evolve_scaling_constants_to_redshift(zi, &sc_i, false); - sc_0 = evolve_scaling_constants_to_redshift(z0, &sc_i, false); - sc_1 = evolve_scaling_constants_to_redshift(z1, &sc_i, false); + sc_i = evolve_scaling_constants_to_redshift(zi, &sc_i); + sc_0 = evolve_scaling_constants_to_redshift(z0, &sc_i); + sc_1 = evolve_scaling_constants_to_redshift(z1, &sc_i); // Ionizing emissivity (num of photons per baryon) // We Force QAG due to the changing limits and messy implementation which I will fix // later (hopefully move the whole thing to python) diff --git a/src/py21cmfast/src/scaling_relations.c b/src/py21cmfast/src/scaling_relations.c index de1607528..33a095c6a 100644 --- a/src/py21cmfast/src/scaling_relations.c +++ b/src/py21cmfast/src/scaling_relations.c @@ -13,6 +13,7 @@ #include "Constants.h" #include "InputParameters.h" #include "cexcept.h" +#include "correlated_sfh.h" #include "cosmology.h" #include "exceptions.h" #include "hmf.h" @@ -36,15 +37,9 @@ void print_sc_consts(ScalingConstants *c) { return; } -void set_scaling_constants(double redshift, double redshift_prev, ScalingConstants *consts, - bool use_photoncons) { +void set_scaling_constants(double redshift, ScalingConstants *consts, bool use_photoncons) { consts->redshift = redshift; - if (redshift_prev > 0) - consts->snapshot_time = time_between_z(redshift, redshift_prev); - else - consts->snapshot_time = -1.0; // indicates single snapshot mode - // Set on for the fixed grid case since we are missing halos above the cell mass consts->fix_mean = matter_options_global->HMF == 2 || matter_options_global->HMF == 3; // whether to fix *integrated* (not sampled) galaxy properties to the expected mean @@ -53,6 +48,7 @@ void set_scaling_constants(double redshift, double redshift_prev, ScalingConstan // we need the sigmas for the current snapshot if (matter_options_global->SOURCE_MODEL > 1) { initialise_sfh_structs(redshift, -1, -1, false); + // TODO: seriously review this get_current_vars(consts->integral_mean_correction); if (astro_options_global->HALO_SCALING_RELATIONS_MEDIAN) { for (int i = 0; i < 3; i++) { @@ -122,6 +118,10 @@ void set_scaling_constants(double redshift, double redshift_prev, ScalingConstan Mass_limit_bisection(M_MIN_INTEGRAL, M_MAX_INTEGRAL, consts->alpha_esc, consts->fesc_7 * pow(1e3, consts->alpha_esc)); } + + // TODO: can remove if we find a better way to get the average integrated SFR + consts->t_h = t_Hubble(redshift); + consts->t_star = astro_params_global->t_STAR; } // It's often useful to create a copy of scaling constants without F_ESC @@ -156,37 +156,6 @@ ScalingConstants evolve_scaling_constants_to_redshift(double redshift, ScalingCo return sc_z; } -ScalingConstants mimic_scatter_in_consts(ScalingConstants *sc) { - // This function mimics the effect of log-normal scatter in the scaling relations by increasing - // the normalisation of the relations appropriately. - // These should be used in individual integrals / table initialisations, scoped tightly, - // and applied after evolving to the correct redshift / relation. - ScalingConstants ev_consts = *sc; - ev_consts.fstar_10 *= exp(0.5 * pow(ev_consts.sigma_star, 2)); - ev_consts.fstar_7 *= exp(0.5 * pow(ev_consts.sigma_star, 2)); - ev_consts.l_x *= exp(0.5 * pow(ev_consts.sigma_xray, 2)); - ev_consts.l_x_mini *= exp(0.5 * pow(ev_consts.sigma_xray, 2)); - - // This is a lower-limit on the effect of scatter in SSFR - // since the scatter depends on stellar mass. To fully apply the limit we would need - // a new HMF integrand. Explicit Monte-Carlo Integration over the property PDFS might also - // work. - // TODO: Something better than this - ev_consts.t_star /= exp(0.5 * pow(ev_consts.sigma_sfr_lim, 2)); - - // By altering the normalisations we need to recalculate the mass limits - ev_consts.Mlim_Fstar = Mass_limit_bisection(M_MIN_INTEGRAL, M_MAX_INTEGRAL, - ev_consts.alpha_star, ev_consts.fstar_10); - - if (astro_options_global->USE_MINI_HALOS) { - ev_consts.Mlim_Fstar_mini = - Mass_limit_bisection(M_MIN_INTEGRAL, M_MAX_INTEGRAL, ev_consts.alpha_star_mini, - ev_consts.fstar_7 * pow(1e3, ev_consts.alpha_star_mini)); - } - - return ev_consts; -} - /* General Scaling realtions used in mass function integrals and sampling. @@ -312,9 +281,9 @@ double get_lx_on_sfr(double sfr, double metallicity, double lx_constant) { return lx_constant; } -void get_halo_sfh(double halo_mass, double mturn_acg, double mturn_mcg, double prog_hm, - double prog_sm[2], double rng[3], ScalingConstants *consts, double sfr_out[3], - double sfr_out_mini[3]) { +void get_halo_sfh(double snapshot_time, double halo_mass, double mturn_acg, double mturn_mcg, + double prog_hm, double prog_sm[2], double rng[3], ScalingConstants *consts, + double sfr_out[3], double sfr_out_mini[3]) { // low-mass ACG power-law parameters double f_10 = consts->fstar_10; double f_a = consts->alpha_star; @@ -371,10 +340,10 @@ void get_halo_sfh(double halo_mass, double mturn_acg, double mturn_mcg, double p } // divide by snapshot time - sfr_out[0] = sfr_out[0] / consts->snapshot_time; - sfr_out_mini[0] = sfr_out_mini[0] / consts->snapshot_time; - sfr_out[1] = sfr_out[1] / consts->snapshot_time; - sfr_out_mini[1] = sfr_out_mini[1] / consts->snapshot_time; + sfr_out[0] = sfr_out[0] / snapshot_time; + sfr_out_mini[0] = sfr_out_mini[0] / snapshot_time; + sfr_out[1] = sfr_out[1] / snapshot_time; + sfr_out_mini[1] = sfr_out_mini[1] / snapshot_time; // Finally, sum the progenitor stellar masses into the third field sfr_out[2] = sfr_out[2] + prog_sm[0]; sfr_out_mini[2] = sfr_out_mini[2] + prog_sm[1]; diff --git a/src/py21cmfast/src/scaling_relations.h b/src/py21cmfast/src/scaling_relations.h index d96a37702..8ae1815b9 100644 --- a/src/py21cmfast/src/scaling_relations.h +++ b/src/py21cmfast/src/scaling_relations.h @@ -12,7 +12,6 @@ // unit changes typedef struct ScalingConstants { double redshift; - double snapshot_time; bool fix_mean; bool scaling_median; @@ -47,14 +46,18 @@ typedef struct ScalingConstants { double Mlim_Fesc; double Mlim_Fstar_mini; double Mlim_Fesc_mini; + + // TODO: remove after making the integrals consistent + double t_h; + double t_star; } ScalingConstants; void set_scaling_constants(double redshift, ScalingConstants *consts, bool use_photoncons); double get_lx_on_sfr(double sfr, double metallicity, double lx_constant); -void get_halo_sfh(double halo_mass, double mturn_acg, double mturn_mcg, double prog_hm, - double prog_sm[2], double rng[3], ScalingConstants *consts, double sfr_out[3], - double sfr_out_mini[3]); +void get_halo_sfh(double snapshot_time, double halo_mass, double mturn_acg, double mturn_mcg, + double prog_hm, double prog_sm[2], double rng[3], ScalingConstants *consts, + double sfr_out[3], double sfr_out_mini[3]); void get_halo_metallicity(double sfr, double stellar, double redshift, double *z_out); void get_halo_xray(double sfr, double sfr_mini, double metallicity, double xray_rng, ScalingConstants *consts, double *xray_out); @@ -68,7 +71,6 @@ ScalingConstants evolve_scaling_constants_sfr(ScalingConstants *sc); ScalingConstants evolve_scaling_constants_to_redshift(double redshift, ScalingConstants *sc); ScalingConstants mimic_scatter_in_consts(ScalingConstants *sc); void print_sc_consts(ScalingConstants *c); -void initialise_sfh_correlation(double z, double z_prev); // Forward define GSL types to avoid including GSL headers here void eval_sfh_moments(double tau, gsl_matrix *out_chol_cov, gsl_matrix *out_mean_correction); diff --git a/src/py21cmfast/wrapper/cfuncs.py b/src/py21cmfast/wrapper/cfuncs.py index 08855f477..62b7f1ea3 100644 --- a/src/py21cmfast/wrapper/cfuncs.py +++ b/src/py21cmfast/wrapper/cfuncs.py @@ -35,7 +35,7 @@ def broadcast_input_struct(inputs: InputParameters): ) -def broadcast_params(func: Callable) -> Callable: +def broadcast_params(func: Callable, redshift: float | None = None) -> Callable: """Broadcast the parameters to the C library before calling the function. This should be added as a decorator to any function which accesses the @@ -44,6 +44,14 @@ def broadcast_params(func: Callable) -> Callable: def wrapper(*args, inputs: InputParameters, **kwargs): broadcast_input_struct(inputs) + if redshift is not None: + lib.Broadcast_snapshot_info( + inputs.node_redshifts.cstruct.size, + np.array( + inputs.node_redshifts.cstruct.node_redshifts, dtype="f8" + ).ctypes.data, + inputs.node_redshifts.cstruct.node_redshifts.index(redshift), + ) return func(*args, inputs=inputs, **kwargs) return wrapper From 493d52e937107a009d19a77d94725d4cfdbf66ab Mon Sep 17 00:00:00 2001 From: James Davies Date: Tue, 10 Mar 2026 09:57:29 +0800 Subject: [PATCH 13/18] compiles --- src/py21cmfast/src/HaloBox.c | 1 + src/py21cmfast/src/scaling_relations.c | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/py21cmfast/src/HaloBox.c b/src/py21cmfast/src/HaloBox.c index 3e9852c59..0f370747d 100644 --- a/src/py21cmfast/src/HaloBox.c +++ b/src/py21cmfast/src/HaloBox.c @@ -292,6 +292,7 @@ int set_fixed_grids(double M_min, double M_max, InitialConditions *ini_boxes, fl float *mturn_m_grid, ScalingConstants *consts, HaloBox *grids) { double M_cell; + double growthf = dicke(consts->redshift); // find grid limits for tables double min_density = 0.; double max_density = 0.; diff --git a/src/py21cmfast/src/scaling_relations.c b/src/py21cmfast/src/scaling_relations.c index 33a095c6a..aae6e6ae1 100644 --- a/src/py21cmfast/src/scaling_relations.c +++ b/src/py21cmfast/src/scaling_relations.c @@ -373,11 +373,9 @@ void get_halo_metallicity(double sfr, double stellar, double redshift, double *z void get_halo_xray(double sfr, double sfr_mini, double metallicity, double xray_rng, ScalingConstants *consts, double *xray_out) { - double sigma_xray = consts->sigma_xray; - // adjustment to the mean for lognormal scatter - double stoc_adjustment_term = consts->scaling_median ? 0 : sigma_xray * sigma_xray / 2.; - double rng_factor = exp(xray_rng * consts->sigma_xray - stoc_adjustment_term); + double stoc_adjustment_term = consts->sampled_mean_correction[0]; + double rng_factor = exp(xray_rng - stoc_adjustment_term); double lx_over_sfr = get_lx_on_sfr(sfr, metallicity, consts->l_x); double xray = lx_over_sfr * (sfr * physconst.s_per_yr) * rng_factor; From 5c96ecb76510e90d08f4b7b982bb878cf7080f90 Mon Sep 17 00:00:00 2001 From: James Davies Date: Wed, 11 Mar 2026 11:34:26 +0800 Subject: [PATCH 14/18] fix debug messages --- src/py21cmfast/src/BrightnessTemperatureBox.c | 3 +-- src/py21cmfast/src/HaloBox.c | 25 +++++++++++-------- src/py21cmfast/src/IonisationBox.c | 5 ++-- src/py21cmfast/src/SpinTemperatureBox.c | 5 ++-- src/py21cmfast/src/Stochasticity.c | 12 ++++----- src/py21cmfast/src/map_mass.c | 6 ++--- src/py21cmfast/src/scaling_relations.c | 15 ++++++----- 7 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/py21cmfast/src/BrightnessTemperatureBox.c b/src/py21cmfast/src/BrightnessTemperatureBox.c index 36ad1b318..07fefce7f 100644 --- a/src/py21cmfast/src/BrightnessTemperatureBox.c +++ b/src/py21cmfast/src/BrightnessTemperatureBox.c @@ -23,6 +23,7 @@ int ComputeBrightnessTemp(TsBox *spin_temp, IonizedBox *ionized_box, PerturbedFi BrightnessTemp *box) { int status; Try { // Try block around whole function. + double redshift = get_current_redshift(); LOG_DEBUG("Starting Brightness Temperature calculation for redshift %f", redshift); // Makes the parameter structs visible to a variety of functions/macros // Do each time to avoid Python garbage collection issues @@ -30,8 +31,6 @@ int ComputeBrightnessTemp(TsBox *spin_temp, IonizedBox *ionized_box, PerturbedFi int i, j, k; double ave; - double redshift = get_current_redshift(); - int box_dim[3] = { simulation_options_global->HII_DIM, simulation_options_global->HII_DIM, simulation_options_global->NON_CUBIC_FACTOR * simulation_options_global->HII_DIM}; diff --git a/src/py21cmfast/src/HaloBox.c b/src/py21cmfast/src/HaloBox.c index 0f370747d..01ccbf600 100644 --- a/src/py21cmfast/src/HaloBox.c +++ b/src/py21cmfast/src/HaloBox.c @@ -432,16 +432,19 @@ void halobox_debug_print_avg(HaloBox *halobox, ScalingConstants *consts, double get_uhmf_averages(M_min, M_max, mturn_a_avg, mturn_m_avg, consts, &averages_global); LOG_DEBUG( - "Exp. averages: (HM %11.3e, SM %11.3e SM_MINI %11.3e SFR %11.3e, SFR_MINI %11.3e, XRAY " - "%11.3e, NION %11.3e)", + "====== Expected averages ======\n" + "| HM %11.3e | SM %11.3e (MCG %11.3e) | SFR10 %11.3e (MCG %11.3e) |\n" + "| SFR100 %11.3e (MCG %11.3e) | XRAY %11.3e | ION %11.3e ", averages_global.halo_mass, averages_global.stellar_mass, averages_global.stellar_mass_mini, - averages_global.halo_sfr, averages_global.sfr_mini, averages_global.halo_xray, - averages_global.n_ion); + averages_global.sfr_10, averages_global.sfr_10_mcg, averages_global.sfr_100, + averages_global.sfr_100_mcg, averages_global.halo_xray, averages_global.n_ion); LOG_DEBUG( - "Box. averages: (HM %11.3e, SM %11.3e SM_MINI %11.3e SFR %11.3e, SFR_MINI %11.3e, XRAY " - "%11.3e, NION %11.3e)", + "====== BOX averages ======\n" + "| HM %11.3e | SM %11.3e (MCG %11.3e) | SFR10 %11.3e (MCG %11.3e) |\n" + "| SFR100 %11.3e (MCG %11.3e) | XRAY %11.3e | ION %11.3e ", averages_box.halo_mass, averages_box.stellar_mass, averages_box.stellar_mass_mini, - averages_box.halo_sfr, averages_box.sfr_mini, averages_box.halo_xray, averages_box.n_ion); + averages_box.sfr_10, averages_box.sfr_10_mcg, averages_box.sfr_100, + averages_box.sfr_100_mcg, averages_box.halo_xray, averages_box.n_ion); } // We need the mean log10 turnover masses for comparison with expected global Nion and SFRD. @@ -784,10 +787,10 @@ int test_halo_props(double redshift, double redshift_prev, float *vcb_grid, floa if (i_halo < 10) { LOG_ULTRA_DEBUG("HM %.2e SM %.2e SF %.2e NI %.2e LX %.2e", out_props.halo_mass, - out_props.stellar_mass, out_props.halo_sfr, out_props.n_ion, + out_props.stellar_mass, out_props.sfr_10, out_props.n_ion, out_props.halo_xray); LOG_ULTRA_DEBUG("MINI: SM %.2e SF %.2e WSF %.2e", out_props.stellar_mass_mini, - out_props.sfr_mini, out_props.fescweighted_sfr); + out_props.sfr_10_mcg, out_props.fescweighted_sfr); LOG_ULTRA_DEBUG("Mturns ACG %.2e MCG %.2e Reion %.2e", M_turn_a, M_turn_m, M_turn_r); LOG_ULTRA_DEBUG("RNG: STAR %.2e SFR %.2e XRAY %.2e", in_props[0], in_props[1], @@ -892,10 +895,10 @@ int convert_halo_props(InitialConditions *ics, TsBox *prev_ts, IonizedBox *prev_ if (i_halo < 10) { LOG_ULTRA_DEBUG("HM %.2e SM %.2e SF %.2e NI %.2e LX %.2e", out_props.halo_mass, - out_props.stellar_mass, out_props.halo_sfr, out_props.n_ion, + out_props.stellar_mass, out_props.sfr_10, out_props.n_ion, out_props.halo_xray); LOG_ULTRA_DEBUG("MINI: SM %.2e SF %.2e WSF %.2e", out_props.stellar_mass_mini, - out_props.sfr_mini, out_props.fescweighted_sfr); + out_props.sfr_10_mcg, out_props.fescweighted_sfr); LOG_ULTRA_DEBUG("Mturns ACG %.2e MCG %.2e", M_turn_a, M_turn_m); LOG_ULTRA_DEBUG("RNG: STAR %.2e SFR %.2e XRAY %.2e", in_props[0], in_props[1], in_props[2]); diff --git a/src/py21cmfast/src/IonisationBox.c b/src/py21cmfast/src/IonisationBox.c index 9fb8c8f84..1f0e6e3b5 100644 --- a/src/py21cmfast/src/IonisationBox.c +++ b/src/py21cmfast/src/IonisationBox.c @@ -1297,6 +1297,8 @@ int ComputeIonizedBox(PerturbedField *perturbed_field, PerturbedField *previous_ int status; Try { // This Try brackets the whole function, so we don't indent. + double redshift = get_current_redshift(); + double prev_redshift = get_previous_redshift(); LOG_DEBUG("input values:"); LOG_DEBUG("redshift=%f, prev_redshift=%f", redshift, prev_redshift); #if LOG_LEVEL >= DEBUG_LEVEL @@ -1307,9 +1309,6 @@ int ComputeIonizedBox(PerturbedField *perturbed_field, PerturbedField *previous_ writeAstroOptions(astro_options_global); #endif - double redshift = get_current_redshift(); - double prev_redshift = get_previous_redshift(); - // Makes the parameter structs visible to a variety of functions/macros // Do each time to avoid Python garbage collection issues omp_set_num_threads(simulation_options_global->N_THREADS); diff --git a/src/py21cmfast/src/SpinTemperatureBox.c b/src/py21cmfast/src/SpinTemperatureBox.c index 7d8092323..79c5bda63 100644 --- a/src/py21cmfast/src/SpinTemperatureBox.c +++ b/src/py21cmfast/src/SpinTemperatureBox.c @@ -88,8 +88,6 @@ int ComputeTsBox(short cleanup, PerturbedField *perturbed_field, XraySourceBox * TsBox *previous_spin_temp, InitialConditions *ini_boxes, TsBox *this_spin_temp) { int status; Try { // This Try{} wraps the whole function. - LOG_DEBUG("Spintemp input values:"); - LOG_DEBUG("redshift=%f, prev_redshift=%f", redshift, prev_redshift); #if LOG_LEVEL >= SUPER_DEBUG_LEVEL writeSimulationOptions(simulation_options_global); @@ -1351,6 +1349,9 @@ void ts_main(short cleanup, PerturbedField *perturbed_field, XraySourceBox *sour double redshift = get_current_redshift(); double prev_redshift = get_previous_redshift(); + LOG_DEBUG("Spintemp input values:"); + LOG_DEBUG("redshift=%f, prev_redshift=%f", redshift, prev_redshift); + // NOTE: For the code to work, previous_spin_temp MUST be allocated & // calculated if redshift < Z_HEAT_MAX growth_factor_z = dicke(redshift); diff --git a/src/py21cmfast/src/Stochasticity.c b/src/py21cmfast/src/Stochasticity.c index 0744534bc..c85ffc8ab 100644 --- a/src/py21cmfast/src/Stochasticity.c +++ b/src/py21cmfast/src/Stochasticity.c @@ -1087,12 +1087,12 @@ int stochastic_halofield(unsigned long long int seed, float *dens_field, float * if (halos->n_halos >= 3) { LOG_DEBUG("First few Masses: %11.3e %11.3e %11.3e", halos->halo_masses[0], halos->halo_masses[1], halos->halo_masses[2]); - LOG_DEBUG("First few Stellar RNG: %11.3e %11.3e %11.3e", halos->star_rng[0], - halos->star_rng[1], halos->star_rng[2]); - LOG_DEBUG("First few SFR RNG: %11.3e %11.3e %11.3e", halos->sfr_rng[0], - halos->sfr_rng[1], halos->sfr_rng[2]); - LOG_DEBUG("First few XRAY RNG: %11.3e %11.3e %11.3e", halos->xray_rng[0], - halos->xray_rng[1], halos->xray_rng[2]); + LOG_DEBUG("First few SFR10 RNG: %11.3e %11.3e %11.3e", halos->sfr_10[0], halos->sfr_10[1], + halos->sfr_10[2]); + LOG_DEBUG("First few SFR100 RNG: %11.3e %11.3e %11.3e", halos->sfr_100[0], + halos->sfr_100[1], halos->sfr_100[2]); + LOG_DEBUG("First few Snapshot RNG: %11.3e %11.3e %11.3e", halos->stellar_mass[0], + halos->stellar_mass[1], halos->stellar_mass[2]); } if (matter_options_global->USE_INTERPOLATION_TABLES > 0) { diff --git a/src/py21cmfast/src/map_mass.c b/src/py21cmfast/src/map_mass.c index a8d693a78..51d16fed9 100644 --- a/src/py21cmfast/src/map_mass.c +++ b/src/py21cmfast/src/map_mass.c @@ -470,9 +470,9 @@ void move_halo_galprops(HaloCatalog *halos, float *progenitor_hm, float *progeni LOG_ULTRA_DEBUG( "First 10 Halos: HM: %.2e SM: %.2e (%.2e) SF: %.2e (%.2e) X: %.2e NI: %.2e WS: " "%.2e Z : %.2e ct : %llu", - hmass, properties.stellar_mass, properties.stellar_mass_mini, - properties.halo_sfr, properties.sfr_mini, properties.halo_xray, - properties.n_ion, properties.fescweighted_sfr, properties.metallicity, i); + hmass, properties.stellar_mass, properties.stellar_mass_mini, properties.sfr_10, + properties.sfr_10_mcg, properties.halo_xray, properties.n_ion, + properties.fescweighted_sfr, properties.metallicity, i); LOG_ULTRA_DEBUG("Mturn_a %.2e Mturn_m %.2e RNG %.3f %.3f %.3f", M_turn_a, M_turn_m, halo_rng[0], halo_rng[1], halo_rng[2]); } diff --git a/src/py21cmfast/src/scaling_relations.c b/src/py21cmfast/src/scaling_relations.c index aae6e6ae1..489070412 100644 --- a/src/py21cmfast/src/scaling_relations.c +++ b/src/py21cmfast/src/scaling_relations.c @@ -24,12 +24,11 @@ void print_sc_consts(ScalingConstants *c) { LOG_DEBUG("Printing scaling relation constants z = %.3f....", c->redshift); - LOG_DEBUG("SHMR: f10 %.2e a %.2e f7 %.2e a_mini %.2e sigma %.2e", c->fstar_10, c->alpha_star, - c->fstar_7, c->alpha_star_mini, c->sigma_star); + LOG_DEBUG("SHMR: f10 %.2e a %.2e f7 %.2e a_mini %.2e", c->fstar_10, c->alpha_star, c->fstar_7, + c->alpha_star_mini); LOG_DEBUG("Upper: a_upper %.2e pivot %.2e", c->alpha_upper, c->pivot_upper); LOG_DEBUG("FESC: f10 %.2e a %.2e f7 %.2e", c->fesc_10, c->alpha_esc, c->fesc_7); - LOG_DEBUG("SSFR: t* %.2e th %.8e sigma %.2e idx %.2e", c->t_star, c->t_h, c->sigma_sfr_lim, - c->sigma_sfr_idx); + LOG_DEBUG("SSFR: t* %.2e th %.8e", c->t_star, c->t_h); LOG_DEBUG("Turnovers (nofb) ACG %.2e MCG %.2e Upper %.2e", c->mturn_a_nofb, c->mturn_m_nofb, c->acg_thresh); LOG_DEBUG("Limits (ACG,MCG) F* (%.2e %.2e) Fesc (%.2e %.2e)", c->Mlim_Fstar, c->Mlim_Fstar_mini, @@ -120,7 +119,7 @@ void set_scaling_constants(double redshift, ScalingConstants *consts, bool use_p } // TODO: can remove if we find a better way to get the average integrated SFR - consts->t_h = t_Hubble(redshift); + consts->t_h = t_hubble(redshift); consts->t_star = astro_params_global->t_STAR; } @@ -321,7 +320,7 @@ void get_halo_sfh(double snapshot_time, double halo_mass, double mturn_acg, doub for (int i = 0; i < 3; i++) { // we move the mturn here to go from 4 exp calls to 3 - sfr_out[i] = max(1.0, fstar_mean * exp(-mturn_acg / halo_mass + rng[i])) * mass_growth * + sfr_out[i] = fmax(1.0, fstar_mean * exp(-mturn_acg / halo_mass + rng[i])) * mass_growth * baryon_ratio * consts->sampled_mean_correction[i]; } @@ -329,8 +328,8 @@ void get_halo_sfh(double snapshot_time, double halo_mass, double mturn_acg, doub fstar_mean_mini = scaling_single_PL(halo_mass, f_a_mini, 1e7) * f_7; for (int i = 0; i < 3; i++) { sfr_out_mini[i] = - max(1.0, fstar_mean_mini * exp(-mturn_mcg / halo_mass - - halo_mass / consts->acg_thresh + rng[i])) * + fmax(1.0, fstar_mean_mini * exp(-mturn_mcg / halo_mass - + halo_mass / consts->acg_thresh + rng[i])) * mass_growth * baryon_ratio * consts->sampled_mean_correction[i]; } } else { From 8c3b8af47b19969a14e86619b02018cf1cde4818 Mon Sep 17 00:00:00 2001 From: James Davies Date: Wed, 11 Mar 2026 11:53:44 +0800 Subject: [PATCH 15/18] hs runs --- src/py21cmfast/src/Stochasticity.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/py21cmfast/src/Stochasticity.c b/src/py21cmfast/src/Stochasticity.c index c85ffc8ab..b09fba94a 100644 --- a/src/py21cmfast/src/Stochasticity.c +++ b/src/py21cmfast/src/Stochasticity.c @@ -87,6 +87,7 @@ void stoc_set_consts_z(struct HaloSamplingConstants *const_struct, double redshi LOG_DEBUG("Setting z constants z=%.2f z_desc=%.2f", redshift, redshift_desc); const_struct->growth_out = dicke(redshift); + const_struct->z_out = redshift; const_struct->z_in = redshift_desc; @@ -111,6 +112,7 @@ void stoc_set_consts_z(struct HaloSamplingConstants *const_struct, double redshi if (from_catalog) { const_struct->from_catalog = true; + const_struct->growth_in = dicke(redshift_desc); initialise_dNdM_tables(log(simulation_options_global->SAMPLER_MIN_MASS), const_struct->lnM_max_tb, const_struct->lnM_min, const_struct->lnM_max_tb, const_struct->growth_out, From a8b21923f85b0c3fdc8e334c41780d3aaeccd736 Mon Sep 17 00:00:00 2001 From: James Davies Date: Sun, 10 May 2026 12:23:38 +1000 Subject: [PATCH 16/18] remove redshift from output functions --- src/py21cmfast/wrapper/outputs.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/py21cmfast/wrapper/outputs.py b/src/py21cmfast/wrapper/outputs.py index c7aacc807..1ff3d4d00 100644 --- a/src/py21cmfast/wrapper/outputs.py +++ b/src/py21cmfast/wrapper/outputs.py @@ -749,7 +749,6 @@ def compute(self, *, allow_already_computed: bool = False, ics: InitialCondition """Compute the function.""" return self._compute( allow_already_computed, - self.redshift, ics, ) @@ -860,8 +859,6 @@ def compute( """Compute the function.""" return self._compute( allow_already_computed, - self.desc_redshift, - self.redshift, ics, ics.random_seed, descendant_halos, @@ -982,7 +979,6 @@ def compute( """Compute the function.""" return self._compute( allow_already_computed, - self.redshift, ics, previous_spin_temp, previous_ionize_box, @@ -1106,7 +1102,6 @@ def compute( """Compute the function.""" return self._compute( allow_already_computed, - self.redshift, initial_conditions, halo_catalog, previous_halo_catalog, @@ -1352,9 +1347,6 @@ def compute( """Compute the function.""" return self._compute( allow_already_computed, - self.redshift, - prev_spin_temp.redshift, - perturbed_field.redshift, cleanup, perturbed_field, xray_source_box, @@ -1519,8 +1511,6 @@ def compute( """Compute the function.""" return self._compute( allow_already_computed, - self.redshift, - prev_perturbed_field.redshift, perturbed_field, prev_perturbed_field, prev_ionize_box, @@ -1611,7 +1601,6 @@ def compute( """Compute the function.""" return self._compute( allow_already_computed, - self.redshift, spin_temp, ionized_box, perturbed_field, From d9a187abcc22643240aa11f418f94c701df87626 Mon Sep 17 00:00:00 2001 From: James Davies Date: Sun, 10 May 2026 22:42:39 +1000 Subject: [PATCH 17/18] hs runs again after merge --- src/py21cmfast/drivers/_param_config.py | 6 +++--- src/py21cmfast/src/HaloBox.c | 4 ++-- src/py21cmfast/src/HaloCatalog.c | 2 +- src/py21cmfast/src/PerturbedField.c | 2 +- src/py21cmfast/src/Stochasticity.c | 18 ++++++++++-------- .../src/_functionprototypes_wrapper.h | 3 ++- src/py21cmfast/src/correlated_sfh.c | 19 ++++++++++++++----- src/py21cmfast/src/cosmology.c | 1 + src/py21cmfast/wrapper/cfuncs.py | 18 +++++++++--------- src/py21cmfast/wrapper/outputs.py | 16 ++++++++-------- src/py21cmfast/wrapper/structs.py | 9 ++++++++- 11 files changed, 59 insertions(+), 39 deletions(-) diff --git a/src/py21cmfast/drivers/_param_config.py b/src/py21cmfast/drivers/_param_config.py index b812d5dbc..dfc8d3445 100644 --- a/src/py21cmfast/drivers/_param_config.py +++ b/src/py21cmfast/drivers/_param_config.py @@ -224,8 +224,8 @@ def check_consistency(kwargs: dict[str, Any], outputs: dict[str, OutputStruct]): def _make_wisdoms(self, use_fftw_wisdom: bool): construct_fftw_wisdoms(use_fftw_wisdom=use_fftw_wisdom) - def _broadcast_inputs(self, inputs: InputParameters): - broadcast_input_struct(inputs=inputs) + def _broadcast_inputs(self, inputs: InputParameters, redshift: float | None = None): + broadcast_input_struct(inputs=inputs, redshift=redshift) def _free_cosmo_tables(self): free_cosmo_tables() @@ -458,7 +458,7 @@ def __call__(self, **kwargs) -> OutputStruct: kwargs["inputs"] = inputs if out is None: - self._broadcast_inputs(inputs) + self._broadcast_inputs(inputs, redshift=current_redshift) self._make_wisdoms(inputs.matter_options.USE_FFTW_WISDOM) out = self._func(**kwargs) self._handle_write_to_cache(cache, write, out) diff --git a/src/py21cmfast/src/HaloBox.c b/src/py21cmfast/src/HaloBox.c index e924ead24..6d032975b 100644 --- a/src/py21cmfast/src/HaloBox.c +++ b/src/py21cmfast/src/HaloBox.c @@ -585,8 +585,8 @@ int ComputeHaloBox(InitialConditions *ini_boxes, HaloCatalog *halos, HaloCatalog "And current catalogues must not have SFRs computed."); Throw(ValueError); } - double redshift = get_current_redshift(ini_boxes); - double redshift_prev = get_previous_redshift(ini_boxes); + double redshift = get_current_redshift(); + double redshift_prev = get_previous_redshift(); #if LOG_LEVEL >= SUPER_DEBUG_LEVEL writeSimulationOptions(simulation_options_global); diff --git a/src/py21cmfast/src/HaloCatalog.c b/src/py21cmfast/src/HaloCatalog.c index 668e4f6e6..90438549f 100644 --- a/src/py21cmfast/src/HaloCatalog.c +++ b/src/py21cmfast/src/HaloCatalog.c @@ -40,7 +40,7 @@ int ComputeHaloCatalog(InitialConditions *boxes, unsigned long long int random_s int status; Try { // This Try brackets the whole function, so we don't indent. - double redshift = get_current_redshift(boxes); + double redshift = get_current_redshift(); bool from_catalog = (matter_options_global->SOURCE_MODEL == 4 && get_descendant_redshift() > 0); if (halos->sfh_computed || halos_desc->sfh_computed) { diff --git a/src/py21cmfast/src/PerturbedField.c b/src/py21cmfast/src/PerturbedField.c index 781f24125..3be9116af 100644 --- a/src/py21cmfast/src/PerturbedField.c +++ b/src/py21cmfast/src/PerturbedField.c @@ -394,7 +394,7 @@ int ComputePerturbedField(InitialConditions *boxes, PerturbedField *perturbed_fi int status; Try { // This Try{} wraps the whole function, so we don't indent. - double redshift = get_current_redshift(boxes); + double redshift = get_current_redshift(); // Makes the parameter structs visible to a variety of functions/macros // Do each time to avoid Python garbage collection issues diff --git a/src/py21cmfast/src/Stochasticity.c b/src/py21cmfast/src/Stochasticity.c index b09fba94a..efa6ef5c2 100644 --- a/src/py21cmfast/src/Stochasticity.c +++ b/src/py21cmfast/src/Stochasticity.c @@ -207,6 +207,8 @@ int add_properties_cat(unsigned long long int seed, float redshift, HaloCatalog gsl_rng *rng_stoc[simulation_options_global->N_THREADS]; seed_rng_threads_fast(rng_stoc, seed); + initialise_sfh_structs(redshift, -1.0, -1.0, false); + LOG_DEBUG("computing rng for %llu halos", halos->n_halos); // loop through the halos and assign properties @@ -225,6 +227,7 @@ int add_properties_cat(unsigned long long int seed, float redshift, HaloCatalog } free_rng_threads(rng_stoc); + cleanup_sfh_structs(); LOG_DEBUG("Done."); return 0; @@ -1052,7 +1055,7 @@ int stochastic_halofield(unsigned long long int seed, float *dens_field, float * HaloCatalog *halos_desc, HaloCatalog *halos) { double redshift = get_current_redshift(); double redshift_desc = get_descendant_redshift(); - double redshift_desc2 = get_redshift_relative(-2); + double redshift_desc2 = get_redshift_relative(2); if (redshift_desc > 0 && halos_desc->n_halos == 0) { LOG_DEBUG("No halos to sample from redshifts %.2f to %.2f, continuing...", redshift_desc, @@ -1068,18 +1071,17 @@ int stochastic_halofield(unsigned long long int seed, float *dens_field, float * struct HaloSamplingConstants hs_constants; stoc_set_consts_z(&hs_constants, redshift, redshift_desc, from_catalog); + initialise_sfh_structs(redshift, redshift_desc, redshift_desc2, from_catalog); // Fill them // NOTE:Halos prev in the first box corresponds to the large DexM halos if (!from_catalog) { LOG_DEBUG("building first halo field at z=%.1f", redshift); - initialise_sfh_structs(redshift, -1.0, -1.0, false); sample_halo_grids(rng_stoc, redshift, dens_field, halo_overlap_box, halos_desc, halos, &hs_constants); } else { LOG_DEBUG("Calculating halo progenitors from z=%.1f to z=%.1f | %llu", redshift_desc, redshift, halos_desc->n_halos); - initialise_sfh_structs(redshift, redshift_desc, redshift_desc2, true); sample_halo_progenitors(rng_stoc, redshift_desc, redshift, halos_desc, halos, &hs_constants); } @@ -1087,13 +1089,13 @@ int stochastic_halofield(unsigned long long int seed, float *dens_field, float * LOG_DEBUG("Found %llu Halos", halos->n_halos); if (halos->n_halos >= 3) { - LOG_DEBUG("First few Masses: %11.3e %11.3e %11.3e", halos->halo_masses[0], + LOG_DEBUG("First few Masses: %11.3e %11.3e %11.3e", halos->halo_masses[0], halos->halo_masses[1], halos->halo_masses[2]); - LOG_DEBUG("First few SFR10 RNG: %11.3e %11.3e %11.3e", halos->sfr_10[0], halos->sfr_10[1], - halos->sfr_10[2]); - LOG_DEBUG("First few SFR100 RNG: %11.3e %11.3e %11.3e", halos->sfr_100[0], + LOG_DEBUG("First few SFR10 RNG: %11.3e %11.3e %11.3e", halos->sfr_10[0], + halos->sfr_10[1], halos->sfr_10[2]); + LOG_DEBUG("First few SFR100 RNG: %11.3e %11.3e %11.3e", halos->sfr_100[0], halos->sfr_100[1], halos->sfr_100[2]); - LOG_DEBUG("First few Snapshot RNG: %11.3e %11.3e %11.3e", halos->stellar_mass[0], + LOG_DEBUG("First few Snapshot RNG: %11.3e %11.3e %11.3e", halos->stellar_mass[0], halos->stellar_mass[1], halos->stellar_mass[2]); } diff --git a/src/py21cmfast/src/_functionprototypes_wrapper.h b/src/py21cmfast/src/_functionprototypes_wrapper.h index f0f54babf..0e132ad14 100644 --- a/src/py21cmfast/src/_functionprototypes_wrapper.h +++ b/src/py21cmfast/src/_functionprototypes_wrapper.h @@ -66,7 +66,8 @@ void Broadcast_struct_global_noastro(SimulationOptions *simulation_options, MatterOptions *matter_options, CosmoParams *cosmo_params); void Broadcast_struct_global_all(SimulationOptions *simulation_options, MatterOptions *matter_options, CosmoParams *cosmo_params, - AstroParams *astro_params, AstroOptions *astro_options); + AstroParams *astro_params, AstroOptions *astro_options, + CosmoTables *cosmo_tables); void Broadcast_snapshot_info(int n_nodes, double *node_redshifts, int curr_node); void Free_cosmo_tables_global(); diff --git a/src/py21cmfast/src/correlated_sfh.c b/src/py21cmfast/src/correlated_sfh.c index 5a733e577..eff756429 100644 --- a/src/py21cmfast/src/correlated_sfh.c +++ b/src/py21cmfast/src/correlated_sfh.c @@ -417,14 +417,23 @@ void initialise_sfh_structs(double z0, double z1, double z2, bool conditioned) { sfh_mats.L_cov = gsl_matrix_alloc(3, 3); sfh_mats.mean_correction = gsl_matrix_alloc(3, 3); - if (z0 < 0. || conditioned && (z1 < 0. || z2 < 0.)) { - LOG_ERROR("You provided negative redshifts for SFH initialisation!"); + if (z0 < 0. || conditioned && !(z2 <= z1 <= z0)) { + LOG_ERROR("You provided invalid redshifts for SFH initialisation!"); + LOG_ERROR("Provided redshifts: z0 = %f, z1 = %f, z2 = %f", z0, z1, z2); + LOG_ERROR("All redshifts must satisfy z2 < z1 < z0 if contitioned"); Throw(ValueError); } - // positive for z2 > z1 > z0 - double tau = time_between_z(z0, z1) / (physconst.s_per_yr * 1e6); // Myr - double tau_prev = time_between_z(z1, z2) / (physconst.s_per_yr * 1e6); // Myr + double tau = astro_params_global->SFH_TAU * 100; // long timescale for uncorrelated sampling + double tau_prev = astro_params_global->SFH_TAU * 100; + if (conditioned) { + tau = time_between_z(z0, z1) / (physconst.s_per_yr * 1e6); // Myr + if (z2 >= 0.) { + tau_prev = time_between_z(z1, z2) / (physconst.s_per_yr * 1e6); // Myr + } + } + LOG_DEBUG("Initialising SFH structs with tau = %f Myr and tau_prev = %f Myr", tau, tau_prev); + LOG_DEBUG(" from redshifts z0 = %f, z1 = %f, z2 = %f", z0, z1, z2); // initialise_psd_corrfunc_tables(tau, tau_prev); // fill_covar_from_tables(tau, sfh_mats.curr_cov, sfh_mats.prev_cov, sfh_mats.pxc_cov); diff --git a/src/py21cmfast/src/cosmology.c b/src/py21cmfast/src/cosmology.c index 32235843a..67c6ab972 100644 --- a/src/py21cmfast/src/cosmology.c +++ b/src/py21cmfast/src/cosmology.c @@ -774,6 +774,7 @@ double t_hubble(float z) { return 1.0 / hubble(z); } /* comoving distance (in cm) per unit redshift */ double drdz(float z) { return (1.0 + z) * physconst.c_cms * dtdz(z); } +// NB: this will be positive when z_low < z_high double time_between_z(double z_low, double z_high) { double result, error; gsl_function F; diff --git a/src/py21cmfast/wrapper/cfuncs.py b/src/py21cmfast/wrapper/cfuncs.py index 742fe49b0..10e0a8422 100644 --- a/src/py21cmfast/wrapper/cfuncs.py +++ b/src/py21cmfast/wrapper/cfuncs.py @@ -24,7 +24,7 @@ # TODO: a lot of these assume input as numpy arrays via use of .shape, explicitly require this -def broadcast_input_struct(inputs: InputParameters): +def broadcast_input_struct(inputs: InputParameters, redshift: float | None = None): """Broadcast the parameters to the C library.""" lib.Broadcast_struct_global_all( inputs.simulation_options.cstruct, @@ -34,6 +34,14 @@ def broadcast_input_struct(inputs: InputParameters): inputs.astro_options.cstruct, inputs.cosmo_tables.cstruct, ) + if redshift is not None: + lib.Broadcast_snapshot_info( + len(inputs.node_redshifts), + ffi.cast( + "double*", np.array(inputs.node_redshifts, dtype="f8").ctypes.data + ), + inputs.node_redshifts.index(redshift), + ) def free_cosmo_tables(): @@ -50,14 +58,6 @@ def broadcast_params(func: Callable, redshift: float | None = None) -> Callable: def wrapper(*args, inputs: InputParameters, **kwargs): broadcast_input_struct(inputs) - if redshift is not None: - lib.Broadcast_snapshot_info( - inputs.node_redshifts.cstruct.size, - np.array( - inputs.node_redshifts.cstruct.node_redshifts, dtype="f8" - ).ctypes.data, - inputs.node_redshifts.cstruct.node_redshifts.index(redshift), - ) try: out = func(*args, inputs=inputs, **kwargs) except: diff --git a/src/py21cmfast/wrapper/outputs.py b/src/py21cmfast/wrapper/outputs.py index 0ca499371..11b6f5e67 100644 --- a/src/py21cmfast/wrapper/outputs.py +++ b/src/py21cmfast/wrapper/outputs.py @@ -775,7 +775,7 @@ class HaloCatalog(OutputStructZ): halo_coords = _arrayfield() n_halos: int = attrs.field(default=None) buffer_size: int = attrs.field(default=None) - _sfh_computed: bool = attrs.field(init=False, default=False) + sfh_computed: bool = attrs.field(init=False, default=False) @classmethod def new( @@ -815,7 +815,7 @@ def new( halo_masses=Array((buffer_size,), dtype=np.float32), sfr_10=Array((buffer_size,), dtype=np.float32), sfr_100=Array((buffer_size,), dtype=np.float32), - sfr_snapshot=Array((buffer_size,), dtype=np.float32), + stellar_mass=Array((buffer_size,), dtype=np.float32), descendant_index=Array((buffer_size,), dtype=np.int64), halo_coords=Array((buffer_size, 3), dtype=np.float32), redshift=redshift, @@ -840,9 +840,9 @@ def get_required_input_arrays(self, input_box: OutputStruct) -> list[str]: required += [ "halo_masses", "halo_coords", - "star_rng", - "sfr_rng", - "xray_rng", + "sfr_10", + "sfr_100", + "stellar_mass", ] else: raise ValueError( @@ -971,9 +971,9 @@ def get_required_input_arrays(self, input_box: OutputStruct) -> list[str]: required += [ "halo_coords", "halo_masses", - "star_rng", - "sfr_rng", - "xray_rng", + "sfr_10", + "sfr_100", + "stellar_mass", ] else: raise ValueError( diff --git a/src/py21cmfast/wrapper/structs.py b/src/py21cmfast/wrapper/structs.py index e8a603701..51fda2abb 100644 --- a/src/py21cmfast/wrapper/structs.py +++ b/src/py21cmfast/wrapper/structs.py @@ -31,7 +31,14 @@ class StructWrapper: cstruct = attrs.field(default=None) _ffi = attrs.field(default=ffi) - _TYPEMAP = bidict({"float32": "float *", "float64": "double *", "int32": "int *"}) + _TYPEMAP = bidict( + { + "float32": "float *", + "float64": "double *", + "int32": "int *", + "int64": "long long unsigned int *", + } + ) @_name.default def _name_default(self): From 45114d958a6139c753cfacf8a9fc00aab8065dc9 Mon Sep 17 00:00:00 2001 From: James Davies Date: Mon, 11 May 2026 21:39:18 +1000 Subject: [PATCH 18/18] fix tau bug in sfh --- src/py21cmfast/src/correlated_sfh.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/py21cmfast/src/correlated_sfh.c b/src/py21cmfast/src/correlated_sfh.c index eff756429..ee23095c7 100644 --- a/src/py21cmfast/src/correlated_sfh.c +++ b/src/py21cmfast/src/correlated_sfh.c @@ -55,12 +55,12 @@ static SFH_matrices sfh_mats; void print_gsl_matrix(gsl_matrix *mat, const char *label) { int nrows = mat->size1; int ncols = mat->size2; - fprintf(stdout, "%s\n", label); + fprintf(stderr, "%s\n", label); for (int i = 0; i < nrows; i++) { for (int j = 0; j < ncols; j++) { - fprintf(stdout, "%9.4f ", gsl_matrix_get(mat, i, j)); + fprintf(stderr, "%9.4f ", gsl_matrix_get(mat, i, j)); } - fprintf(stdout, "\n"); + fprintf(stderr, "\n"); } } @@ -427,9 +427,9 @@ void initialise_sfh_structs(double z0, double z1, double z2, bool conditioned) { double tau = astro_params_global->SFH_TAU * 100; // long timescale for uncorrelated sampling double tau_prev = astro_params_global->SFH_TAU * 100; if (conditioned) { - tau = time_between_z(z0, z1) / (physconst.s_per_yr * 1e6); // Myr + tau = time_between_z(z1, z0) / (physconst.s_per_yr * 1e6); // Myr if (z2 >= 0.) { - tau_prev = time_between_z(z1, z2) / (physconst.s_per_yr * 1e6); // Myr + tau_prev = time_between_z(z2, z1) / (physconst.s_per_yr * 1e6); // Myr } } LOG_DEBUG("Initialising SFH structs with tau = %f Myr and tau_prev = %f Myr", tau, tau_prev);