@@ -157,6 +157,14 @@ def test__mge_model_from__default_sigma_list_is_bitwise_unchanged():
157157 `pytest.approx(rel=1e-8)` is deliberately NOT used: it only fails once the ladder
158158 has moved by a relative ~1e-7, which is already past the point where the
159159 identifier changes.
160+
161+ PORTABILITY TRAP -- the expected ladder is built element by element, exactly as
162+ `mge_model_from` builds it (`10 ** log10_sigma_list[i]`), and must stay that way.
163+ A vectorised `10 ** np.linspace(...)` is a DIFFERENT numpy code path: numpy does
164+ not guarantee its scalar and SIMD power loops agree bit for bit, and on AVX-512
165+ hardware they differ by 1 ULP, so writing the expectation vectorised makes this
166+ test pass on GitHub's runners and fail on an AVX-512 developer machine. Comparing
167+ like for like keeps the assertion exact without measuring the host's CPU.
160168 """
161169 for mask_radius , total_gaussians in [(3.0 , 20 ), (3.5 , 30 ), (7.5 , 10 ), (1.0 , 5 )]:
162170 model = ag .model_util .mge_model_from (
@@ -166,9 +174,11 @@ def test__mge_model_from__default_sigma_list_is_bitwise_unchanged():
166174 instance = model .instance_from_prior_medians ()
167175 sigma_list = [profile .sigma for profile in instance .profile_list ]
168176
169- assert sigma_list == list (
170- 10 ** np .linspace (- 4 , np .log10 (mask_radius ), total_gaussians )
171- )
177+ log10_sigma_list = np .linspace (- 4 , np .log10 (mask_radius ), total_gaussians )
178+
179+ assert sigma_list == [
180+ 10 ** log10_sigma_list [i ] for i in range (total_gaussians )
181+ ]
172182
173183
174184def test__mge_point_model_from__returns_basis_model_with_correct_gaussians ():
@@ -207,6 +217,12 @@ def test__mge_point_model_from__default_sigma_list_is_bitwise_unchanged():
207217 As for `mge_model_from`, the default `sigma_min=0.01` must reproduce the
208218 hardcoded `min_log10_sigma = -2.0` ladder that predates the argument EXACTLY,
209219 so the identifier of an existing point-source fit does not change.
220+
221+ The same portability trap applies here: build the expected ladder element by
222+ element, the way `mge_point_model_from` does, so both sides take numpy's scalar
223+ power path. A vectorised `10 ** np.linspace(...)` disagrees with it by 1 ULP on
224+ AVX-512 hardware -- see `test__mge_model_from__default_sigma_list_is_bitwise_unchanged`
225+ for the full reasoning, including why `pytest.approx(rel=1e-8)` is not the fix.
210226 """
211227 for pixel_scales , total_gaussians in [(0.1 , 10 ), (0.05 , 5 ), (0.2 , 3 ), (0.001 , 4 )]:
212228 model = ag .model_util .mge_point_model_from (
@@ -217,9 +233,11 @@ def test__mge_point_model_from__default_sigma_list_is_bitwise_unchanged():
217233
218234 max_sigma = max (2.0 * pixel_scales , 10 ** - 2.0 )
219235
220- assert sigma_list == list (
221- 10 ** np .linspace (- 2.0 , np .log10 (max_sigma ), total_gaussians )
222- )
236+ log10_sigma_list = np .linspace (- 2.0 , np .log10 (max_sigma ), total_gaussians )
237+
238+ assert sigma_list == [
239+ 10 ** log10_sigma_list [i ] for i in range (total_gaussians )
240+ ]
223241
224242
225243def test__mge_point_model_from__sigma_min_input_sets_smallest_gaussian ():
0 commit comments