From c1bda00418e2cdc8f8cc6e83c439a3a439cc9d96 Mon Sep 17 00:00:00 2001 From: Johannes Kasimir Date: Wed, 30 Jul 2025 10:08:21 +0200 Subject: [PATCH 1/2] feat: add QBins provider --- docs/user-guide/amor/amor-reduction.ipynb | 8 ++-- src/ess/amor/utils.py | 37 ++++++++++++++++- tests/amor/utils_test.py | 48 +++++++++++++++++++++++ 3 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 tests/amor/utils_test.py diff --git a/docs/user-guide/amor/amor-reduction.ipynb b/docs/user-guide/amor/amor-reduction.ipynb index 2c9e82e7..ba018248 100644 --- a/docs/user-guide/amor/amor-reduction.ipynb +++ b/docs/user-guide/amor/amor-reduction.ipynb @@ -24,6 +24,7 @@ "metadata": {}, "outputs": [], "source": [ + "%matplotlib widget\n", "import warnings\n", "import scipp as sc\n", "from ess import amor\n", @@ -60,7 +61,6 @@ "workflow[ChopperPhase[ReferenceRun]] = sc.scalar(-7.5, unit='deg')\n", "workflow[ChopperPhase[SampleRun]] = sc.scalar(-7.5, unit='deg')\n", "\n", - "workflow[QBins] = sc.geomspace(dim='Q', start=0.005, stop=0.3, num=391, unit='1/angstrom')\n", "workflow[WavelengthBins] = sc.geomspace('wavelength', 2.8, 12.5, 2001, unit='angstrom')\n", "\n", "# The YIndexLimits and ZIndexLimits define ranges on the detector where\n", @@ -235,7 +235,7 @@ "outputs": [], "source": [ "from ess.reflectometry.tools import combine_curves\n", - "combined = combine_curves(scaled_reflectivity_curves, workflow.compute(QBins))\n", + "combined = combine_curves(scaled_reflectivity_curves, sc.geomspace('Q', 0.005, 0.4, 499, unit='1/angstrom'))\n", "combined.plot(norm='log')" ] }, @@ -336,7 +336,7 @@ "q_theta_figure(\n", " [res[ReflectivityOverZW] for res in diagnostics.values()],\n", " theta_bins=[res[ThetaBins[SampleRun]] for res in diagnostics.values()],\n", - " q_bins=workflow.compute(QBins)\n", + " q_bins=sc.geomspace('Q', 0.005, 0.4, 499, unit='1/angstrom')\n", ")" ] }, @@ -527,7 +527,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.18" } }, "nbformat": 4, diff --git a/src/ess/amor/utils.py b/src/ess/amor/utils.py index cdf273c1..0d1a9341 100644 --- a/src/ess/amor/utils.py +++ b/src/ess/amor/utils.py @@ -1,7 +1,17 @@ # Copyright (c) 2025 Scipp contributors (https://github.com/scipp) import scipp as sc -from ..reflectometry.types import DetectorRotation, RunType, SampleRotation, ThetaBins +from ..reflectometry.conversions import reflectometry_q +from ..reflectometry.types import ( + BeamDivergenceLimits, + DetectorRotation, + QBins, + RunType, + SampleRotation, + SampleRun, + ThetaBins, + WavelengthBins, +) from .geometry import Detector @@ -55,4 +65,27 @@ def theta_grid( return grid -providers = (theta_grid,) +def qgrid( + detector_rotation: DetectorRotation[SampleRun], + sample_rotation: SampleRotation[SampleRun], + wbins: WavelengthBins, + bdlims: BeamDivergenceLimits, +) -> QBins: + theta_min = ( + bdlims[0].to(unit='rad', copy=False) + + detector_rotation.to(unit='rad', dtype='float64') + - sample_rotation.to(unit='rad', dtype='float64') + ) + theta_max = ( + bdlims[-1].to(unit='rad', copy=False) + + detector_rotation.to(unit='rad', dtype='float64') + - sample_rotation.to(unit='rad', dtype='float64') + ) + wmin, wmax = wbins[0], wbins[-1] + qmin = reflectometry_q(wavelength=wmax, theta=theta_min) + qmax = reflectometry_q(wavelength=wmin, theta=theta_max) + qmin = max(qmin, sc.scalar(1e-3, unit='1/angstrom')) + return QBins(sc.geomspace('Q', qmin, qmax, 499)) + + +providers = (theta_grid, qgrid) diff --git a/tests/amor/utils_test.py b/tests/amor/utils_test.py new file mode 100644 index 00000000..11111ef3 --- /dev/null +++ b/tests/amor/utils_test.py @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2024 Scipp contributors (https://github.com/scipp) +# flake8: noqa: F403, F405 + +import scipp as sc +import scipp.constants +import scipp.testing + +from ess.amor.utils import qgrid + + +def test_qgrid_provider(): + grid = qgrid( + detector_rotation=sc.scalar(2, unit='deg'), + sample_rotation=sc.scalar(0.9, unit='deg'), + wbins=sc.linspace('wavelength', 3, 12, 10, unit='angstrom'), + bdlims=(sc.scalar(-0.75, unit='deg'), sc.scalar(0.75, unit='deg')), + ) + sc.testing.assert_allclose( + grid[0], + 4 + * sc.constants.pi + * sc.sin( + sc.scalar(-0.75, unit='deg').to(unit='rad') + + sc.scalar(1.1, unit='deg').to(unit='rad') + ) + / sc.scalar(12, unit='angstrom'), + ) + sc.testing.assert_allclose( + grid[-1], + 4 + * sc.constants.pi + * sc.sin( + sc.scalar(0.75, unit='deg').to(unit='rad') + + sc.scalar(1.1, unit='deg').to(unit='rad') + ) + / sc.scalar(3, unit='angstrom'), + ) + + +def test_qgrid_provider_minimum_q(): + grid = qgrid( + detector_rotation=sc.scalar(1.2, unit='deg'), + sample_rotation=sc.scalar(0.7, unit='deg'), + wbins=sc.linspace('wavelength', 3, 12, 10, unit='angstrom'), + bdlims=(sc.scalar(-0.75, unit='deg'), sc.scalar(0.75, unit='deg')), + ) + sc.testing.assert_allclose(grid[0], sc.scalar(0.001, unit='1/angstrom')) From 20c6fa9436dc4f1801cd344a174630ea5bf4ac8f Mon Sep 17 00:00:00 2001 From: Johannes Kasimir Date: Wed, 30 Jul 2025 10:09:42 +0200 Subject: [PATCH 2/2] docstring --- src/ess/amor/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ess/amor/utils.py b/src/ess/amor/utils.py index 0d1a9341..30944ebd 100644 --- a/src/ess/amor/utils.py +++ b/src/ess/amor/utils.py @@ -71,6 +71,8 @@ def qgrid( wbins: WavelengthBins, bdlims: BeamDivergenceLimits, ) -> QBins: + '''Generates a suitable Q-binnning from + the limits on wavelength and divergence angle.''' theta_min = ( bdlims[0].to(unit='rad', copy=False) + detector_rotation.to(unit='rad', dtype='float64')