From c152011fd7456b78bc1ba5f4f84c105bb9cd340b Mon Sep 17 00:00:00 2001 From: Simon Heybrock Date: Wed, 4 Sep 2024 05:16:22 +0200 Subject: [PATCH] Update docstrings and cleanup --- src/ess/sans/normalization.py | 168 +++++++++++++++------------------- 1 file changed, 73 insertions(+), 95 deletions(-) diff --git a/src/ess/sans/normalization.py b/src/ess/sans/normalization.py index 3b8567e9..1bfd6ad9 100644 --- a/src/ess/sans/normalization.py +++ b/src/ess/sans/normalization.py @@ -176,21 +176,13 @@ def norm_monitor_term( transmission_fraction: TransmissionFraction[ScatteringRunType], ) -> MonitorTerm[ScatteringRunType]: """ - Compute the wavelength-dependent contribution to the denominator term for the I(Q) - normalization. - Keeping this as a separate function allows us to compute it once during the - iterations for finding the beam center, while the solid angle is recomputed - for each iteration. - - This is basically: - ``incident_monitor * transmission_fraction * direct_beam`` - If the direct beam is not supplied, it is assumed to be 1. + Compute the monitor-dependent contribution to the denominator term of I(Q). + + This is basically ``incident_monitor * transmission_fraction``. - Because the multiplication between the ``incident_monitor * transmission_fraction`` - (pixel-independent) and the direct beam (potentially pixel-dependent) consists of a - broadcast operation which would introduce correlations, variances of the direct - beam are dropped or replaced by an upper-bound estimation, depending on the - configured mode. + Keeping the monitor term separate from the detector term allows us to compute the + the latter only once when repeatedly processing chunks of events in streamed data + processing, e.g., for live data reduction. Parameters ---------- @@ -198,19 +190,11 @@ def norm_monitor_term( The incident monitor data (depends on wavelength). transmission_fraction: The transmission fraction (depends on wavelength). - direct_beam: - The direct beam function (depends on wavelength). - uncertainties: - The mode for broadcasting uncertainties. See - :py:class:`ess.reduce.uncertainty.UncertaintyBroadcastMode` for details. Returns ------- : - Wavelength-dependent term - (incident_monitor * transmission_fraction * direct_beam) to be used for - the denominator of the SANS I(Q) normalization. - Used by :py:func:`iofq_denominator`. + Monitor-dependent factor of the normalization term for the SANS I(Q). """ out = incident_monitor * transmission_fraction # Convert wavelength coordinate to midpoints for future histogramming @@ -222,79 +206,28 @@ def norm_detector_term( solid_angle: MaskedSolidAngle[ScatteringRunType], direct_beam: CleanDirectBeam, uncertainties: UncertaintyBroadcastMode, -) -> CleanWavelength[ScatteringRunType, Denominator]: - # Make wavelength the inner dim - dims = list(direct_beam.dims) - dims.remove('wavelength') - dims.append('wavelength') - direct_beam = direct_beam.transpose(dims) - out = solid_angle * broadcast_uncertainties( - direct_beam, prototype=solid_angle, mode=uncertainties - ) - # Convert wavelength coordinate to midpoints for future histogramming - out.coords['wavelength'] = sc.midpoints(out.coords['wavelength']) - return CleanWavelength[ScatteringRunType, Denominator](out) - - -def iofq_denominator( - wavelength_term: MonitorTerm[ScatteringRunType], - solid_angle: MaskedSolidAngle[ScatteringRunType], - uncertainties: UncertaintyBroadcastMode, ) -> CleanWavelength[ScatteringRunType, Denominator]: """ - Compute the denominator term for the I(Q) normalization. - - In a SANS experiment, the scattering cross section :math:`I(Q)` is defined as - (`Heenan et al. 1997 `_): - - .. math:: - - I(Q) = \\frac{\\partial\\Sigma{Q}}{\\partial\\Omega} = \\frac{A_{H} \\Sigma_{R,\\lambda\\subset Q} C(R, \\lambda)}{A_{M} t \\Sigma_{R,\\lambda\\subset Q}M(\\lambda)T(\\lambda)D(\\lambda)\\Omega(R)} - - where :math:`A_{H}` is the area of a mask (which avoids saturating the detector) - placed between the monitor of area :math:`A_{M}` and the main detector. - :math:`\\Omega` is the detector solid angle, and :math:`C` is the count rate on the - main detector, which depends on the position :math:`R` and the wavelength. - :math:`t` is the sample thickness, :math:`M` represents the incident monitor count - rate for the sample run, and :math:`T` is known as the transmission fraction. - - Note that the incident monitor used to compute the transmission fraction is not - necessarily the same as :math:`M`, as the transmission fraction is usually computed - from a separate 'transmission' run (in the 'sample' run, the transmission monitor is - commonly moved out of the beam path, to avoid polluting the sample detector signal). - - Finally, :math:`D` is the 'direct beam function', and is defined as + Compute the detector-dependent contribution to the denominator term of I(Q). - .. math:: - - D(\\lambda) = \\frac{\\eta(\\lambda)}{\\eta_{M}(\\lambda)} \\frac{A_{H}}{A_{M}} - - where :math:`\\eta` and :math:`\\eta_{M}` are the detector and monitor - efficiencies, respectively. - - Hence, in order to normalize the main detector counts :math:`C`, we need compute the - transmission fraction :math:`T(\\lambda)`, the direct beam function - :math:`D(\\lambda)` and the solid angle :math:`\\Omega(R)`. + This is basically ``solid_angle * direct_beam``. + If the direct beam is not supplied, it is assumed to be 1. - The denominator is then simply: - :math:`M_{\\lambda} T_{\\lambda} D_{\\lambda} \\Omega_{R}`, - which is equivalent to ``wavelength_term * solid_angle``. - The ``wavelength_term`` includes all but the ``solid_angle`` and is computed by - :py:func:`iofq_norm_wavelength_term_sample` or - :py:func:`iofq_norm_wavelength_term_background`. + Keeping the monitor term separate from the detector term allows us to compute the + the latter only once when repeatedly processing chunks of events in streamed data + processing, e.g., for live data reduction. - Because the multiplication between the wavelength dependent terms - and the pixel dependent term (solid angle) consists of a broadcast operation which - would introduce correlations, variances are dropped or replaced by an upper-bound - estimation, depending on the configured mode. + Because the multiplication between the pixel-dependent solid angle and the + (potentially) pixel-independent direct beam constitutes a broadcast operation which + would introduce correlations, variances of the direct beam are dropped or replaced + by an upper-bound estimation, depending on the configured mode. Parameters ---------- - wavelength_term: - The term that depends on wavelength, computed by - :py:func:`iofq_norm_wavelength_term`. solid_angle: The solid angle of the detector pixels, as viewed from the sample position. + direct_beam: + The direct beam function (depends on wavelength). uncertainties: The mode for broadcasting uncertainties. See :py:class:`ess.reduce.uncertainty.UncertaintyBroadcastMode` for details. @@ -302,12 +235,19 @@ def iofq_denominator( Returns ------- : - The denominator for the SANS I(Q) normalization. - """ # noqa: E501 - denominator = solid_angle * broadcast_uncertainties( - wavelength_term, prototype=solid_angle, mode=uncertainties + Detector-dependent factor of the normalization term for the SANS I(Q). + """ + # Make wavelength the inner dim + dims = list(direct_beam.dims) + dims.remove('wavelength') + dims.append('wavelength') + direct_beam = direct_beam.transpose(dims) + out = solid_angle * broadcast_uncertainties( + direct_beam, prototype=solid_angle, mode=uncertainties ) - return CleanWavelength[ScatteringRunType, Denominator](denominator) + # Convert wavelength coordinate to midpoints for future histogramming + out.coords['wavelength'] = sc.midpoints(out.coords['wavelength']) + return CleanWavelength[ScatteringRunType, Denominator](out) def process_wavelength_bands( @@ -353,10 +293,48 @@ def _normalize( uncertainties: UncertaintyBroadcastMode, ) -> sc.DataArray: """ - Perform normalization of counts as a function of Q. - If the numerator contains events, we use the sc.lookup function to perform the + Perform normalization of counts as a function of Q to obtain I(Q) or I(Qx, Qy). division. + In a SANS experiment, the scattering cross section :math:`I(Q)` is defined as + (`Heenan et al. 1997 `_): + + .. math:: + + I(Q) = \\frac{\\partial\\Sigma{Q}}{\\partial\\Omega} = \\frac{A_{H} \\Sigma_{R,\\lambda\\subset Q} C(R, \\lambda)}{A_{M} t \\Sigma_{R,\\lambda\\subset Q}M(\\lambda)T(\\lambda)D(\\lambda)\\Omega(R)} + + where :math:`A_{H}` is the area of a mask (which avoids saturating the detector) + placed between the monitor of area :math:`A_{M}` and the main detector. + :math:`\\Omega` is the detector solid angle, and :math:`C` is the count rate on the + main detector, which depends on the position :math:`R` and the wavelength. + :math:`t` is the sample thickness, :math:`M` represents the incident monitor count + rate for the sample run, and :math:`T` is known as the transmission fraction. + + Note that the incident monitor used to compute the transmission fraction is not + necessarily the same as :math:`M`, as the transmission fraction is usually computed + from a separate 'transmission' run (in the 'sample' run, the transmission monitor is + commonly moved out of the beam path, to avoid polluting the sample detector signal). + + Finally, :math:`D` is the 'direct beam function', and is defined as + + .. math:: + + D(\\lambda) = \\frac{\\eta(\\lambda)}{\\eta_{M}(\\lambda)} \\frac{A_{H}}{A_{M}} + + where :math:`\\eta` and :math:`\\eta_{M}` are the detector and monitor + efficiencies, respectively. + + Hence, in order to normalize the main detector counts :math:`C`, we need compute the + transmission fraction :math:`T(\\lambda)`, the direct beam function + :math:`D(\\lambda)` and the solid angle :math:`\\Omega(R)`. + + The denominator is then simply: + :math:`M_{\\lambda} T_{\\lambda} D_{\\lambda} \\Omega_{R}`, + which is equivalent to ``wavelength_term * solid_angle``. + The ``wavelength_term`` includes all but the ``solid_angle`` and is computed by + :py:func:`iofq_norm_wavelength_term_sample` or + :py:func:`iofq_norm_wavelength_term_background`. + Parameters ---------- numerator: @@ -374,8 +352,8 @@ def _normalize( Returns ------- : - The input data normalized by the supplied denominator. - """ + Normalized I(Q) or I(Qx, Qy). + """ # noqa: E501 if return_events and numerator.bins is not None: # Naive event-mode normalization is not correct if norm-term has variances. # See https://doi.org/10.3233/JNR-220049 for context.