From 07134edb5cf6304eb3e7f553920fc0220f765dd4 Mon Sep 17 00:00:00 2001 From: rozyczko Date: Wed, 29 Jul 2026 08:42:58 +0200 Subject: [PATCH 1/2] fixed pointwise treatment on file load --- CHANGELOG.md | 19 +++++++++++ src/easyreflectometry/project.py | 10 +++++- tests/summary/test_summary.py | 2 +- tests/test_project.py | 55 ++++++++++++++++++++++++++------ 4 files changed, 75 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ca4e2a..9919c472 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Unreleased +Restored the measured per-point resolution on data load (issue #368). + +- Loading data through `Project` (`load_new_experiment`, + `load_experiment_for_model_at_index`, + `load_all_experiments_from_file`) again sets a `Pointwise` resolution + function when the file carries per-point q-resolution (an sQz column + in `.ort` files, or a 4th column in text files). Since PR #293 the + loaders discarded this data and always applied a flat + `PercentageFwhm(5.0)` — a temporary workaround that never got + reverted. Fits of such data were smeared with 5% FWHM regardless of + what the instrument delivered and should be re-run. +- Files without q-resolution data keep the 5% FWHM default. The + pre-#293 fallback that built a `LinearSpline` from the *reflectivity* + error (`sqrt(ye)`) was not restored: a reflectivity uncertainty is not + a q-width, and that branch produced effectively zero smearing. +- Known limitation (pre-existing): the resolution function lives on the + model, so when several experiments share one model the last-loaded + dataset's resolution wins. + Fixed inconsistent interpretation of vector resolution functions between the refnx and refl1d engines (issue #367). diff --git a/src/easyreflectometry/project.py b/src/easyreflectometry/project.py index 5ae272a6..aa0eacf6 100644 --- a/src/easyreflectometry/project.py +++ b/src/easyreflectometry/project.py @@ -28,6 +28,7 @@ from easyreflectometry.model import Model from easyreflectometry.model import ModelCollection from easyreflectometry.model import PercentageFwhm +from easyreflectometry.model import Pointwise from easyreflectometry.sample import Layer from easyreflectometry.sample import Material from easyreflectometry.sample import MaterialCollection @@ -521,6 +522,10 @@ def _apply_resolution_function( ) -> None: """Set the resolution function on *model* based on variance data in *experiment*. + Uses the measured per-point q-resolution (``Pointwise``) when the + experiment carries q-variance data (``xe``, i.e. sQz²); otherwise + falls back to the default 5% FWHM percentage resolution. + Parameters ---------- experiment : DataSet1D @@ -528,7 +533,10 @@ def _apply_resolution_function( model : Model The model whose resolution function is set. """ - model.resolution_function = PercentageFwhm(5.0) + if experiment.xe is not None and np.any(experiment.xe): + model.resolution_function = Pointwise(q_data_points=[experiment.x, experiment.y, experiment.xe]) + else: + model.resolution_function = PercentageFwhm(5.0) @staticmethod def _auto_set_background(experiment: DataSet1D) -> None: diff --git a/tests/summary/test_summary.py b/tests/summary/test_summary.py index 5f135fb0..a636b504 100644 --- a/tests/summary/test_summary.py +++ b/tests/summary/test_summary.py @@ -136,7 +136,7 @@ def test_experiments_section(self, project: Project) -> None: assert 'No. of data points' in html assert '408' in html assert 'Resolution function' in html - assert 'PercentageFwhm' in html + assert 'Pointwise' in html def test_experiments_section_percentage_fhwm(self, project: Project) -> None: # When diff --git a/tests/test_project.py b/tests/test_project.py index ec85b3f2..62dd67d2 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -19,6 +19,7 @@ from easyreflectometry.model import Model from easyreflectometry.model import ModelCollection from easyreflectometry.model import PercentageFwhm +from easyreflectometry.model import Pointwise from easyreflectometry.project import Project from easyreflectometry.sample import Layer from easyreflectometry.sample import Material @@ -663,7 +664,8 @@ def test_load_experiment(self): assert isinstance(project.experiments[5], DataSet1D) assert project.experiments[5].name == 'Example data file from refnx docs' assert project.experiments[5].model == model_5 - assert isinstance(project.models[5].resolution_function, PercentageFwhm) + # example.ort carries an sQz column, so the measured resolution is used + assert isinstance(project.models[5].resolution_function, Pointwise) assert isinstance(project.models[4].resolution_function, PercentageFwhm) def test_load_experiment_sets_resolution_function_pointwise_when_xe_present(self, tmp_path): @@ -679,12 +681,13 @@ def test_load_experiment_sets_resolution_function_pointwise_when_xe_present(self # Then project.load_experiment_for_model_at_index(str(fpath)) - # Resolution is always set to PercentageFwhm - from easyreflectometry.model.resolution_functions import PercentageFwhm + # Expect Pointwise because xe (q-resolution) is present + resolution_function = project.models[0].resolution_function + assert isinstance(resolution_function, Pointwise) + # The 4th column is sQz (sigma); smearing() must return it unchanged + assert_allclose(resolution_function.smearing([0.01, 0.02]), [1e-4, 1e-4]) - assert isinstance(project.models[0].resolution_function, PercentageFwhm) - - def test_load_experiment_sets_linearspline_when_only_ye_present(self, tmp_path): + def test_load_experiment_keeps_percentage_fwhm_when_no_xe(self, tmp_path): # When global_object.map._clear() project = Project() @@ -697,11 +700,45 @@ def test_load_experiment_sets_linearspline_when_only_ye_present(self, tmp_path): # Then project.load_experiment_for_model_at_index(str(fpath)) - # Resolution is always set to PercentageFwhm - from easyreflectometry.model.resolution_functions import PercentageFwhm - + # No q-resolution data available, so the 5% FWHM default is kept assert isinstance(project.models[0].resolution_function, PercentageFwhm) + def test_apply_resolution_function_prefers_pointwise_falls_back_to_percentage(self): + # When + global_object.map._clear() + project = Project() + model = Model() + with_xe = DataSet1D(x=[0.01, 0.02], y=[1.0, 2.0], ye=[0.1, 0.1], xe=[1e-8, 4e-8]) + zero_xe = DataSet1D(x=[0.01, 0.02], y=[1.0, 2.0], ye=[0.1, 0.1], xe=[0.0, 0.0]) + no_xe = DataSet1D(x=[0.01, 0.02], y=[1.0, 2.0], ye=[0.1, 0.1]) + + # Then Expect + project._apply_resolution_function(with_xe, model) + assert isinstance(model.resolution_function, Pointwise) + # xe holds sQz variances; smearing() must return sigma = sqrt(xe) + assert_allclose(model.resolution_function.smearing([0.01, 0.02]), [1e-4, 2e-4]) + + project._apply_resolution_function(zero_xe, model) + assert isinstance(model.resolution_function, PercentageFwhm) + + project._apply_resolution_function(no_xe, model) + assert isinstance(model.resolution_function, PercentageFwhm) + + def test_load_all_experiments_from_file_sets_pointwise_when_sqz_present(self): + # When + global_object.map._clear() + project = Project() + project.models = ModelCollection(Model()) + fpath = os.path.join(PATH_STATIC, 'test_example2.ort') + + # Then + n_loaded = project.load_all_experiments_from_file(fpath) + + # Expect + assert n_loaded == 2 + assert list(project.experiments.keys()) == [0, 1] + assert isinstance(project.models[0].resolution_function, Pointwise) + def test_experimental_data_at_index(self): # When global_object.map._clear() From d2bf32a4db258d2ae9d280e331525431a3f16cd2 Mon Sep 17 00:00:00 2001 From: rozyczko Date: Wed, 29 Jul 2026 08:44:16 +0200 Subject: [PATCH 2/2] ruff --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9919c472..55aafcae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,10 @@ Restored the measured per-point resolution on data load (issue #368). `PercentageFwhm(5.0)` — a temporary workaround that never got reverted. Fits of such data were smeared with 5% FWHM regardless of what the instrument delivered and should be re-run. -- Files without q-resolution data keep the 5% FWHM default. The - pre-#293 fallback that built a `LinearSpline` from the *reflectivity* - error (`sqrt(ye)`) was not restored: a reflectivity uncertainty is not - a q-width, and that branch produced effectively zero smearing. +- Files without q-resolution data keep the 5% FWHM default. The pre-#293 + fallback that built a `LinearSpline` from the _reflectivity_ error + (`sqrt(ye)`) was not restored: a reflectivity uncertainty is not a + q-width, and that branch produced effectively zero smearing. - Known limitation (pre-existing): the resolution function lives on the model, so when several experiments share one model the last-loaded dataset's resolution wins.