On Windows, get_cmb_power_spectra returns an all-NaN lens_potential (and hence NaN lensed_scalar/total) for an ordinary Planck-like LCDM, with no exception raised. Everything else in the same call is correct.
I appreciate this looks like it cannot be CAMB's fault, and it may well not be — the trigger involves two unrelated libraries. I'm reporting it here because CAMB is where the wrong number is produced, and because the failure is confined to the non-linear lensing branch, which narrows it a lot. Happy to move it to scipy or matplotlib if you think it belongs there.
Reproduction
Three ingredients. All three are required; any two are harmless.
import scipy.stats # 1
from matplotlib import font_manager
from matplotlib.ft2font import FT2Font
font = FT2Font(font_manager.findfont("DejaVu Sans"))
font.set_size(10, 100)
font.set_text("Redshift z") # 2
font.get_width_height()
import numpy as np, camb # 3
pars = camb.CAMBparams()
pars.set_cosmology(H0=70.0, ombh2=0.02237, omch2=0.1177, tau=0.0544, mnu=0.06)
pars.InitPower.set_params(As=np.exp(3.044) / 1e10, ns=0.9649)
pars.set_for_lmax(2508, lens_potential_accuracy=1)
pars.set_matter_power(redshifts=[0.0], kmax=2.0)
results = camb.get_results(pars)
powers = results.get_cmb_power_spectra(pars, CMB_unit="muK", raw_cl=True)
print("sigma8 ", np.isfinite(results.get_sigma8_0()))
print("unlensed_scalar", np.all(np.isfinite(powers["unlensed_scalar"])))
print("lens_potential ", np.all(np.isfinite(powers["lens_potential"])))
Output on a failing run:
sigma8 True
unlensed_scalar True
lens_potential False
Each run needs a fresh process. The outcome is decided once per process — repeating the call inside one process returns NaN again every time, so this is not a transient glitch. Rate is 8/8 with default threading, 7/10 with OMP_NUM_THREADS=1.
A self-contained script that prints versions and can measure the rate itself is at
https://github.com/salihyesil59/CosmoFit/blob/dev/tools/camb_nan_repro.py
What still works in the failing call
|
|
hubble_parameter, comoving_radial_distance |
finite |
matter transfer data, P(k) |
finite |
get_sigma8_0() |
finite |
unlensed_scalar, unlensed_total, tensor |
finite |
lens_potential |
NaN |
lensed_scalar, total |
NaN (inherited) |
pars.NonLinear = camb.model.NonLinear_none makes the same call finite. Every configuration with the non-linear correction on fails, including lens_potential_accuracy=0 with NonLinear_lens still set.
Things I checked, so you don't have to
Everything below was measured over at least 8 fresh processes per configuration; single runs are meaningless here.
- Not the floating-point mode. MXCSR and the CRT control word are bit-identical (
0x1fa0, round-to-nearest, all exceptions masked) before the layout and in 8 of 8 failing runs.
- Not memory pressure. Substituting allocation churn for the text layout — 20k small numpy arrays, 40 MB-scale numpy arrays, or 400k Python objects — is 0/8 in all three cases.
- Not scipy's bundled OpenBLAS.
scipy.linalg is what loads it, and scipy.linalg + layout is 0/8. Of linalg, special, integrate, interpolate, optimize, sparse, stats, only stats reproduces (4/8 alone; 7/8 with all seven imported).
- Not HMcode's derivation. At
set_feedback_level(3) the HMcode-2020 output (Dv, dc, eta, k*, A, fdamp, alpha at each redshift) is byte-identical, 240 lines, between a failing run and a clean one. With NonLinear_both the returned non-linear P(k) is finite while lens_potential is NaN in the same call.
- Not the parameters. Pickling every
CAMBparams from a failing session and replaying them in a fresh process is completely clean.
- Order-dependent. Importing
scipy.stats after the glyph layout instead of before it is clean, 0/8. So it is not simply that both are loaded; which one initialises first decides it.
The last point plus the per-process determinism is what makes me suspect module layout or DLL binding rather than arithmetic.
Environment
Windows 11 (10.0.26200), AMD64, Intel 16 cores
Python 3.13.15 (MSC v.1944 64 bit)
camb 2.0.4 (pip wheel, cambdll.dll 2,722,426 bytes)
numpy 2.5.2
scipy 1.18.1
matplotlib 3.11.1
halofit_version = mead2020 (the default via set_for_lmax)
Verified on this one machine only. Everything above is measured
rather than inferred, but all of it comes from a single Windows
installation, so I cannot say whether the trigger generalises or
depends on something local to it. That is the first thing worth
checking, and the script prints the versions it ran under to make
comparison easy.
Why it matters
The NaN is returned rather than raised, so a likelihood consuming these spectra turns it into a non-finite chi2, which a sampler reads as a rejection. In an MCMC that is a quietly dropped point rather than a visible error. I guard against it downstream now, but a caller who does not check would never see it.
On Windows,
get_cmb_power_spectrareturns an all-NaNlens_potential(and hence NaNlensed_scalar/total) for an ordinary Planck-like LCDM, with no exception raised. Everything else in the same call is correct.I appreciate this looks like it cannot be CAMB's fault, and it may well not be — the trigger involves two unrelated libraries. I'm reporting it here because CAMB is where the wrong number is produced, and because the failure is confined to the non-linear lensing branch, which narrows it a lot. Happy to move it to scipy or matplotlib if you think it belongs there.
Reproduction
Three ingredients. All three are required; any two are harmless.
Output on a failing run:
Each run needs a fresh process. The outcome is decided once per process — repeating the call inside one process returns NaN again every time, so this is not a transient glitch. Rate is 8/8 with default threading, 7/10 with
OMP_NUM_THREADS=1.A self-contained script that prints versions and can measure the rate itself is at
https://github.com/salihyesil59/CosmoFit/blob/dev/tools/camb_nan_repro.py
What still works in the failing call
hubble_parameter,comoving_radial_distanceP(k)get_sigma8_0()unlensed_scalar,unlensed_total,tensorlens_potentiallensed_scalar,totalpars.NonLinear = camb.model.NonLinear_nonemakes the same call finite. Every configuration with the non-linear correction on fails, includinglens_potential_accuracy=0withNonLinear_lensstill set.Things I checked, so you don't have to
Everything below was measured over at least 8 fresh processes per configuration; single runs are meaningless here.
0x1fa0, round-to-nearest, all exceptions masked) before the layout and in 8 of 8 failing runs.scipy.linalgis what loads it, andscipy.linalg+ layout is 0/8. Oflinalg,special,integrate,interpolate,optimize,sparse,stats, onlystatsreproduces (4/8 alone; 7/8 with all seven imported).set_feedback_level(3)the HMcode-2020 output (Dv, dc, eta, k*, A, fdamp, alpha at each redshift) is byte-identical, 240 lines, between a failing run and a clean one. WithNonLinear_boththe returned non-linearP(k)is finite whilelens_potentialis NaN in the same call.CAMBparamsfrom a failing session and replaying them in a fresh process is completely clean.scipy.statsafter the glyph layout instead of before it is clean, 0/8. So it is not simply that both are loaded; which one initialises first decides it.The last point plus the per-process determinism is what makes me suspect module layout or DLL binding rather than arithmetic.
Environment
Verified on this one machine only. Everything above is measured
rather than inferred, but all of it comes from a single Windows
installation, so I cannot say whether the trigger generalises or
depends on something local to it. That is the first thing worth
checking, and the script prints the versions it ran under to make
comparison easy.
Why it matters
The NaN is returned rather than raised, so a likelihood consuming these spectra turns it into a non-finite chi2, which a sampler reads as a rejection. In an MCMC that is a quietly dropped point rather than a visible error. I guard against it downstream now, but a caller who does not check would never see it.