Skip to content

Commit 7f09d76

Browse files
committed
Merge branch 'main' into use_fitcommondata
2 parents a89dc3b + 58b40ed commit 7f09d76

9 files changed

Lines changed: 223 additions & 109 deletions

colibri/analytic_fit.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,9 @@ def analytic_evidence_uniform_prior(sol_covmat, sol_mean, max_logl, a_vec, b_vec
8585
def analytic_fit(
8686
central_covmat_index,
8787
forward_map,
88-
pdf_model,
8988
analytic_settings,
9089
prior_settings,
91-
FIT_XGRID,
9290
fast_kernel_arrays,
93-
data,
9491
):
9592
"""
9693
Analytic fits, for any *linear* PDF model.
@@ -109,28 +106,18 @@ def analytic_fit(
109106
forward_map: @jax.jit CompiledFunction
110107
Forward map function for the fit.
111108
112-
pdf_model: pdf_model.PDFModel
113-
PDF model to fit.
114-
115109
analytic_settings: dict
116110
Settings for the analytic fit.
117111
118112
prior_settings: PriorSettings
119113
Settings for the prior.
120114
121-
FIT_XGRID: np.ndarray
122-
xgrid of the theory, computed by a production rule by taking
123-
the sorted union of the xgrids of the datasets entering the fit.
124-
125115
fast_kernel_arrays: tuple
126116
Tuple containing the fast kernel arrays.
127-
128-
data: validphys.core.DataGroupSpec
129-
The data group specification for the fit.
130117
"""
131118
# Ensure that the PDF model is linear before running the fit.
132119
log.info("Checking that the PDF model is linear...")
133-
check_pdf_model_is_linear(forward_map, FIT_XGRID, data)
120+
check_pdf_model_is_linear(forward_map, fast_kernel_arrays)
134121

135122
log.warning("The prior is assumed to be flat in the parameters.")
136123
log.warning(

colibri/checks.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
from reportengine.checks import make_argcheck
88
import jax.numpy as jnp
99
import jax
10-
from colibri.theory_predictions import fast_kernel_arrays
11-
1210
from colibri.utils import get_fit_path, get_pdf_model
1311

1412

@@ -42,13 +40,17 @@ def check_pdf_models_equal(prior_settings, forward_map, theoryid):
4240
)
4341

4442

45-
def check_pdf_model_is_linear(forward_map, FIT_XGRID, data):
43+
def check_pdf_model_is_linear(forward_map, fast_kernel_arrays):
4644
"""
4745
Decorator that can be added to functions to check that the
4846
PDF model is linear.
47+
48+
Note that the FK arrays are taken as an argument rather than rebuilt here,
49+
so that they are guaranteed to be consistent with the
50+
``fill_fk_xgrid_with_zeros`` setting ``forward_map`` was built with.
4951
"""
5052

51-
fk = fast_kernel_arrays(data, FIT_XGRID)
53+
fk = fast_kernel_arrays
5254

5355
parameters = forward_map.param_names
5456
intercept, _ = forward_map(fk, jnp.zeros(len(parameters)))

colibri/commondata_utils.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,7 @@ def level_0_commondata_tuple(
113113
Subset of flavour (evolution basis) indices to be used.
114114
115115
fill_fk_xgrid_with_zeros: bool, default is False
116-
If True, then the missing xgrid points in the FK table
117-
will be filled with zeros. This is useful when the FK table
118-
is needed as tensor of shape (Ndat, Nfl, Nfk_xgrid) with Nfk_xgrid and Nfl fixed
119-
for all datasets.
120-
116+
Must match the value used to build ``fast_kernel_arrays``.
121117
122118
Returns
123119
-------

colibri/tests/test_analytic_fit.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import jax.random
1212
import pytest
1313

14-
from colibri.api import API as colibriAPI
1514
from colibri.analytic_fit import AnalyticFit, analytic_fit, run_analytic_fit
1615
from colibri.core import PriorSettings
1716
from colibri.forward_map import FKTableForwardMap
@@ -21,7 +20,6 @@
2120
TEST_FK_ARRAYS,
2221
TEST_PRIOR_SETTINGS_UNIFORM,
2322
TEST_XGRID,
24-
TEST_DATASETS,
2523
)
2624

2725
analytic_settings = {
@@ -30,8 +28,6 @@
3028
"n_posterior_samples": 10,
3129
}
3230

33-
TEST_DATA = colibriAPI.data(**TEST_DATASETS)
34-
3531

3632
def test_analytic_fit_flat_direction():
3733
"""
@@ -51,12 +47,9 @@ def test_analytic_fit_flat_direction():
5147
analytic_fit(
5248
MOCK_CENTRAL_COVMAT_INDEX,
5349
forward_map,
54-
MOCK_PDF_MODEL,
5550
analytic_settings,
5651
TEST_PRIOR_SETTINGS_UNIFORM,
57-
TEST_XGRID,
5852
TEST_FK_ARRAYS,
59-
TEST_DATA,
6053
)
6154

6255

@@ -80,12 +73,9 @@ def test_analytic_fit(caplog, monkeypatch):
8073
result = analytic_fit(
8174
MOCK_CENTRAL_COVMAT_INDEX,
8275
forward_map,
83-
MOCK_PDF_MODEL,
8476
analytic_settings,
8577
TEST_PRIOR_SETTINGS_UNIFORM,
86-
TEST_XGRID,
8778
TEST_FK_ARRAYS,
88-
TEST_DATA,
8979
)
9080

9181
assert isinstance(result, AnalyticFit)
@@ -103,12 +93,9 @@ def test_analytic_fit(caplog, monkeypatch):
10393
result_2 = analytic_fit(
10494
MOCK_CENTRAL_COVMAT_INDEX,
10595
forward_map,
106-
MOCK_PDF_MODEL,
10796
analytic_settings,
10897
TEST_PRIOR_SETTINGS_UNIFORM,
109-
TEST_XGRID,
11098
TEST_FK_ARRAYS,
111-
TEST_DATA,
11299
)
113100

114101
# Check that an error message was logged, because the prior was not wide enough
@@ -147,12 +134,9 @@ def test_analytic_fit_different_priors(caplog, monkeypatch):
147134
result = analytic_fit(
148135
MOCK_CENTRAL_COVMAT_INDEX,
149136
forward_map,
150-
MOCK_PDF_MODEL,
151137
analytic_settings,
152138
PRIOR_SETTINGS1,
153-
TEST_XGRID,
154139
TEST_FK_ARRAYS,
155-
TEST_DATA,
156140
)
157141

158142
assert isinstance(result, AnalyticFit)
@@ -174,12 +158,9 @@ def test_analytic_fit_different_priors(caplog, monkeypatch):
174158
result = analytic_fit(
175159
MOCK_CENTRAL_COVMAT_INDEX,
176160
forward_map,
177-
MOCK_PDF_MODEL,
178161
analytic_settings,
179162
PRIOR_SETTINGS2,
180-
TEST_XGRID,
181163
TEST_FK_ARRAYS,
182-
TEST_DATA,
183164
)
184165

185166

colibri/tests/test_checks.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,11 @@ def test_check_pdf_models_equal_false_param_names(
111111
check_pdf_models_equal.__wrapped__(prior_settings, forward_map, theoryid)
112112

113113

114-
@patch("colibri.checks.fast_kernel_arrays")
115-
def test_check_pdf_model_is_linear(mock_fast_kernel_arrays):
114+
def test_check_pdf_model_is_linear():
116115
# Create test data
117116
FIT_XGRID = jnp.array([1.0, 2.0, 3.0])
118-
data = jnp.array([0.1, 0.2, 0.3])
119117
fk = jnp.array([0.3, 0.1, 0.6])
120118

121-
mock_fast_kernel_arrays.return_value = fk
122-
123119
# Create a mock for the PDF model
124120
mock_pdf_model = MagicMock()
125121
mock_pdf_model.param_names = ["a", "b", "c"]
@@ -139,7 +135,7 @@ def pdf_linear_model(params):
139135
)
140136

141137
# Test for linear model (should not raise an exception)
142-
check_pdf_model_is_linear(forward_map_lin, FIT_XGRID, data)
138+
check_pdf_model_is_linear(forward_map_lin, fk)
143139

144140
# Now mock a non-linear model to ensure the ValueError is raised
145141
non_linear_model = FKTableForwardMap(
@@ -151,4 +147,4 @@ def pdf_linear_model(params):
151147

152148
# Ensure ValueError is raised for non-linear model
153149
with pytest.raises(ValueError):
154-
check_pdf_model_is_linear(non_linear_model, FIT_XGRID, data)
150+
check_pdf_model_is_linear(non_linear_model, fk)

colibri/tests/test_theory_predictions.py

Lines changed: 104 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@
99
from numpy.testing import assert_allclose
1010
from validphys.fkparser import load_fktable
1111

12+
import numpy as np
13+
14+
from validphys.api import API as vpAPI
15+
from validphys.convolution import central_predictions
16+
1217
from colibri.api import API as colibriAPI
1318
from colibri.tests.conftest import (
1419
CLOSURE_TEST_PDFSET,
1520
TEST_DATASET,
1621
TEST_DATASET_HAD,
1722
TEST_DATASETS,
23+
TEST_DATASETS_DIS_HAD,
1824
TEST_DATASETS_HAD,
1925
)
2026
from colibri.theory_predictions import (
@@ -30,26 +36,12 @@ def __init__(self, xgrid):
3036
self.xgrid = xgrid
3137

3238

33-
def test_fktable_xgrid_indices_fill_with_zeros():
34-
# Case where fill_fk_xgrid_with_zeros is True
35-
fktable = FKTableDataMock(xgrid=jnp.array([0.1, 0.2, 0.3]))
36-
FIT_XGRID = jnp.array([0.05, 0.1, 0.15, 0.2, 0.25, 0.3])
37-
38-
expected_indices = jnp.arange(
39-
len(FIT_XGRID)
40-
) # Should return indices for the entire FIT_XGRID
41-
result = fktable_xgrid_indices(fktable, FIT_XGRID, fill_fk_xgrid_with_zeros=True)
42-
43-
assert jnp.array_equal(result, expected_indices)
44-
45-
46-
def test_fktable_xgrid_indices_no_fill():
47-
# Case where fill_fk_xgrid_with_zeros is False
39+
def test_fktable_xgrid_indices():
4840
fktable = FKTableDataMock(xgrid=jnp.array([0.1, 0.2, 0.3]))
4941
FIT_XGRID = jnp.array([0.05, 0.1, 0.15, 0.2, 0.25, 0.3])
5042

5143
expected_indices = jnp.array([1, 3, 5]) # Indices where fk_xgrid matches FIT_XGRID
52-
result = fktable_xgrid_indices(fktable, FIT_XGRID, fill_fk_xgrid_with_zeros=False)
44+
result = fktable_xgrid_indices(fktable, FIT_XGRID)
5345

5446
assert jnp.array_equal(result, expected_indices)
5547

@@ -61,7 +53,7 @@ def test_fktable_xgrid_indices_with_tolerance():
6153

6254
# Due to tolerance, the indices should match as if they were the same
6355
expected_indices = jnp.array([1, 3, 5])
64-
result = fktable_xgrid_indices(fktable, FIT_XGRID, fill_fk_xgrid_with_zeros=False)
56+
result = fktable_xgrid_indices(fktable, FIT_XGRID)
6557

6658
assert jnp.array_equal(result, expected_indices)
6759

@@ -74,7 +66,7 @@ def test_fktable_xgrid_indices_no_matches():
7466
expected_indices = jnp.array(
7567
[]
7668
) # No matching indices, closest_indices returns empty array
77-
result = fktable_xgrid_indices(fktable, FIT_XGRID, fill_fk_xgrid_with_zeros=False)
69+
result = fktable_xgrid_indices(fktable, FIT_XGRID)
7870
assert jnp.array_equal(result, expected_indices)
7971

8072

@@ -122,6 +114,49 @@ def test_fast_kernel_arrays():
122114
assert jnp.any(fk_arrays_filled[0][0][:, :, non_zero_indices] != 0)
123115

124116

117+
def test_fast_kernel_arrays_hadronic_fill_with_zeros():
118+
"""
119+
Test that fast_kernel_arrays correctly fills the x-grid with zeros for hadronic FK tables.
120+
This is a regression test for the bug where the 4D hadronic array was assigned
121+
into a 3D zeros array.
122+
"""
123+
from colibri.utils import closest_indices
124+
from validphys.fkparser import load_fktable
125+
126+
dataset = colibriAPI.data(**TEST_DATASETS_HAD)
127+
ds = dataset.datasets[0]
128+
FIT_XGRID = colibriAPI.FIT_XGRID(**TEST_DATASETS_HAD)
129+
130+
# This should not raise an error (regression check)
131+
fk_arrays_filled = colibriAPI.fast_kernel_arrays(
132+
**{**TEST_DATASETS_HAD, "fill_fk_xgrid_with_zeros": True}
133+
)
134+
135+
fk_arr = fk_arrays_filled[0][0]
136+
137+
# Hadronic FK array should be 4D: (Ndat, Nfl, Nfit_x, Nfit_x)
138+
assert fk_arr.ndim == 4
139+
assert fk_arr.shape[2] == len(FIT_XGRID)
140+
assert fk_arr.shape[3] == len(FIT_XGRID)
141+
142+
# Check that non-zero values are placed at the correct x-grid positions
143+
fk_xgrid = load_fktable(ds.fkspecs[0]).xgrid
144+
non_zero_indices = closest_indices(FIT_XGRID, fk_xgrid, atol=1e-8)
145+
non_zero_indices = np.array(non_zero_indices)
146+
147+
# The non-zero block should contain non-zero values
148+
assert jnp.any(
149+
fk_arr[:, :, non_zero_indices[:, None], non_zero_indices[None, :]] != 0
150+
)
151+
152+
# Entries outside the non-zero block should be zero
153+
all_indices = np.arange(len(FIT_XGRID))
154+
zero_indices = np.setdiff1d(all_indices, non_zero_indices)
155+
if len(zero_indices) > 0:
156+
assert jnp.all(fk_arr[:, :, zero_indices, :] == 0)
157+
assert jnp.all(fk_arr[:, :, :, zero_indices] == 0)
158+
159+
125160
def test_make_dis_prediction():
126161
"""
127162
Test make_dis_prediction function gives the same results
@@ -198,3 +233,54 @@ def test_make_pred_data():
198233

199234
assert callable(eval_preds)
200235
assert pred_data.shape == (fk_arrs[0][0].shape[0],)
236+
237+
238+
def test_predictions_independent_of_fill_fk_xgrid_with_zeros():
239+
"""
240+
Regression test: ``fill_fk_xgrid_with_zeros`` is a memory/layout option and
241+
must not change the theory predictions.
242+
243+
Previously the prediction closures unconditionally indexed the FK array with
244+
``fktable_xgrid_indices``, which are indices into FIT_XGRID. That is only
245+
valid for zero-padded FK arrays; with ``fill_fk_xgrid_with_zeros=False`` the
246+
FK x-grid axis was silently re-shuffled and out-of-range indices were clamped
247+
by jax, giving wrong predictions.
248+
249+
Uses a mixed DIS + hadronic setup so that both convolution paths are covered
250+
and FIT_XGRID is a strict superset of at least one FK x-grid (otherwise the
251+
index mapping is the identity and the bug is invisible).
252+
"""
253+
data = colibriAPI.data(**TEST_DATASETS_DIS_HAD)
254+
FIT_XGRID = colibriAPI.FIT_XGRID(**TEST_DATASETS_DIS_HAD)
255+
256+
# Guard: the setup must actually exercise a non-trivial x-grid mapping.
257+
mappings = [
258+
fktable_xgrid_indices(load_fktable(ds.fkspecs[0]).with_cuts(ds.cuts), FIT_XGRID)
259+
for ds in data.datasets
260+
]
261+
assert any(
262+
not jnp.array_equal(idx, jnp.arange(len(idx))) for idx in mappings
263+
), "test setup is degenerate: every FK x-grid maps onto FIT_XGRID as the identity"
264+
265+
pdf_grid = colibriAPI.closure_test_central_pdf_grid(
266+
**{**CLOSURE_TEST_PDFSET, **TEST_DATASETS_DIS_HAD}
267+
)
268+
269+
preds = {}
270+
for fill in (False, True):
271+
inp = {**TEST_DATASETS_DIS_HAD, "fill_fk_xgrid_with_zeros": fill}
272+
eval_preds = colibriAPI.make_pred_data(**inp)
273+
preds[fill] = eval_preds(pdf_grid, colibriAPI.fast_kernel_arrays(**inp))
274+
275+
assert_allclose(preds[False], preds[True], rtol=1e-6)
276+
277+
# Cross-check both against validphys, which is the ground truth here.
278+
# NOTE: use central_predictions rather than dataset_inputs_results: the latter
279+
# convolves every replica of the PDF set (and makes LHAPDF load all 101 members)
280+
# only for its central value to be taken, which is ~200x slower here.
281+
pdf = vpAPI.pdf(pdf=CLOSURE_TEST_PDFSET["closure_test_pdf"])
282+
vp_preds = np.concatenate(
283+
[np.array(central_predictions(ds, pdf)).ravel() for ds in data.datasets]
284+
)
285+
assert_allclose(preds[False], vp_preds, rtol=1e-6)
286+
assert_allclose(preds[True], vp_preds, rtol=1e-6)

colibri/theory_penalties.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,13 @@ def make_penalty_posdataset(posdataset, FIT_XGRID, flavour_indices=None):
7979
8080
"""
8181

82+
# NOTE: positivity_fast_kernel_arrays never zero-pads the FK x-grid, so the
83+
# prediction closures must not mask the FK arrays onto the fit x-grid.
8284
pred_funcs = pred_funcs_from_dataset(
83-
posdataset, FIT_XGRID, flavour_indices, fill_fk_xgrid_with_zeros=False
85+
posdataset,
86+
FIT_XGRID,
87+
flavour_indices,
88+
fill_fk_xgrid_with_zeros=False,
8489
)
8590

8691
def pos_penalty(pdf, alpha, lambda_positivity, fk_dataset):

0 commit comments

Comments
 (0)