Skip to content

Commit b84b0c4

Browse files
committed
Merge branch 'main' into BMF_and_ionized_maps
2 parents 1efe55c + 9ea7c39 commit b84b0c4

13 files changed

Lines changed: 628 additions & 69 deletions

.codecov_trigger

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# This file triggers the CI workflow

.github/workflows/python-tests.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Python Tests
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
- name: Set up Python 3.11
16+
uses: actions/setup-python@v4
17+
with:
18+
python-version: '3.11'
19+
20+
- name: Install CLASS dependencies
21+
run: |
22+
sudo apt-get update
23+
sudo apt-get install -y build-essential gfortran
24+
25+
- name: Install dependencies
26+
run: |
27+
python -m pip install --upgrade pip
28+
pip install pytest pytest-cov
29+
pip install Cython
30+
pip install -r requirements.txt
31+
32+
- name: Install CLASS
33+
run: |
34+
git clone https://github.com/lesgourg/class_public.git
35+
cd class_public
36+
make
37+
cd python
38+
python setup.py install
39+
cd ../../
40+
# Keep the directory for data files
41+
42+
- name: Install package
43+
run: |
44+
pip install -e .
45+
46+
- name: Debug SFR_III function
47+
env:
48+
CLASSDIR: ${{ github.workspace }}/class_public
49+
run: |
50+
python -c "import zeus21; from zeus21.sfrd import SFR_III; import inspect; print('SFR_III parameters:', inspect.signature(SFR_III)); print('Parameter count:', len(inspect.signature(SFR_III).parameters))"
51+
52+
- name: Run tests with coverage
53+
env:
54+
CLASSDIR: ${{ github.workspace }}/class_public
55+
run: |
56+
python -m pytest --cov=zeus21 --cov-report=xml --cov-report=term tests/
57+
58+
- name: Upload coverage reports to Codecov
59+
uses: codecov/codecov-action@v4
60+
env:
61+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
62+
with:
63+
files: ./coverage.xml
64+
flags: unittests
65+
name: codecov-umbrella
66+
verbose: true
67+
fail_ci_if_error: false

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
# Zeus21: Lightning-fast simulations of cosmic dawn
66

