Skip to content

Halo–halo 2PCF

The halo–halo two-point correlation function xi_hh(r) is computed in a periodic box with pycorr (Corrfunc backend) for every auto and cross combination of the configured log-mass bins.

Two flavours

halocat exposes two complementary entry points:

Method Result Persisted? Use for
XiHHLoader.get All auto + cross pairs from the static MASS_BINS grid → XiHHRecord yes (xi_hh.hdf5) reproducible measurements you'll re-use across analyses
XiHHLoader.measure_pair One ξ for an arbitrary (log10M1, log10M2) pair → XiHHPairRecord no ad-hoc bins, parameter scans, exploratory analysis

Static grid via get

The static grid is defined by MASS_BINS and R_EDGES in halocat.config, and may be overridden by scripts/config_xi_hh.yaml (see Configuration).

from halocat import XiHHLoader

xi = XiHHLoader().get("LCDM", 0.25, imodel=1, ibox=1)

print(xi.pairs)            # ['M0_M0', 'M0_M1', ..., 'Mp_Mp']
print(xi.xi.shape)         # (n_pairs, n_r_bins)
print(xi.mass_bins)        # (P, 2) [lo, hi] log10 M

Pair groups are named M{i}_M{j} with i <= j (autos plus upper-triangular crosses). pair_indices gives the (i, j) pair as integer columns.

Plotting r²ξ for the autos

import matplotlib.pyplot as plt

for k, (i, j) in enumerate(xi.pair_indices):
    if i != j:
        continue
    lo, hi = xi.mass_bins[i]
    plt.plot(xi.r[k], xi.r[k] ** 2 * xi.xi[k],
             label=f"{xi.pairs[k]}  log10M=[{lo:.2f}, {hi:.2f})")
plt.xscale("log")
plt.legend(fontsize=8)

Custom mass-bin pair via measure_pair

When you need xi_hh(r | bin1, bin2) for finite-width bins not in the static grid:

loader = XiHHLoader()

# Auto-correlation of one custom bin
rec = loader.measure_pair(
    "LCDM", 0.25, imodel=1, ibox=1,
    log10M1=(13.0, 13.5),       # log10M2 defaults to log10M1
)
rec.is_auto, rec.n1, rec.xi.shape

# Cross-correlation of two non-overlapping bins
rec_x = loader.measure_pair(
    "LCDM", 0.25, 1, 1,
    log10M1=(13.0, 13.3),
    log10M2=(13.7, 14.0),
)

measure_pair:

  • never writes anything to disk
  • never reads xi_hh.hdf5 — it only loads halo.hdf5 (auto-reformatting the source .DAT if missing) and runs pycorr for that one pair
  • accepts a custom r_edges=... argument; the default is R_EDGES

The returned XiHHPairRecord carries r, xi, r_edges, log10M1, log10M2, n1, n2 and an attrs dict.

Bin-centre and empty-bin policy

Always arithmetic centres

Both code paths set r to the arithmetic mean of r_edges, never pycorr.sepavg. sepavg returns NaN for empty bins and fluctuates from realisation to realisation, which would prevent sub-grid stacking from staying aligned.

Empty separation bins are flagged with xi = NaN. Empty mass-bin selections (n1 == 0 or n2 == 0) yield an all-NaN row.

Stacking across iboxes

XiHHLoader.get_grid returns arrays of shape (G, Z, M, B, P, K) for r and xi, plus per-pair n1 / n2 of shape (G, Z, M, B, P) and a (G, Z, M, B) present mask.

import numpy as np

grid = loader.get_grid(
    gravities=["LCDM"], redshifts=[0.25],
    imodels=[1], iboxes=[1, 2, 3, 4, 5],
)
xi_mean = np.nanmean(grid["xi"], axis=3)   # mean over iboxes

Driver scripts

Script Purpose
scripts/measure_xi_hh.py Batch measurement loop (reads YAML by default)
scripts/load_xi_hh.py Inspect single records or a sub-grid summary
scripts/plot_xi_hh.py One-panel r²ξ plot, one line per mass-bin pair

See scripts/bookkeeping.md for the full flag list.