Source code for ess.polarization.supermirror
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
from abc import ABC, abstractmethod
from dataclasses import dataclass
from io import BytesIO, StringIO
from pathlib import Path
from typing import Any, Generic
import sciline
import scipp as sc
from typing_extensions import Self
from .types import PlusMinus, PolarizingElement, TransmissionFunction
[docs]
class SupermirrorEfficiencyFunction(Generic[PolarizingElement], ABC):
"""Base class for supermirror efficiency functions"""
@abstractmethod
def __call__(self, *, wavelength: sc.Variable) -> sc.DataArray:
"""Return the efficiency of a supermirror for a given wavelength"""
[docs]
@dataclass
class SecondDegreePolynomialEfficiency(
SupermirrorEfficiencyFunction[PolarizingElement]
):
"""
Efficiency of a supermirror as a second-degree polynomial
The efficiency is given by a * wavelength^2 + b * wavelength + c
Parameters
----------
a:
Coefficient of the quadratic term, with unit of 1/angstrom^2
b:
Coefficient of the linear term, with unit of 1/angstrom
c:
Constant term, dimensionless
"""
a: sc.Variable
b: sc.Variable
c: sc.Variable
def __call__(self, *, wavelength: sc.Variable) -> sc.DataArray:
"""Return the efficiency of a supermirror for a given wavelength"""
return (
(self.a * wavelength**2).to(unit='', copy=False)
+ (self.b * wavelength).to(unit='', copy=False)
+ self.c.to(unit='', copy=False)
)
[docs]
@dataclass
class EfficiencyLookupTable(SupermirrorEfficiencyFunction[PolarizingElement]):
"""
Efficiency of a supermirror as a lookup table.
The names of the columns in the table has to be "wavelength", "efficiency".
Parameters
----------
table:
The lookup table.
"""
table: sc.DataArray
def __post_init__(self):
table = self.table if self.table.variances is None else sc.values(self.table)
self._lut = sc.lookup(table, 'wavelength')
def __call__(self, *, wavelength: sc.Variable) -> sc.DataArray:
"""Return the efficiency of a supermirror for a given wavelength"""
return sc.DataArray(self._lut(wavelength), coords={'wavelength': wavelength})
@classmethod
def from_file(
cls,
path: str | Path | StringIO | BytesIO,
wavelength_colname: str,
efficiency_colname: str,
wavelength_unit: sc.Unit | str = 'angstrom',
**kwargs: Any,
) -> Self:
ds = sc.io.load_csv(path, **kwargs)
wavelength = (
ds[wavelength_colname]
.rename_dims({ds[wavelength_colname].dim: 'wavelength'})
.data
)
wavelength.unit = wavelength_unit
efficiency = (
ds[efficiency_colname]
.rename_dims({ds[efficiency_colname].dim: 'wavelength'})
.data
)
return cls(sc.DataArray(efficiency, coords={'wavelength': wavelength}))
@dataclass
class SupermirrorTransmissionFunction(TransmissionFunction[PolarizingElement]):
"""Wavelength-dependent transmission of a supermirror"""
efficiency_function: SupermirrorEfficiencyFunction
def __call__(
self, *, wavelength: sc.Variable, plus_minus: PlusMinus
) -> sc.DataArray:
"""Return the transmission fraction for a given wavelength"""
efficiency = self.efficiency_function(wavelength=wavelength)
if plus_minus == 'plus':
return 0.5 * (1 + efficiency)
else:
return 0.5 * (1 - efficiency)
def apply(self, data: sc.DataArray, plus_minus: PlusMinus) -> sc.DataArray:
"""Apply the transmission function to a data array"""
return self(wavelength=data.coords['wavelength'], plus_minus=plus_minus)
def get_supermirror_transmission_function(
efficiency_function: SupermirrorEfficiencyFunction[PolarizingElement],
) -> TransmissionFunction[PolarizingElement]:
return SupermirrorTransmissionFunction[PolarizingElement](
efficiency_function=efficiency_function
)
[docs]
def SupermirrorWorkflow() -> sciline.Pipeline:
"""
Workflow for computing transmission functions for supermirror polarizing elements.
"""
return sciline.Pipeline((get_supermirror_transmission_function,))