This repository implements a Fourier-domain PTA detection statistic together with right-tail p-value evaluation under get_phi implementation (PTA_Lite) to avoid enterprise-level constraints, while keeping the same
1) Portable parameter dump (param_list.txt + chain (just try the last few elements) .txt → samples.json)
- Reads parameter names from
param_list.txt - Reads the chain matrix from the sample chain file in
some sample data that you can play with/ - Aligns columns (default:
align="left") and writes a list of dict samples tosamples.json
(parameter-name → float value, one dict per sample)
Helpers to write/read a nested per-pulsar dictionary to a Feather file:
write_fourier_dataframe(pta_model, filename)read_fourier_dataframe(filename)
Arrays are converted into JSON-like objects that preserve shape
({"data": ..., "shape": ...} for ndim > 1).
PTA_Lite constructs Fourier prior variances in a real (cos/sin) basis:
- Fourier grid:
-
$K =$ nfrequenciespositive Fourier frequencies per pulsar - real basis duplication → vectors of length
2K
-
- Common GW process:
- only first
gw_components = Gfrequencies are active → length2G, then zero-padded to2K - uses
_rho_flat_tail(log10_A, gamma, f, df, log10_kappa=None)
(GW useslog10_kappa=None)
- only first
- Per-pulsar red noise (RN):
- each pulsar has its own
log10_A,gamma,log10_kappa
- each pulsar has its own
- Modes:
-
mode="curn"(H0): returns a list (length$P$ ) of per-pulsar diagonals
rho_rn + rho_gw_padded -
mode="hd"(H1): returns the full correlated matrix $ \Phi_C = I\otimes \mathrm{diag}(\rho_\mathrm{RN}) + \Gamma\otimes \mathrm{diag}(\rho_\mathrm{GW}) $ where$\Gamma$ is the Hellings–Downs ORF built from sky positions
(phi, theta).
-
Required keys in pta_model[pulsar] for PTA_Lite:
phi,theta(radians)Tspan(in Julian years; converted internally to seconds)nfrequencies(same for all pulsars)
Required keys in par_dict for PTA_Lite.get_phi
(if rn_name="red_noise"):
- Global GW:
gw_log10_A,gw_gamma - Per pulsar RN:
{PSR}_red_noise_log10_A{PSR}_red_noise_gamma{PSR}_red_noise_log10_kappa
FourierDetectionStatistic computes:
-
os_val: detection statistic -
y: reduced data vector -
Q: reduced quadratic form matrix -
Sigma_y: covariance ofyunder$H_0$ (used for p-values)
Expected keys in pta_model[pulsar] for the statistic:
phiinv(vector-diagonal or square matrix)Sigma/Sigma_inv(vector-diagonal or square matrix)a_hat(shape(fourier_num,))
Core steps (as implemented):
- Build big block-diagonals:
BigPhi0Inv,Sigma0,Sigma0Inv - Stack
ahat0across pulsars - Build
$\phi_N$ from H0 and$\phi_C$ from H1, then
$\Delta\phi = \phi_C - \phi_N$
(slicing to the last -K coefficients per pulsar via_idx_lastK) - Transfer covariance and coefficients:
$\Sigma^{-1} = \Sigma_0^{-1} + \phi_N^{-1} - \Phi_0^{-1}, \hat a = \Sigma,\Sigma_0^{-1},\hat a_0 $ - Dimension reduction from
$\Delta\phi$ :-
proj_method="mask": select active support (recommended/default) -
proj_method="svd": thin-SVD basis (optional)
-
- Reduced statistic:
y = G_A^T \hat aQ_num = PhiN_red @ dphi_red @ PhiN_red.T- normalize by
dento obtain
Q = Q_num / denand
os_val = (y^T Q_num y) / den
- Output
Sigma_y = G_A^T (phiN - Sigma) G_A
Given Q, Sigma_y, and observed os_val, the p-value routine:
- repairs
Sigma_yto be PSD (clips tiny negative eigenvalues) - whitens using Cholesky (adds a small diagonal jitter if needed)
- diagonalizes
$S = L^T Q L$ so that
$D = \sum_i \lambda_i z_i^2$ - evaluates
$F_D(\mathrm{os_val})$ via Imhof and returns
p_right = 1 - F_D(os_val)
Main function:
spectral_and_pvalue_from_yQ(Q, Sigma_y, os_val, ...) -> p_right
plot_pdf(Q, Sigma_y, os_val, ...) evaluates the gx2pdf)
and marks os_val and the mean
The notebooks use (at least):
numpy, scipy, matplotlib, pandas, pyarrow, astropy,
dill, tqdm, and la_forge.
Sample files are provided in
some sample data that you can play with\:
param_list.txt- a small example chain file
Run the corresponding notebook cells to generate:
samples.json
A sample PTA model is provided as:
pta_model_gpta_2.feather
This is read into a pta_model dictionary using the Feather helpers.
K = next(iter(pta_model.values()))['nfrequencies']
pta_h0 = PTA_Lite(pta_model, mode='curn', components=K,
gw_components=5, rn_name='red_noise')
pta_h1 = PTA_Lite(pta_model, mode='hd', components=K,
gw_components=5, rn_name='red_noise')par_dict = big_array[-1] # any sample dict
os_fourier = FourierDetectionStatistic(
pta_h0, pta_h1, pta_model,
fourier_num=20,
proj_method="mask", # or "svd"
tol=1e-30,
rel_eps=None,
order=None,
verbose=False
)
os_val, y, Q, Sigma_y = os_fourier.get_deflection_coordinates(par_dict)
p_right = spectral_and_pvalue_from_yQ(Q, Sigma_y, os_val)
print("p-right =", p_right)fig, ax = plot_pdf(Q, Sigma_y, os_val,
complex_mode=False,
cutoff=1e-12,
npts=800)
plt.show()-
fourier_num: number of Fourier coefficients per pulsar used
(the last$K$ per pulsar) -
proj_method="mask": selects active support of$\Delta\phi$ (recommended) -
proj_method="svd": thin-SVD basis (optional) -
tol: threshold for mask support selection -
rel_eps: SVD cutoff scaling (Noneuseseps * n * smax) -
order: pulsar ordering for consistent stacking
(default:pta_h0.pulsars) -
complex_mode(p-value / PDF): duplicates eigenvalues if desired