Source code for scippneutron.atoms

# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
"""Parameters for neutron interactions with atoms."""

from __future__ import annotations

import dataclasses
import importlib.resources
from functools import lru_cache
from typing import TextIO

import scipp as sc


[docs] def reference_wavelength() -> sc.Variable: """Return the reference wavelength for absorption cross-sections. Returns ------- : 1.7982 Å """ return sc.scalar(1.7982, unit='angstrom')
[docs] @dataclasses.dataclass(frozen=True, eq=False) class ScatteringParams: """Scattering parameters for neutrons with a specific element / isotope. Provides access to the scattering lengths and cross-sections of neutrons with a given element or isotope. Values have been retrieved at 2024-02-19T17:00:00Z from the list at https://www.ncnr.nist.gov/resources/n-lengths/list.html which is based on :cite:`sears:1992`. Values are ``None`` where the table does not provide values. The absorption cross-section applies to neutrons with a wavelength of 1.7982 Å. See :func:`reference_wavelength`. """ isotope: str """Element / isotope name.""" coherent_scattering_length_re: sc.Variable | None = None """Bound coherent scattering length (real part).""" coherent_scattering_length_im: sc.Variable | None = None """Bound coherent scattering length (imaginary part).""" incoherent_scattering_length_re: sc.Variable | None = None """Bound incoherent scattering length (real part).""" incoherent_scattering_length_im: sc.Variable | None = None """Bound incoherent scattering length (imaginary part).""" coherent_scattering_cross_section: sc.Variable | None = None """Bound coherent scattering cross-section.""" incoherent_scattering_cross_section: sc.Variable | None = None """Bound incoherent scattering cross-section.""" total_scattering_cross_section: sc.Variable | None = None """Total bound scattering cross-section.""" absorption_cross_section: sc.Variable | None = None """Absorption cross-section for λ = 1.7982 Å neutrons.""" def __eq__(self, other: object) -> bool | type(NotImplemented): if not isinstance(other, ScatteringParams): return NotImplemented return all( self.isotope == other.isotope if field.name == 'isotope' else _eq_or_identical(getattr(self, field.name), getattr(other, field.name)) for field in dataclasses.fields(self) )
[docs] @staticmethod @lru_cache def for_isotope(isotope: str) -> ScatteringParams: """Return the scattering parameters for the given element / isotope. Parameters ---------- isotope: Name of the element or isotope. For example, 'H', '3He', 'V', '50V'. Returns ------- : Neutron scattering parameters. """ with _open_scattering_parameters_file() as f: while line := f.readline(): name, rest = line.split(',', 1) if name == isotope: return _parse_line(isotope, rest) raise ValueError(f"No entry for element / isotope '{isotope}'")
def _open_scattering_parameters_file() -> TextIO: return ( importlib.resources.files('scippneutron.atoms') .joinpath('scattering_parameters.csv') .open('r') ) def _parse_line(isotope: str, line: str) -> ScatteringParams: line = line.rstrip().split(',') return ScatteringParams( isotope=isotope, coherent_scattering_length_re=_assemble_scalar(line[0], line[1], 'fm'), coherent_scattering_length_im=_assemble_scalar(line[2], line[3], 'fm'), incoherent_scattering_length_re=_assemble_scalar(line[4], line[5], 'fm'), incoherent_scattering_length_im=_assemble_scalar(line[6], line[7], 'fm'), coherent_scattering_cross_section=_assemble_scalar(line[8], line[9], 'barn'), incoherent_scattering_cross_section=_assemble_scalar( line[10], line[11], 'barn' ), total_scattering_cross_section=_assemble_scalar(line[12], line[13], 'barn'), absorption_cross_section=_assemble_scalar(line[14], line[15], 'barn'), ) def _assemble_scalar(value: str, std: str, unit: str) -> sc.Variable | None: if not value: return None value = float(value) variance = float(std) ** 2 if std else None return sc.scalar(value, variance=variance, unit=unit) def _eq_or_identical(a: sc.Variable | None, b: sc.Variable | None) -> bool: if a is None: return b is None return sc.identical(a, b)