Source code for ess.amor.utils
# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
import scipp as sc
from ..reflectometry.conversions import reflectometry_q
from ..reflectometry.types import (
BeamDivergenceLimits,
DetectorRotation,
QBins,
RunType,
SampleRotation,
SampleRun,
ThetaBins,
WavelengthBins,
)
from .geometry import Detector
[docs]
def theta_grid(
nu: DetectorRotation[RunType], mu: SampleRotation[RunType]
) -> ThetaBins[RunType]:
"""Special grid used to create intensity maps over
(theta, wavelength).
The grid avoids aliasing artifacts that occur if the
theta bins overlap the blade edges."""
# angular offset of two blades:
bladeAngle = 2.0 * sc.asin(0.5 * Detector.bladeZ / Detector.distance)
# associate an angle with each z-coordinate on one blade
blade_grid = sc.atan(
sc.arange("theta", 0, 33)
* Detector.dZ
/ (Detector.distance + sc.arange("theta", 0, 33) * Detector.dX)
)
# approximate angular step width on one blade
stepWidth = blade_grid[1] - blade_grid[0]
# shift "downwards" of the grid in order to define boundaries rather than centers
blade_grid = blade_grid - 0.2 * stepWidth
delta_grid = sc.array(
dims=["theta"],
values=[],
unit=blade_grid.unit,
dtype=blade_grid.dtype,
)
# loop over all blades but one:
for _ in range(Detector.nBlades.value - 1):
# append the actual blade's grid to the array of detector-local angles
delta_grid = sc.concat((delta_grid, blade_grid), "theta")
# shift the blade grid by the angular offset
blade_grid = blade_grid + bladeAngle
# remove all entries in the detector local grid which are above the
# expected next value (plus some space to avoid very thin bins)
delta_grid = delta_grid[delta_grid < blade_grid[0] - 0.5 * stepWidth]
# append the grid of the last blade.
delta_grid = sc.concat((delta_grid, blade_grid), "theta")
# add angular position of the detector
grid = (
nu.to(unit="rad")
- mu.to(unit="rad")
- sc.array(
dims=delta_grid.dims, values=delta_grid.values[::-1], unit=delta_grid.unit
).to(unit="rad")
+ 0.5 * Detector.nBlades * bladeAngle.to(unit="rad")
)
return grid
[docs]
def qgrid(
detector_rotation: DetectorRotation[SampleRun],
sample_rotation: SampleRotation[SampleRun],
wbins: WavelengthBins,
bdlims: BeamDivergenceLimits,
) -> QBins:
'''Generates a suitable Q-binnning from
the limits on wavelength and divergence angle.
The binning is a geometric grid starting from
the minimum achievable Q value or ``1e-3 A``, whichever is larger.
'''
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, 501))
providers = (theta_grid, qgrid)