"""Readers for dynamic data"""
import logging
import numpy as np
from MDMC.common.units import SYSTEM, Unit
from MDMC.readers.observables.obs_reader import SQwReader
logger = logging.getLogger(__name__)
eV_in_Joules = 1.602176634 * 10**(-19)
mole = 6.02214076 * 10**23
conversion_to_meV = {
'J' : 6.2415091e+21,
'kJ' : 6.2415091e+24,
'kcal' : 2.6114474e+25,
'cal' : 2.6114474e+22,
'kJ/mol' : 6.2415091e+24 / mole,
'kcal/mol' : 2.6114474e+25 / mole,
'J/mol' : 6.2415091e+21 / mole,
'cal/mol' : 2.6114474e+22 / mole,
'rad/ps' : 0.6582231395941951,
'rad/fs' : 0.6582231395941951 *1e3,
'rad/ns' : 0.6582231395941951 *1e-3,
'1/ps' : 0.6582231395941951 * 2*np.pi,
'1/fs' : 0.6582231395941951 *1e3 * 2*np.pi,
'1/ns' : 0.6582231395941951 *1e-3 * 2*np.pi,
'meV' : 1.0,
'eV' : 1e3,
'keV' : 1e6,
'ueV' : 1e-3,
}
[docs]
class MDANSESQw(SQwReader):
"""
A class for reading SQw files from MDANSE's trajectory analysis
The output from MDANSE analysis of trajectories is a .csv file with some lines
of comments describing the dataset and columns/rows, followed by an array
of numbers. The first row and column of the array define the axes of the data,
where the role and physical unit of each axis is described in the comment lines
preceding the data. The [0,0] element of the array is always 0.0 and is not used,
while all the remaining points are the S(Q,w) at each corresponding Q and w.
Attributes
----------
file_variables : ndarray
numpy array containing all the data
"""
def __init__(self, file_name: str):
super().__init__(file_name)
self.file_variables = None
self.first_row = 'Q'
self.first_column = 'E'
self.q_unit = None
self.e_unit = None
self.transpose_data = True
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 = np.loadtxt(self.file_name)
def __exit__(self, exception_type, exception_value, traceback) -> None:
"""Does nothing since numpy closes the file after reading anyway"""
# pylint: disable=unnecessary-pass
pass
[docs]
def parse(self, **settings: dict) -> None:
"""
Parse into SQw format, creates an error on SQw 1% of the value of SQw,
since MDANSE does not yet output an error. This should be changed once
it is possible to read an error.
E is the energy transfer (in meV)
Q is wavevector transfer (in Ang^-1)
"""
self.parse_header()
if self.first_row == 'Q' and self.first_column == 'E':
self.Q = self.file_variables[0, 1:] # Entry at [0,0] is always zero
self.E = self.file_variables[1:, 0]
elif self.first_row == 'E' and self.first_column == 'Q':
self.E = self.file_variables[0, 1:]
self.Q = self.file_variables[1:, 0]
self.Q *= self.q_unit.conversion_factor
self.E *= conversion_to_meV[self.e_unit]
if self.transpose_data:
self.SQw = self.file_variables[1:, 1:].T
else:
self.SQw = self.file_variables[1:, 1:]
self.SQw_err = self.SQw*0.01 # TODO: When MDANSE outputs an error, read it in
if np.any(self.SQw <= 0.):
self.SQw[np.where(self.SQw <= 0.)] = 0.0
# Change and zero errors into 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)