Source code for MDMC.resolution.resolution_factory

"""A factory pattern for instantiating Resolution objects."""
# pylint: disable=too-few-public-methods
# pylint: disable=cyclic-import
# as this is a factory class

from inspect import getmembers, isclass, isabstract
from typing import Any, Union
import warnings

import MDMC.resolution

[docs] class ResolutionFactory: """ Factory class for resolution window functions. Any function in resolution_windows.py can be instantiated using this factory. """ resolutions = {} def __init__(self): self._load_resolutions() def _load_resolutions(self): resolutions = getmembers(MDMC.resolution, lambda m: isclass(m) and not isabstract(m)) for name, _type in resolutions: if isclass(_type) and issubclass(_type, MDMC.resolution.Resolution): self.resolutions.update([[name, _type]])
[docs] def create_instance(self, resolution: Union[dict, float, str], *args: Any) -> 'MDMC.resolution.Resolution': """ Create a Resolution object from a dictionary. Parameters ---------- resolution : dict, float, or str A parameter specifying the resolution. Should be a one-line dict giving the resolution type and parameters, e.g. a Lorentzian resolution with FWHM of 3 is specified {'lorentzian': 3.0}. If a float is given, resolution is assumed to be Gaussian with FWHM of that float. If a str is given, the string is assumed to be a file path to a vanadium run used to define a resolution. Returns ------- ~MDMC.resolution.Resolution A resolution object with the desired properties. """ resolution = _standardise_input(resolution) function_name = list(resolution.keys())[0].title() + 'Resolution' function_res = list(resolution.values())[0] if function_name in self.resolutions: # *args are only required by some resolution types, e.g. file resolution if function_name == 'FileResolution': return self.resolutions[function_name](function_res, *args) return self.resolutions[function_name](function_res) # else, error if unrecognised function is used # the userkeys line is to convert class names to the user equivalents userkeys = [] for key in self.resolutions: userkeys.append(key.lower().replace('resolution', '')) raise NotImplementedError("Resolution function " + list(resolution.keys())[0] + " not recognised. Recognised functions are: " + str(userkeys))
def _standardise_input(resolution: Any) -> dict: """ Ensure that resolution is a one-line dictionary. Fixes 'lazy' input, e.g. if resolution was input as a string or number. Parameters ---------- resolution: Any The input to the resolution factory. Returns ------- dict If ``resolution`` is a dict, returns the first item of the dict. If ``resolution`` was a float, returns ``{'gaussian': resolution}`` If ``resolution`` was a string, returns ``{'file': resolution}`` Raises ------ NotImplementedError If ``resolution`` is not a dict, string or float. Warns ----- SyntaxWarning If ``resolution`` is a dict with multiple lines, or a float. """ if isinstance(resolution, dict): if len(resolution) > 1: warnings.warn("The resolution dict should only have one line; ignoring" " all lines except the first." " Dictionaries are technically unordered, so" " this may cause unintended behaviour!", SyntaxWarning) res = {list(resolution.keys())[0]: list(resolution.values())[0]} else: res = resolution elif isinstance(resolution, str): res = {'file': resolution} elif isinstance(resolution, (float, int)): warnings.warn("Assuming energy resolution is Gaussian. To change this," " input energy resolution as {'function': 'value'}, where" " 'function' is your desired resolution approximation function.", SyntaxWarning) res = {'gaussian': float(resolution)} elif resolution is None: res = {'null': 0} else: raise NotImplementedError("Format of resolution function not recognised." " It should be a one-line dictionary, but can also" " be a number (for Gaussian resolution), a string" " (for resolution from file), or explicitly stated as" " None if resolution application is not needed.") return res