Source code for MDMC.readers.observables.MantidSQw

"""Readers for dynamic data"""

import logging
from contextlib import suppress
from typing import IO

import numpy as np

from MDMC.readers.observables.obs_reader import SQwReader

logger = logging.getLogger(__name__)

[docs] class MantidSQw(SQwReader): """ A class for reading SQw files from Mantid Mantid's ascii output uses one or two files, either: - A file containing the SQw data and error for the range of energy values measured at each Q with ``file_name`` or - A file containing the SQw data and error for the range of energy values measured at each detector (or group of detectors) ID with ``file_name`` and a file giving the momentum value associated with each detector (or group of detectors) ID, with the name given by ``file_name + '_detectors'`` If a single file is supplied, then it is assumed that the Q values are included in the data, this is the typical output of Mantid reduced ISIS data. An example reduction script is included in doc/tutorials/data/water_reduction_IRIS.py If there are two files then it is assumed that the second file links the detector ID's with the corresponding Q's Attributes ---------- ID_or_Q : file, optional File containing the ID's of the detectors, default=None file_detectors : file, optional File containing the errors on the dependent variables, default=None file_variables : file File containing the variables for each detector ID or Q """ def __init__(self, file_name: str): super().__init__(file_name) self.detector_ID_or_Q = None self.file_detectors = None self.file_variables = None def __enter__(self) -> None: """Open the files for variables and detector momenta""" # pylint: disable=consider-using-with # as this is an abstracted open method self.file_variables = open(self.file_name, encoding='UTF-8') try: self.file_detectors = open(self.file_name + '_detectors', encoding='UTF-8') except FileNotFoundError: self.file_detectors = None def __exit__(self, exception_type, exception_value, traceback) -> None: """Closes variable and detector files after parsing""" self.file_variables.close() with suppress(AttributeError): self.file_detectors.close()
[docs] def parse(self, **settings: dict) -> None: """ Parse into SQw format E is the energy transfer (in meV) Q is wavevector transfer (in Ang^-1) """ self.E, self.SQw, self.SQw_err = self.parse_variables( self.file_variables) if self.file_detectors is not None: self.Q = self.parse_detectors(self.file_detectors) else: self.Q = self._make_float(self.detector_ID_or_Q) # Explicitly sort data E_argsort = self.E.argsort() Q_argsort = self.Q.argsort() self.E = self.E[E_argsort] self.Q = self.Q[Q_argsort] self.SQw = self.SQw[Q_argsort, :] self.SQw = self.SQw[:, E_argsort] self.SQw_err = self.SQw_err[Q_argsort, :] self.SQw_err = self.SQw_err[:, E_argsort] # Mantid sets errors to 0 if the corresponding datum is 0. Change these to # inf so that error calculations can still be performed on them. if np.any(self.SQw_err <= 0.): self.SQw_err[np.where(self.SQw_err <= 0.)] = float('inf') msg = "We have set the error bar to infinity for any zero error values, this allows\ us to calculate chi-squared but effectively ignores these points, this may not\ be what you want to do, consider using a FoM which doesn't need errors if\ this is an issue" logger.warning(msg)
[docs] def parse_variables(self, file: IO) -> 'tuple[float]': """ Parses the values for energy, SQw and its error for each detector, or momentum value if it is defined instead of detector_ID Parameters ---------- file : file Open file containing the variables Returns ------- tuple (X, Y, E) where X is the independent variable (energy), Y is the dependent variable (SQw) and E is the errors of Y """ self.detector_ID_or_Q = [] data = [] for line in file: line = line.strip() # Skip any lines which are comments or headers if line[0] == '#': continue strings = line.split(',') if len(strings) == 1: self.detector_ID_or_Q.append(strings[0]) data.append({'X': [], 'Y': [], 'E': []}) else: data[-1]['X'].append(self._make_float(strings[0])) data[-1]['Y'].append(self._make_float(strings[1])) data[-1]['E'].append(self._make_float(strings[2])) X = np.array(data[0]['X']) Y = np.zeros((len(self.detector_ID_or_Q), len(X))) E = np.zeros((len(self.detector_ID_or_Q), len(X))) for i, datum in enumerate(data): # X data should be the same for each detector assert np.all(np.array(datum['X']) == X) Y[i] = np.array(datum['Y']) E[i] = np.array(datum['E']) return X, Y, E
[docs] def parse_detectors(self, file: IO) -> np.ndarray: """ Parses the detector momenta values. Parameters ---------- file : file Open file containing detector IDs and momenta Returns ------- numpy.ndarray A 1D array of momenta values """ Q = np.zeros(len(self.detector_ID_or_Q)) for i, line in enumerate(file): if i == 0: headings = line.split(', ') try: ID_header = 'Spectrum No' spectrum_index = headings.index(ID_header) except ValueError as error: raise ValueError(f'Detector file must have the heading "{ID_header}"') \ from error try: Q_header = 'Q' Q_index = headings.index(Q_header) except ValueError as error: raise ValueError(f'Detector file must have the heading "{Q_header}"') \ from error else: values = line.split() spectrum_no = values[spectrum_index] Q_value = values[Q_index] # Ensure that we assign Q values in the same order as detector_IDs Q[self.detector_ID_or_Q.index( spectrum_no)] = self._make_float(Q_value) return Q