From 2d60e60d9ad55c76c5c540ab0c749a973e233ea0 Mon Sep 17 00:00:00 2001 From: Johannes Kasimir Date: Mon, 23 Oct 2023 14:56:04 +0200 Subject: [PATCH] restructure the type system to avoid nested parameterized types --- docs/examples/amor.ipynb | 30 +++------ src/essreflectometry/amor/__init__.py | 18 ++++- src/essreflectometry/amor/beamline.py | 4 ++ src/essreflectometry/amor/calibrations.py | 11 +-- src/essreflectometry/amor/conversions.py | 4 +- src/essreflectometry/amor/load.py | 3 + src/essreflectometry/amor/normalize.py | 14 ++-- src/essreflectometry/amor/resolution.py | 67 +++++++++++++------ .../reflectometry/__init__.py | 8 +++ .../reflectometry/conversions.py | 55 ++++++++------- .../reflectometry/corrections.py | 30 ++++++--- src/essreflectometry/reflectometry/types.py | 29 ++++---- 12 files changed, 171 insertions(+), 102 deletions(-) diff --git a/docs/examples/amor.ipynb b/docs/examples/amor.ipynb index cb96917..da43879 100644 --- a/docs/examples/amor.ipynb +++ b/docs/examples/amor.ipynb @@ -6,31 +6,18 @@ "metadata": {}, "outputs": [], "source": [ - "import numpy as np\n", - "\n", "import scipp as sc\n", "import sciline\n", - "from essreflectometry.amor.load import load\n", - "from essreflectometry.amor.beamline import make_beamline\n", - "from essreflectometry.amor.conversions import specular_reflection\n", - "from essreflectometry.amor.resolution import compute_resolution\n", - "from essreflectometry.amor.normalize import normalize_by_supermirror\n", - "from essreflectometry.amor.calibrations import supermirror_calibration\n", - "from essreflectometry.reflectometry.corrections import footprint_correction, normalize_by_counts\n", - "from essreflectometry.reflectometry.conversions import providers\n", + "from essreflectometry.amor import providers as amor\n", + "from essreflectometry.reflectometry import providers as reflectometry\n", "from essreflectometry.reflectometry.types import (\n", - " ThetaBins, WavelengthBins, Sample, Reference, Sample, SampleRotation, Filename, Raw, BeamlineParams,\n", - " ThetaData, WavelengthData, HistogrammedByQ, QDataWithResolutions, QData, QBins, NormalizedIOverQ\n", + " Sample, Reference, Sample, SampleRotation, Filename,\n", + " ThetaData, QBins, NormalizedIOverQ, QStd\n", ")\n", "\n", - "\n", "pipeline = sciline.Pipeline(\n", - " [load, make_beamline, specular_reflection, footprint_correction,\n", - " compute_resolution, normalize_by_counts, supermirror_calibration, normalize_by_supermirror]\n", - " + providers,\n", + " amor + reflectometry,\n", " params={\n", - " ThetaBins: sc.linspace(dim='theta', start=0, stop=np.pi/2, num=2, unit='rad'),\n", - " WavelengthBins: sc.array(dims=['wavelength'], values=[2.4, 16.0], unit='angstrom'),\n", " QBins: sc.geomspace(dim='Q', start=0.008, stop=0.075, num=200, unit='1/angstrom'),\n", "\n", " SampleRotation[Sample]: sc.scalar(0.7989, unit='deg'),\n", @@ -40,7 +27,7 @@ " }\n", ")\n", "\n", - "pipeline.visualize(NormalizedIOverQ)" + "pipeline.visualize((NormalizedIOverQ, QStd))" ] }, { @@ -75,9 +62,8 @@ "metadata": {}, "outputs": [], "source": [ - "import plopp\n", - "\n", - "[]" + "res = pipeline.compute((NormalizedIOverQ, QStd))\n", + "res[QStd].plot()" ] } ], diff --git a/src/essreflectometry/amor/__init__.py b/src/essreflectometry/amor/__init__.py index a74676e..c8bd3e1 100644 --- a/src/essreflectometry/amor/__init__.py +++ b/src/essreflectometry/amor/__init__.py @@ -1,7 +1,19 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) # flake8: noqa: F401 -from . import calibrations, conversions, data, normalize, resolution, tools -from .beamline import instrument_view_components, make_beamline +from itertools import chain + +from . import calibrations, conversions, load, normalize, resolution, tools +from .beamline import instrument_view_components from .instrument_view import instrument_view -from .load import load + +providers = list( + chain( + load.providers, + calibrations.providers, + conversions.providers, + normalize.providers, + resolution.providers, + beamline.providers, + ) +) diff --git a/src/essreflectometry/amor/beamline.py b/src/essreflectometry/amor/beamline.py index 2027968..0969afa 100644 --- a/src/essreflectometry/amor/beamline.py +++ b/src/essreflectometry/amor/beamline.py @@ -14,6 +14,7 @@ def make_beamline( sample_rotation: SampleRotation[Run], ) -> BeamlineParams[Run]: + # TODO beam_size: sc.Variable = None sample_size: sc.Variable = None detector_spatial_resolution: sc.Variable = None @@ -132,3 +133,6 @@ def instrument_view_components(da: sc.DataArray) -> dict: 'type': 'disk', }, } + + +providers = [make_beamline] diff --git a/src/essreflectometry/amor/calibrations.py b/src/essreflectometry/amor/calibrations.py index 3d308b6..acbb57e 100644 --- a/src/essreflectometry/amor/calibrations.py +++ b/src/essreflectometry/amor/calibrations.py @@ -3,12 +3,12 @@ import scipp as sc # from ..reflectometry import orso -from ..reflectometry.types import CalibratedReference, HistogrammedByQ, QData, Reference +from ..reflectometry.types import CalibratedReference, Histogrammed, Reference def supermirror_calibration( - data_array: HistogrammedByQ[QData[Reference]], -) -> CalibratedReference: + data_array: Histogrammed[Reference], +) -> Histogrammed[CalibratedReference]: # TODO m_value: sc.Variable = None critical_edge: sc.Variable = None @@ -47,7 +47,7 @@ def supermirror_calibration( # ] # except KeyError: # orso.not_found_warning() - return CalibratedReference(data_array_cal) + return Histogrammed[CalibratedReference](data_array_cal) def calibration_factor( @@ -90,3 +90,6 @@ def calibration_factor( nq = 1.0 / (1.0 - alpha * (q - critical_edge)) calibration_factor = sc.where(q < max_q, lim + (1 - lim) * nq, sc.scalar(1.0)) return calibration_factor + + +providers = [supermirror_calibration] diff --git a/src/essreflectometry/amor/conversions.py b/src/essreflectometry/amor/conversions.py index 3a643a2..cf8e0e3 100644 --- a/src/essreflectometry/amor/conversions.py +++ b/src/essreflectometry/amor/conversions.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) -from typing import NewType import scipp as sc from ..reflectometry.conversions import specular_reflection as spec_relf_graph @@ -31,3 +30,6 @@ def specular_reflection() -> SpecularReflectionCoordTransformGraph: graph = spec_relf_graph() graph['incident_beam'] = incident_beam return SpecularReflectionCoordTransformGraph(graph) + + +providers = [specular_reflection] diff --git a/src/essreflectometry/amor/load.py b/src/essreflectometry/amor/load.py index 7479ada..23441ce 100644 --- a/src/essreflectometry/amor/load.py +++ b/src/essreflectometry/amor/load.py @@ -160,3 +160,6 @@ def populate_orso(orso: Any, data: sc.DataGroup, filename: str) -> Any: '%Y-%m-%d', ) orso.data_source.measurement.data_files = [filename] + + +providers = [load] diff --git a/src/essreflectometry/amor/normalize.py b/src/essreflectometry/amor/normalize.py index fbb626e..024498f 100644 --- a/src/essreflectometry/amor/normalize.py +++ b/src/essreflectometry/amor/normalize.py @@ -5,16 +5,15 @@ # from ..reflectometry import orso from ..reflectometry.types import ( CalibratedReference, - HistogrammedByQ, - NormalizedData, + Normalized, NormalizedIOverQ, - QDataWithResolutions, + Sample, ) def normalize_by_supermirror( - sample: NormalizedData[HistogrammedByQ[QDataWithResolutions]], - supermirror: NormalizedData[CalibratedReference], + sample: Normalized[Sample], + supermirror: Normalized[CalibratedReference], ) -> NormalizedIOverQ: """ Normalize the sample measurement by the (ideally calibrated) supermirror. @@ -50,4 +49,7 @@ def normalize_by_supermirror( # ].value.data_source.measurement.data_files # except KeyError: # orso.not_found_warning() - return normalized + return NormalizedIOverQ(normalized) + + +providers = [normalize_by_supermirror] diff --git a/src/essreflectometry/amor/resolution.py b/src/essreflectometry/amor/resolution.py index 31bfaf7..8654717 100644 --- a/src/essreflectometry/amor/resolution.py +++ b/src/essreflectometry/amor/resolution.py @@ -2,29 +2,49 @@ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) import scipp as sc -from ..reflectometry.types import QData, QDataWithResolutions, Sample +from ..reflectometry.types import ( + AngularResolution, + Histogrammed, + QBins, + QData, + QStd, + Sample, + SampleSizeResolution, + WavelengthResolution, +) from .tools import fwhm_to_std -def compute_resolution(da: QData[Sample]) -> QDataWithResolutions: - da.coords['wavelength_resolution'] = wavelength_resolution( - chopper_1_position=da.coords['source_chopper_1'].value['position'], - chopper_2_position=da.coords['source_chopper_2'].value['position'], - pixel_position=da.coords['position'], +def wavelength_resolution(da: QData[Sample]) -> WavelengthResolution: + return WavelengthResolution( + _wavelength_resolution( + chopper_1_position=da.coords['source_chopper_1'].value['position'], + chopper_2_position=da.coords['source_chopper_2'].value['position'], + pixel_position=da.coords['position'], + ) ) - da.coords['angular_resolution'] = angular_resolution( - pixel_position=da.coords['position'], - theta=da.bins.coords['theta'], - detector_spatial_resolution=da.coords['detector_spatial_resolution'], + + +def angular_resolution(da: QData[Sample]) -> AngularResolution: + return AngularResolution( + _angular_resolution( + pixel_position=da.coords['position'], + theta=da.bins.coords['theta'], + detector_spatial_resolution=da.coords['detector_spatial_resolution'], + ) ) - da.coords['sample_size_resolution'] = sample_size_resolution( - pixel_position=da.coords['position'], - sample_size=da.coords['sample_size'], + + +def sample_size_resolution(da: QData[Sample]) -> SampleSizeResolution: + return SampleSizeResolution( + _sample_size_resolution( + pixel_position=da.coords['position'], + sample_size=da.coords['sample_size'], + ) ) - return QDataWithResolutions(da) -def wavelength_resolution( +def _wavelength_resolution( chopper_1_position: sc.Variable, chopper_2_position: sc.Variable, pixel_position: sc.Variable, @@ -57,7 +77,7 @@ def wavelength_resolution( return fwhm_to_std(distance_between_choppers / chopper_detector_distance) -def sample_size_resolution( +def _sample_size_resolution( pixel_position: sc.Variable, sample_size: sc.Variable ) -> sc.Variable: """ @@ -83,7 +103,7 @@ def sample_size_resolution( ) -def angular_resolution( +def _angular_resolution( pixel_position: sc.Variable, theta: sc.Variable, detector_spatial_resolution: sc.Variable, @@ -123,11 +143,11 @@ def angular_resolution( def sigma_Q( - angular_resolution: sc.Variable, - wavelength_resolution: sc.Variable, - sample_size_resolution: sc.Variable, - q_bins: sc.Variable, -) -> sc.Variable: + angular_resolution: Histogrammed[AngularResolution], + wavelength_resolution: WavelengthResolution, + sample_size_resolution: SampleSizeResolution, + q_bins: QBins, +) -> QStd: """ Combine all of the components of the resolution and add Q contribution. @@ -152,3 +172,6 @@ def sigma_Q( + wavelength_resolution**2 + sample_size_resolution**2 ).max('detector_number') * sc.midpoints(q_bins) + + +providers = [sigma_Q, angular_resolution, wavelength_resolution, sample_size_resolution] diff --git a/src/essreflectometry/reflectometry/__init__.py b/src/essreflectometry/reflectometry/__init__.py index fcb6518..e5328ac 100644 --- a/src/essreflectometry/reflectometry/__init__.py +++ b/src/essreflectometry/reflectometry/__init__.py @@ -2,5 +2,13 @@ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) # flake8: noqa: F401 +from itertools import chain from . import conversions, corrections, io + +providers = list( + chain( + conversions.providers, + corrections.providers, + ) +) diff --git a/src/essreflectometry/reflectometry/conversions.py b/src/essreflectometry/reflectometry/conversions.py index acd58a3..68cc602 100644 --- a/src/essreflectometry/reflectometry/conversions.py +++ b/src/essreflectometry/reflectometry/conversions.py @@ -7,17 +7,15 @@ # from . import orso from .types import ( - CorrectedQData, + AngularResolution, FootprintCorrected, - HistogrammedByQ, + Histogrammed, QBins, QData, Raw, Run, SpecularReflectionCoordTransformGraph, - ThetaBins, ThetaData, - WavelengthBins, WavelengthData, ) @@ -120,7 +118,6 @@ def specular_reflection() -> SpecularReflectionCoordTransformGraph: def tof_to_wavelength( data_array: Raw[Run], - wavelength_edges: WavelengthBins, graph: SpecularReflectionCoordTransformGraph, ) -> WavelengthData[Run]: """ @@ -131,9 +128,6 @@ def tof_to_wavelength( ---------- data_array: Data array to convert. - wavelength_edges: - The lower and upper limits for the wavelength. - If :code:`None`, no binning is performed. graph: Graph for :code:`transform_coords`. @@ -143,7 +137,6 @@ def tof_to_wavelength( New data array with wavelength dimension. """ data_array_wav = data_array.transform_coords(["wavelength"], graph=graph) - data_array_wav = data_array_wav.bin({wavelength_edges.dim: wavelength_edges}) # TODO # try: # from orsopy import fileio @@ -166,7 +159,6 @@ def tof_to_wavelength( def wavelength_to_theta( data_array: WavelengthData[Run], - theta_edges: ThetaBins, graph: SpecularReflectionCoordTransformGraph, ) -> ThetaData[Run]: """ @@ -177,9 +169,6 @@ def wavelength_to_theta( ---------- data_array: Data array to convert. - theta_edges: - The lower and upper limits for the theta. If :code:`None`, no - binning is performed. graph: Graph for :code:`transform_coords`. @@ -189,7 +178,6 @@ def wavelength_to_theta( New data array with theta coordinate. """ data_array_theta = data_array.transform_coords(['theta'], graph=graph) - data_array_theta = data_array_theta.bin({theta_edges.dim: theta_edges}) # TODO # try: # from orsopy import fileio @@ -230,8 +218,7 @@ def theta_to_q( data_array: Data array to convert. q_edges: - The lower and upper limits for the Q. If :code:`None`, no - binning is performed. + The lower and upper limits for the Q. graph: Graph for :code:`transform_coords`. @@ -245,7 +232,7 @@ def theta_to_q( return QData[Run](data_array_q) -def sum_bins(data_array: CorrectedQData) -> HistogrammedByQ[CorrectedQData]: +def sum_bins(data_array: QData[Run]) -> Histogrammed[Run]: """ Sum the event bins and propagate the maximum resolution, where available. @@ -259,12 +246,32 @@ def sum_bins(data_array: CorrectedQData) -> HistogrammedByQ[CorrectedQData]: : Summed data array. """ - data_array_summed = data_array.bins.sum() - if 'angular_resolution' in data_array.bins.coords: - data_array_summed.coords['angular_resolution'] = data_array.bins.coords[ - 'angular_resolution' - ].max('detector_number') - return data_array_summed + return Histogrammed[Run](data_array.bins.sum()) -providers = [tof_to_wavelength, wavelength_to_theta, theta_to_q, sum_bins] +def aggregate_resolution_in_bins( + angular_resolution: AngularResolution, +) -> Histogrammed[AngularResolution]: + """ + Propagate the maximum resolution. + + Parameters + ---------- + angular_resolution: + Angular resolution for each Q. + + Returns + ------- + : + Max of angular resolution over detectors. + """ + return Histogrammed[AngularResolution](angular_resolution.max('detector_number')) + + +providers = [ + tof_to_wavelength, + wavelength_to_theta, + theta_to_q, + sum_bins, + aggregate_resolution_in_bins, +] diff --git a/src/essreflectometry/reflectometry/corrections.py b/src/essreflectometry/reflectometry/corrections.py index 22de22b..f539d9d 100644 --- a/src/essreflectometry/reflectometry/corrections.py +++ b/src/essreflectometry/reflectometry/corrections.py @@ -4,8 +4,16 @@ import scipp as sc from ..amor.tools import fwhm_to_std -from . import orso -from .types import FootprintCorrected, Normalizable, NormalizedData, Run, ThetaData + +# from . import orso +from .types import ( + CalibratedRun, + FootprintCorrected, + Histogrammed, + Normalized, + Run, + ThetaData, +) def footprint_correction(data_array: ThetaData[Run]) -> FootprintCorrected[Run]: @@ -39,7 +47,9 @@ def footprint_correction(data_array: ThetaData[Run]) -> FootprintCorrected[Run]: return FootprintCorrected[Run](data_array_fp_correction) -def normalize_by_counts(data_array: Normalizable) -> NormalizedData[Normalizable]: +def normalize_by_counts( + data_array: Histogrammed[CalibratedRun], +) -> Normalized[CalibratedRun]: """ Normalize the bin-summed data by the total number of counts. If the data has variances, a check is performed to ensure that the counts in each @@ -73,11 +83,12 @@ def normalize_by_counts(data_array: Normalizable) -> NormalizedData[Normalizable f'regime. The maximum counts found is {data_array.values[ind]} at ' f'index {ind}. The total number of counts is {ncounts.value}.' ) - try: - norm.attrs['orso'].value.reduction.corrections += ['total counts'] - except KeyError: - orso.not_found_warning() - return norm + # TODO + # try: + # norm.attrs['orso'].value.reduction.corrections += ['total counts'] + # except KeyError: + # orso.not_found_warning() + return Normalized[CalibratedRun](norm) def beam_on_sample(beam_size: sc.Variable, theta: sc.Variable) -> sc.Variable: @@ -97,3 +108,6 @@ def beam_on_sample(beam_size: sc.Variable, theta: sc.Variable) -> sc.Variable: Size of the beam on the sample. """ return beam_size / sc.sin(theta) + + +providers = [footprint_correction, normalize_by_counts] diff --git a/src/essreflectometry/reflectometry/types.py b/src/essreflectometry/reflectometry/types.py index b5ea793..799682b 100644 --- a/src/essreflectometry/reflectometry/types.py +++ b/src/essreflectometry/reflectometry/types.py @@ -28,15 +28,20 @@ class QData(sciline.Scope[Run, sc.DataArray], sc.DataArray): """Theta data transformed to momentum transfer""" +QStd = NewType('QStd', sc.Variable) + + class FootprintCorrected(sciline.Scope[Run, sc.DataArray], sc.DataArray): """Experiment data corrected by footprint on sample""" -CalibratedReference = NewType('CalibratedReference', sc.DataArray) -Resolutions = NewType('Resolutions', dict) -Normalized = NewType('Normalized', sc.DataArray) CountsByMomentumTransfer = NewType('CountsByMomentumTransfer', sc.DataArray) +WavelengthResolution = NewType('WavelengthResolution', sc.Variable) +AngularResolution = NewType('AngularResolution', sc.Variable) +SampleSizeResolution = NewType('SampleSizeResolution', sc.Variable) + + ''' Parameters for the workflow ''' QBins = NewType('QBins', sc.Variable) WavelengthBins = NewType('WavelengthBins', sc.Variable) @@ -55,21 +60,21 @@ class BeamlineParams(sciline.Scope[Run, dict], dict): 'SpecularReflectionCoordTransformGraph', dict ) -QDataWithResolutions = NewType('QDataWithResolutions', sc.DataArray) -CorrectedQData = TypeVar('CorrectedQData', QData[Reference], QDataWithResolutions) +CalibratedReference = NewType('CalibratedReference', sc.DataArray) +HistogramContent = TypeVar( + 'HistogramContent', Sample, Reference, CalibratedReference, AngularResolution +) -class HistogrammedByQ(sciline.Scope[CorrectedQData, sc.DataArray], sc.DataArray): - """Histogrammmed by Q. Either reference data or sample data with resolutions.""" +class Histogrammed(sciline.Scope[HistogramContent, sc.DataArray], sc.DataArray): + """Histogrammmed by Q and detector_number""" -Normalizable = TypeVar( - 'Normalizable', HistogrammedByQ[QDataWithResolutions], CalibratedReference -) +CalibratedRun = TypeVar('CalibratedRun', CalibratedReference, Sample) -class NormalizedData(sciline.Scope[Normalizable, sc.DataArray], sc.DataArray): - """Normalized histogramm by Q.""" +class Normalized(sciline.Scope[CalibratedRun, sc.DataArray], sc.DataArray): + """Normalized histogram""" NormalizedIOverQ = NewType('NormalizedIOverQ', sc.DataArray)