7+
[![Tests](https://github.com/JulianBMunoz/Zeus21/actions/workflows/python-tests.yml/badge.svg)](https://github.com/JulianBMunoz/Zeus21/actions/workflows/python-tests.yml)
8+
[![codecov](https://codecov.io/gh/JulianBMunoz/Zeus21/branch/main/graph/badge.svg)](https://codecov.io/gh/JulianBMunoz/Zeus21)
9+
710
Zeus21 encodes the effective model for the 21-cm power spectrum and global signal from [Muñoz 2023a](https://arxiv.org/abs/2302.08506). The goal is to capture all the nonlocal and nonlinear physics of cosmic dawn in a light and fully Pythonic code. Zeus21 takes advantage of the approximate log-normality of the star-formation rate density (SFRD) during cosmic dawn to compute the 21-cm power spectrum analytically. It agrees with more expensive semi-numerical simulations to roughly 10% precision, but has comparably negligible computational cost (~ s) and memory requirements. Now Zeus21 can also predict galaxy UV luminosity functions (UVLFs) and their linear clustering (galaxy bias) at any z, see [Muñoz et al. 2023b](https://arxiv.org/abs/2306.09403) for the implementation and application to JWST. Now Zeus21 includes Population III stars with inhomogeneous Lyman-Werner feedback and relative velocities (with their fluctuations), as described in [Cruz et al. 2024](https://arxiv.org/abs/2407.18294).
811

912
Zeus21 (Zippy Early-Universe Solver for 21-cm) pairs well with data from [HERA](https://reionization.org/), but can be used for any 21-cm inference or prediction. Current capabilities include finding the 21-cm power spectrum (at a broad range of k and z), the global signal, IGM temperatures (Tk, Ts, Tcolor), neutral fraction xHI, Lyman-alpha fluxes, and the evolution of the SFRD; all across cosmic dawn z=5-35. Zeus21 can use three different astrophysical models, one of which emulates 21cmFAST, and can vary the cosmology through CLASS.

codecov.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
codecov:
2+
require_ci_to_pass: yes
3+
4+
coverage:
5+
precision: 2
6+
round: down
7+
range: "70...100"
8+
9+
status:
10+
project:
11+
default:
12+
target: auto
13+
threshold: 5%
14+
patch: yes
15+
changes: no
16+
17+
parsers:
18+
gcov:
19+
branch_detection:
20+
conditional: yes
21+
loop: yes
22+
method: no
23+
macro: no
24+
25+
comment:
26+
layout: "reach,diff,flags,files,footer"
27+
behavior: default
28+
require_changes: no

docs/Tutorial_Zeus21_UVLFs.ipynb

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,14 +226,27 @@
226226
"execution_count": null,
227227
"metadata": {},
228228
"outputs": [],
229-
"source": []
229+
"source": [
230+
"#and the newly added pop3 UVLF - note they'll normally be very faint, so you'll need to go to fainter MUVs\n",
231+
"AstroParams_popIII = zeus21.Astro_Parameters(UserParams, CosmoParams, accretion_model=0, USE_POPIII=True) "
232+
]
230233
},
231234
{
232235
"cell_type": "code",
233236
"execution_count": null,
234237
"metadata": {},
235238
"outputs": [],
236-
"source": []
239+
"source": [
240+
"UVLFs_pop2,UVLFs_pop3= zeus21.UVLFs.UVLF_binned(AstroParams_popIII,CosmoParams,HMFintclass,z,dz,MUVcenters,MUVwidths)\n",
241+
"\n",
242+
"\n",
243+
"plt.semilogy(MUVcenters,UVLFs_pop2,'k-')\n",
244+
"plt.semilogy(MUVcenters,UVLFs_pop3,'r--')\n",
245+
"plt.xlim(-22,-17)\n",
246+
"plt.ylim(1e-6,1e-1)\n",
247+
"plt.xlabel(r'$M_{\\rm UV}$');\n",
248+
"plt.ylabel(r'$\\Phi_{\\rm UV}\\,\\rm [Mpc^{-3}\\,mag^{-1}]$');"
249+
]
237250
}
238251
],
239252
"metadata": {

requirements.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
pytest
22
numpy
33
scipy
4-
sphinx
5-
myst_parser
4+
mcfit
65
numexpr
76
astropy
87
tqdm
98
matplotlib
9+
pyfftw
1010
powerbox
11-
pyfftw
11+
sphinx
12+
myst_parser

tests/test_UVLFs.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
"""
2+
3+
Test UV luminosity functions for Zeus21
4+
5+
Author: Claude AI
6+
April 2025
7+
8+
"""
9+
10+
import pytest
11+
import zeus21
12+
import numpy as np
13+
14+
from zeus21.UVLFs import UVLF_binned, MUV_of_SFR, AUV, beta
15+
16+
def test_MUV_of_SFR():
17+
"""Test the conversion from SFR to UV magnitudes"""
18+
# Test a range of SFR values
19+
SFR_test = np.logspace(-3, 2, 10) # M_sun/yr
20+
kappaUV_test = 1.15e-28 # Typical value
21+
22+
# Calculate MUV
23+
MUV_result = MUV_of_SFR(SFR_test, kappaUV_test)
24+
25+
# Check that increasing SFR leads to brighter (more negative) MUV
26+
assert np.all(np.diff(MUV_result) < 0)
27+
28+
# Check specific value based on the formula M_UV = 51.63 - 2.5*log10(SFR/kappaUV)
29+
# For SFR = 1 M_sun/yr with kappaUV = 1.15e-28
30+
expected_MUV = 51.63 - 2.5 * np.log10(1.0/1.15e-28)
31+
assert MUV_of_SFR(np.array([1.0]), kappaUV_test)[0] == pytest.approx(expected_MUV)
32+
33+
# Test different kappaUV values
34+
kappaUV_test2 = 2.0e-28
35+
MUV_result2 = MUV_of_SFR(SFR_test, kappaUV_test2)
36+
37+
# Higher kappaUV should result in fainter magnitudes (more positive)
38+
assert np.all(MUV_result2 > MUV_result)
39+
40+
def test_beta_function():
41+
"""Test the beta (UV slope) calculation"""
42+
# Test a single redshift and magnitude but use arrays as the function expects
43+
z_test = np.array([5.0])
44+
MUV_test = np.array([-20.0])
45+
46+
# Calculate beta value
47+
beta_value = beta(z_test, MUV_test)
48+
49+
# Check that beta value is reasonable (typical range is -3 to -1)
50+
assert beta_value > -3.0
51+
assert beta_value < -1.0
52+
53+
# Test at pivot point
54+
MUV_pivot = np.array([-19.5]) # The pivot point defined in the code
55+
beta_at_pivot = beta(z_test, MUV_pivot)
56+
57+
# Check that a value is returned
58+
assert isinstance(beta_at_pivot, np.ndarray)
59+
60+
def test_AUV_function():
61+
"""Test the dust attenuation calculation"""
62+
# Set up parameters
63+
UserParams = zeus21.User_Parameters()
64+
CosmoParams_input = zeus21.Cosmo_Parameters_Input()
65+
ClassyCosmo = zeus21.runclass(CosmoParams_input)
66+
CosmoParams = zeus21.Cosmo_Parameters(UserParams, CosmoParams_input, ClassyCosmo)
67+
AstroParams = zeus21.Astro_Parameters(UserParams, CosmoParams)
68+
69+
# Test with arrays as the function expects
70+
z_test = np.array([5.0])
71+
MUV_test = np.array([-20.0])
72+
73+
# Calculate dust attenuation
74+
A_UV = AUV(AstroParams, z_test, MUV_test)
75+
76+
# Check that attenuation is non-negative
77+
assert np.all(A_UV >= 0.0)
78+
79+
# Test the HIGH_Z_DUST flag behavior
80+
z_high = np.array([9.0]) # High redshift above _zmaxdata
81+
_zmaxdata = 8.0
82+
83+
# Test with HIGH_Z_DUST=True (dust applied at high z)
84+
A_UV_high = AUV(AstroParams, z_high, MUV_test, HIGH_Z_DUST=True)
85+
86+
# Test with HIGH_Z_DUST=False (no dust above _zmaxdata)
87+
A_UV_no_highz = AUV(AstroParams, z_high, MUV_test, HIGH_Z_DUST=False, _zmaxdata=_zmaxdata)
88+
89+
# HIGH_Z_DUST=False should give zero attenuation for z > _zmaxdata
90+
assert np.all(A_UV_no_highz == 0.0)
91+
92+
def test_UVLF_binned():
93+
"""Test the binned UV luminosity function calculation"""
94+
# Set up parameters
95+
UserParams = zeus21.User_Parameters()
96+
CosmoParams_input = zeus21.Cosmo_Parameters_Input(kmax_CLASS=10., zmax_CLASS=20.)
97+
ClassyCosmo = zeus21.runclass(CosmoParams_input)
98+
CosmoParams = zeus21.Cosmo_Parameters(UserParams, CosmoParams_input, ClassyCosmo)
99+
AstroParams = zeus21.Astro_Parameters(UserParams, CosmoParams)
100+
HMFintclass = zeus21.HMF_interpolator(UserParams, CosmoParams, ClassyCosmo)
101+
102+
# Test data
103+
z_center = 6.0
104+
z_width = 0.5
105+
MUV_centers = np.array([-22.0, -20.0, -18.0])
106+
MUV_widths = np.full_like(MUV_centers, 1.0)
107+
108+
# Calculate UVLF
109+
uvlf = UVLF_binned(AstroParams, CosmoParams, HMFintclass, z_center, z_width,
110+
MUV_centers, MUV_widths, DUST_FLAG=True, RETURNBIAS=False)
111+
112+
# Check dimensions
113+
assert uvlf.shape == (3,)
114+
115+
# Check that values are positive
116+
assert np.all(uvlf >= 0.0)
117+
118+
# Test that fainter (more positive MUV) bins typically have higher number densities
119+
# This is a general trend for LFs, but not strictly required
120+
# We'll do a weak test that they're not all identical
121+
assert len(np.unique(uvlf)) > 1
122+
123+
# Test RETURNBIAS flag
124+
bias_values = UVLF_binned(AstroParams, CosmoParams, HMFintclass, z_center, z_width,
125+
MUV_centers, MUV_widths, DUST_FLAG=True, RETURNBIAS=True)
126+
127+
# Check dimensions
128+
assert bias_values.shape == (3,)
129+
130+
# Check that biases are positive
131+
assert np.all(bias_values >= 0.0)
132+
133+
# Test without dust correction
134+
uvlf_nodust = UVLF_binned(AstroParams, CosmoParams, HMFintclass, z_center, z_width,
135+
MUV_centers, MUV_widths, DUST_FLAG=False, RETURNBIAS=False)
136+
137+
# Check dimensions
138+
assert uvlf_nodust.shape == (3,)
139+
140+
# Without dust, we expect different values than with dust
141+
assert not np.array_equal(uvlf, uvlf_nodust)

tests/test_astrophysics.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def test_background():
5757
assert( (0 <= sSFR).all()) #positive
5858
assert( (sSFR/zeus21.cosmology.Hubinvyr(CosmoParams,ztest) <= 1).all()) #make sure sSFR/H < 1 (not all mass forms stars in a Hubble time)
5959

60-
sSFR3 = SFR_III(AstroParams, CosmoParams, ClassyCosmo, HMFintclass, HMFintclass.Mhtab, Coeffs_popIII.J21LW_interp_conv_avg, ztest, ztest, ClassyCosmo.pars['v_avg'])/HMFintclass.Mhtab
60+
sSFR3 = SFR_III(AstroParams, CosmoParams, HMFintclass, HMFintclass.Mhtab, Coeffs_popIII.J21LW_interp_conv_avg, ztest, ztest, ClassyCosmo.pars['v_avg'])/HMFintclass.Mhtab
6161
assert( (0 <= sSFR3).all()) #positive
6262
assert( (sSFR3/zeus21.cosmology.Hubinvyr(CosmoParams,ztest) <= 1).all()) #make sure sSFR3/H < 1 (not all mass forms stars in a Hubble time)
6363

@@ -67,7 +67,7 @@ def test_background():
6767
assert( (0 <= sSFR_exp).all())
6868
assert( (sSFR_exp/zeus21.cosmology.Hubinvyr(CosmoParams,ztest) <= 1).all())
6969

70-
sSFR_exp3 = SFR_III(AstroParams_expacc, CosmoParams, ClassyCosmo, HMFintclass, HMFintclass.Mhtab, Coeffs_popIII.J21LW_interp_conv_avg, ztest, ztest, ClassyCosmo.pars['v_avg'])/HMFintclass.Mhtab
70+
sSFR_exp3 = SFR_III(AstroParams_expacc, CosmoParams, HMFintclass, HMFintclass.Mhtab, Coeffs_popIII.J21LW_interp_conv_avg, ztest, ztest, ClassyCosmo.pars['v_avg'])/HMFintclass.Mhtab
7171
assert( (0 <= sSFR_exp3).all())
7272
assert( (sSFR_exp3/zeus21.cosmology.Hubinvyr(CosmoParams,ztest) <= 1).all())
7373

@@ -77,7 +77,7 @@ def test_background():
7777
assert( (0 <= sSFR_21cmfast).all())
7878
assert( (sSFR_21cmfast/zeus21.cosmology.Hubinvyr(CosmoParams_21cmfast,ztest) <= 1).all())
7979

80-
sSFR_21cmfast3 = SFR_III(AstroParams_expacc, CosmoParams_21cmfast, ClassyCosmo, HMFintclass, HMFintclass.Mhtab, Coeffs_popIII.J21LW_interp_conv_avg, ztest, ztest, ClassyCosmo.pars['v_avg'])/HMFintclass.Mhtab
80+
sSFR_21cmfast3 = SFR_III(AstroParams_expacc, CosmoParams_21cmfast, HMFintclass, HMFintclass.Mhtab, Coeffs_popIII.J21LW_interp_conv_avg, ztest, ztest, ClassyCosmo.pars['v_avg'])/HMFintclass.Mhtab
8181
assert( (0 <= sSFR_21cmfast3).all())
8282
assert( (sSFR_21cmfast3/zeus21.cosmology.Hubinvyr(CosmoParams_21cmfast,ztest) <= 1).all())
8383

0 commit comments

Comments
 (0)