diff --git a/Doc/docstrings_to_rst.py b/Doc/docstrings_to_rst.py index ffbff6d03e..399f6d4e11 100644 --- a/Doc/docstrings_to_rst.py +++ b/Doc/docstrings_to_rst.py @@ -1,6 +1,3 @@ - -import numpy as np - from MDANSE.Framework.Configurators.IConfigurator import IConfigurator from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Framework.Converters.Converter import Converter @@ -8,10 +5,9 @@ from MDANSE.MolecularDynamics.UnitCell import UnitCell -configurators = sorted(IConfigurator.indirect_subclasses()) -converters = sorted(Converter.indirect_subclasses()) -jobs = sorted(IJob.indirect_subclasses()) -generators = sorted(set(IQVectors.indirect_subclasses()) - {"IQVectors", "LatticeQVectors"}) +configurators = sorted(IConfigurator.available_names()) +converters = sorted(Converter.available_names()) +jobs = sorted(IJob.available_names()) job_inputs = {} converter_inputs = {} @@ -62,7 +58,7 @@ def make_configurator_doc(conf: str, parent: str) -> str: job_inputs[conf] = make_configurator_doc(conf, "analysis") result += f"- {iname}: :ref:`configurator-analysis-{conf}` default={defval}\n" job_page.append(result) - + for job in converters: result = "" diff --git a/MDANSE/Src/MDANSE/Core/SubclassFactory.py b/MDANSE/Src/MDANSE/Core/SubclassFactory.py index 328cb3b4e2..4a05fd21e3 100644 --- a/MDANSE/Src/MDANSE/Core/SubclassFactory.py +++ b/MDANSE/Src/MDANSE/Core/SubclassFactory.py @@ -16,9 +16,11 @@ from __future__ import annotations import difflib -from typing import TypeVar +from collections.abc import Callable, Iterable, Sequence +from typing import Any, ClassVar, Generic, ParamSpec, TypeVar, get_args + +from more_itertools import always_iterable -Self = TypeVar("Self", bound="SubclassFactory") # The Self TypeVar is a typing hint indicating that # a method of a class A will be returning an object # of type A as well. Since we don't know for which class @@ -27,177 +29,134 @@ # NOTE: the later versions of Python (3.11) define Self # as a type explicitly, but for now we have to define it # ourselves. +Self = TypeVar("Self", bound="RegisterFactory") +P = ParamSpec("P") -def single_search(parent_class: type, name: str, case_sensitive: bool = False): - """Finds a subclass of a parent class in the - by searching the _registered_subclasses dictionary. +class RegisterFactory: + """ + Factory requiring manual registration to data. - Arguments: - parent_class (type) -- a class with SubclassFactory metaclass - name (str) -- name of the child class to be found + Attributes + ---------- + registry : dict[str, Callable] + Dictionary of keys to names. - Returns: - A class (type) or None - """ - for skey in parent_class._registered_subclasses: - if case_sensitive: - lhand = skey - rhand = name - else: - lhand = str(skey).lower() - rhand = name.lower() - if lhand == rhand: - return parent_class._registered_subclasses[skey] - - return None - - -def recursive_search(parent_class: type, name: str): - """Recursively searches _registered_subclasses dictionaries, - allowing the parent class to find a subclass of a subclass as - well as direct subclasses. - - Arguments: - parent_class (type) -- a class with SubclassFactory metaclass - name (str) -- name of the child class to be found - - Returns: - A class (type) or None - """ - return_type = single_search(parent_class, name) - if return_type is not None: - return return_type - else: - for child in parent_class._registered_subclasses: - return_type = recursive_search( - parent_class._registered_subclasses[child], name - ) - if return_type is not None: - return return_type - - -def recursive_keys(parent_class: type) -> list: - """Returns a list of class names of all the subclasses - of a class created with SubclassFactory metaclass. - This includes subclasses of subclasses. - - Arguments: - parent_class (type) -- a class with SubclassFactory metaclass - - Returns: - A list of class names (str) - """ - try: - results = parent_class.subclasses() - except Exception: - return [] - else: - for child in parent_class.subclasses(): - results += recursive_keys(parent_class._registered_subclasses[child]) - return results - - -def recursive_dict(parent_class: type) -> dict: - """Returns a dictionary of {str: type} - of classes derived from the parent_class. The class name (str) - is the key, and the class itself is a value. - This way all the subclasses of a class built with SubclassFactory - can be found, even if they are not _directly_ derived from - the parent class. - - Arguments: - parent_class (type) -- a class with SubclassFactory metaclass - - Returns: - A dictionary {str: type} of class_name:class pairs - """ - try: - results = { - ckey: parent_class._registered_subclasses[ckey] - for ckey in parent_class.subclasses() - } - except Exception: - return {} - else: - for child in parent_class.subclasses(): - newdict = recursive_dict(parent_class._registered_subclasses[child]) - results = {**results, **newdict} - return results - - -class SubclassFactory(type): - """A metaclass which gives a class the ability to keep track of - its subclasses, and to work as a factory. + See Also + -------- + RegisterFactory.register : Registration mechanism. """ - def __init__(cls, name, base, dct, **kwargs): - super().__init__(name, base, dct) - # Add the registry attribute to the each new child class. - cls._registered_subclasses = {} - - @classmethod - def __init_subclass__(cls, **kwargs): - regkey = cls.__name__ - super().__init_subclass__(**kwargs) - cls._registered_subclasses[regkey] = cls - - # Assign the nested classmethod to the "__init_subclass__" attribute - # of each child class. - # It isn't needed in the terminal children too. - # May be there is a way to avoid adding these needless attributes - # (registry, __init_subclass__) to there. I don't think about it yet. - cls.__init_subclass__ = __init_subclass__ - - def create(cls, name: str, *args, **kwargs) -> Self: - """Finds the class called 'name' in the _registered_subclasses - dictionary of the parent class, and returns an instance - of that class with the *args, **kwargs passed to the constructor. - - Arguments: - name (str) -- Name of the subclass to be created - *args -- arguments for the subclass constructor - **kwargs -- keyword arguments for the subclass constructor - - Returns: - Self-type object - an instance of the requested class. + @classmethod + def instance(cls, key: str) -> Callable[P, type[Self]]: + """ + Return a callable instance to construct given class. + """ + return cls.registry[key] + + @classmethod + def create(cls, key: str, *args: P.args, **kwargs: P.kwargs) -> type[Self]: """ - try: - specific_class = cls._registered_subclasses[name] - except KeyError: - specific_class = recursive_search(cls, name) - if specific_class is None: - subclasses = [i.lower() for i in cls.indirect_subclasses()] - closest = difflib.get_close_matches(name.lower(), subclasses) - err_str = f"Could not find {name} in {cls.__name__}." - if len(closest) > 0: - err_str += f" Did you mean: {closest[0]}?" - raise ValueError(err_str) - return specific_class(*args, **kwargs) - - def subclasses(cls): - """Returns a list of class names that are derived - from this class. - - Returns: - list(str) -- a list of the subclasses of this class + Return an instance of given class. """ - return list(cls._registered_subclasses.keys()) + return cls.instance(key)(*args, **kwargs) - def indirect_subclasses(cls): - """Returns an extended list of class names that are derived - from this class, including subclasses of subclasses + @classmethod + def available_names(cls) -> Sequence[str]: + """ + Known names supported by factory. + + Returns + ------- + ~collections.abc.Sequence[str] + Available keys to load. + """ + return cls.registry.keys() + + @classmethod + def raw_dict(cls) -> dict[str, type[Self]]: + """ + Get raw name dictionary. + + Returns + ------- + ~collections.abc.Sequence[str] + Available keys to load. + + Notes + ----- + Only available on cases where registry is UCDict. + """ + if not hasattr(cls.registry, "raw_dict"): + raise TypeError("No raw names available for class") + return cls.registry.raw_dict - Returns: - list(str) -- a list of the subclasses of this class + @classmethod + def raw_names(cls) -> Sequence[str]: """ - return recursive_keys(cls) + Get raw names of classes. - def indirect_subclass_dictionary(cls): - """Returns a {name(str): class(type)} dictionary of classes derived - from this class, including subclasses of subclasses. + Returns + ------- + ~collections.abc.Sequence[str] + Available keys to load. - Returns: - dict(str:type) -- a dictionary of the subclasses of this class + Notes + ----- + Only available on cases where registry is UCDict. """ - return recursive_dict(cls) + if not hasattr(cls.registry, "raw_mapping"): + raise TypeError("No raw names available for class") + return cls.registry.raw_mapping.values() + + @classmethod + def available_classes(cls) -> set[type[Self]]: + """ + Known classes supported by factory. + + Returns + ------- + set[type[Self]] + Available classes to load. + """ + return set(cls.registry.values()) + + @classmethod + def registered(cls): + return cls.registry.copy() + + @classmethod + def register(cls, names: str | Iterable[str]) -> Callable[P, type[Self]]: + """ + A class level decorator for registering classes. + + The names of the modules with which the class is registered + should be the parameter passed to the decorator. + + Parameters + ---------- + names : str + The names of the modules with are registered + + Example + ------- + To register the ``SphericalQVectors`` class with ``IQVectors``: + + .. code-block:: python + + class IQVectors(RegisterFactory[IQVectors]): ... + + @IQVectors.register('SphericalQVectors') + class SphericalQVectors(Observable): ... + """ + + def class_wrapper(wrapped_class: type) -> Callable[P, type[Self]]: + for name in always_iterable(names): + if name in cls.registry: + raise KeyError( + f"{name!r} already in registry. Over-riding registry is forbidden." + ) + cls.registry[name] = wrapped_class + return wrapped_class + + return class_wrapper diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/AseInputFileConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/AseInputFileConfigurator.py index b2137472fb..e9d7ce0f53 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/AseInputFileConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/AseInputFileConfigurator.py @@ -18,10 +18,12 @@ from ase.io.formats import all_formats from MDANSE import PLATFORM +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator from MDANSE.Framework.Configurators.InputFileConfigurator import InputFileConfigurator from MDANSE.MLogging import LOG +@IConfigurator.register("AseInputFileConfigurator") class AseInputFileConfigurator(InputFileConfigurator): """Sets an input file for the ASE-based converters.""" diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/AtomMappingConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/AtomMappingConfigurator.py index 26422ed5c7..0d66aeaa61 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/AtomMappingConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/AtomMappingConfigurator.py @@ -21,6 +21,7 @@ from MDANSE.Framework.Configurators.IConfigurator import IConfigurator +@IConfigurator.register("AtomMappingConfigurator") class AtomMappingConfigurator(IConfigurator): """The atom mapping configurator for trajectory converters. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/AtomSelectionConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/AtomSelectionConfigurator.py index 90ba934d56..22e1b5a3d1 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/AtomSelectionConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/AtomSelectionConfigurator.py @@ -21,6 +21,7 @@ from MDANSE.Framework.Configurators.IConfigurator import IConfigurator +@IConfigurator.register("AtomSelectionConfigurator") class AtomSelectionConfigurator(IConfigurator): """Selects atoms in trajectory based on the input string. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/AtomTransmutationConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/AtomTransmutationConfigurator.py index 27350f9179..bc94aba1db 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/AtomTransmutationConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/AtomTransmutationConfigurator.py @@ -90,6 +90,7 @@ def reset_setting(self) -> None: self.selector.reset() +@IConfigurator.register("AtomTransmutationConfigurator") class AtomTransmutationConfigurator(IConfigurator): """Assigns different chemical elements to selected atoms. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/AxisSelectionConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/AxisSelectionConfigurator.py index a564cb343d..24035ac2f3 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/AxisSelectionConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/AxisSelectionConfigurator.py @@ -15,11 +15,13 @@ # from __future__ import annotations +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator from MDANSE.Framework.Configurators.MoleculeSelectionConfigurator import ( MoleculeSelectionConfigurator, ) +@IConfigurator.register("AxisSelectionConfigurator") class AxisSelectionConfigurator(MoleculeSelectionConfigurator): """Defines a local axis in a molecule. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/BooleanConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/BooleanConfigurator.py index 596ddbea5b..8a53b72b7d 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/BooleanConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/BooleanConfigurator.py @@ -18,6 +18,7 @@ from MDANSE.Framework.Configurators.IConfigurator import IConfigurator +@IConfigurator.register("BooleanConfigurator") class BooleanConfigurator(IConfigurator): """Sets a value to a logical True or False. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/CorrelationFramesConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/CorrelationFramesConfigurator.py index 95455707e6..cb5f6ee94c 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/CorrelationFramesConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/CorrelationFramesConfigurator.py @@ -17,10 +17,13 @@ import math +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator + from .FramesConfigurator import FramesConfigurator from .IConfigurator import PredictionSettings +@IConfigurator.register("CorrelationFramesConfigurator") class CorrelationFramesConfigurator(FramesConfigurator): """Parses the input of trajectory frames. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/DerivativeOrderConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/DerivativeOrderConfigurator.py index ab3efd7f5b..61a1cb0462 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/DerivativeOrderConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/DerivativeOrderConfigurator.py @@ -15,9 +15,11 @@ # from __future__ import annotations +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator from MDANSE.Framework.Configurators.IntegerConfigurator import IntegerConfigurator +@IConfigurator.register("DerivativeOrderConfigurator") class DerivativeOrderConfigurator(IntegerConfigurator): """Specifies the order of a numerical derivative. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/DistHistCutoffConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/DistHistCutoffConfigurator.py index e1355029b8..963f3ec92c 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/DistHistCutoffConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/DistHistCutoffConfigurator.py @@ -4,10 +4,13 @@ import numpy as np +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator + from .IConfigurator import PredictionSettings from .RangeConfigurator import RangeConfigurator +@IConfigurator.register("DistHistCutoffConfigurator") class DistHistCutoffConfigurator(RangeConfigurator): """Range of interatomic distances for a histogram. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/FileWithAtomDataConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/FileWithAtomDataConfigurator.py index 37f6cca5cb..a60fdc292c 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/FileWithAtomDataConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/FileWithAtomDataConfigurator.py @@ -20,11 +20,13 @@ from typing import Any from MDANSE.Framework.AtomMapping import AtomLabel +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator from MDANSE.Framework.Parsers import Parser from .InputFileConfigurator import InputFileConfigurator +@IConfigurator.register("FileWithAtomDataConfigurator") class FileWithAtomDataConfigurator(InputFileConfigurator): """ Class for handling files that contain atom information. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/FloatConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/FloatConfigurator.py index 45d331b8f7..6d99eba247 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/FloatConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/FloatConfigurator.py @@ -18,6 +18,7 @@ from MDANSE.Framework.Configurators.IConfigurator import IConfigurator +@IConfigurator.register("FloatConfigurator") class FloatConfigurator(IConfigurator): """Inputs a single floating-point number.""" diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/FramesConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/FramesConfigurator.py index b0832f0f97..5535a56ee7 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/FramesConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/FramesConfigurator.py @@ -15,10 +15,13 @@ # from __future__ import annotations +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator + from .IConfigurator import PredictionSettings from .RangeConfigurator import RangeConfigurator +@IConfigurator.register("FramesConfigurator") class FramesConfigurator(RangeConfigurator): """Select the trajectory frames on which to run the analysis. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/GridStepConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/GridStepConfigurator.py index 7ce06323fd..3df28b920b 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/GridStepConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/GridStepConfigurator.py @@ -16,11 +16,13 @@ import numpy as np from MDANSE.Framework.Configurators.FloatConfigurator import FloatConfigurator +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator from MDANSE.MolecularDynamics.Trajectory import Trajectory from .IConfigurator import PredictionSettings +@IConfigurator.register("GridStepConfigurator") class GridStepConfigurator(FloatConfigurator): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/GroupingLevelConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/GroupingLevelConfigurator.py index 1bbce83ac4..7e2e34471f 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/GroupingLevelConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/GroupingLevelConfigurator.py @@ -15,12 +15,14 @@ # from __future__ import annotations +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator from MDANSE.Framework.Configurators.SingleChoiceConfigurator import ( SingleChoiceConfigurator, ) from MDANSE.MolecularDynamics.Trajectory import GroupingLevels +@IConfigurator.register("GroupingLevelConfigurator") class GroupingLevelConfigurator(SingleChoiceConfigurator): """Define how the partial results will be grouped in the output. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/HDFInputFileConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/HDFInputFileConfigurator.py index e156f251c2..228a79e91d 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/HDFInputFileConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/HDFInputFileConfigurator.py @@ -17,9 +17,11 @@ import h5py +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator from MDANSE.Framework.Configurators.InputFileConfigurator import InputFileConfigurator +@IConfigurator.register("HDFInputFileConfigurator") class HDFInputFileConfigurator(InputFileConfigurator): """Uses an .mda file from another analysis as input.""" diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/HDFTrajectoryConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/HDFTrajectoryConfigurator.py index ca49ed3bc0..0f4755374e 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/HDFTrajectoryConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/HDFTrajectoryConfigurator.py @@ -16,10 +16,12 @@ from __future__ import annotations from MDANSE import PLATFORM +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator from MDANSE.Framework.Configurators.InputFileConfigurator import InputFileConfigurator from MDANSE.MolecularDynamics.Trajectory import Trajectory +@IConfigurator.register("HDFTrajectoryConfigurator") class HDFTrajectoryConfigurator(InputFileConfigurator): """Chooses the trajectory to be analysed. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/IConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/IConfigurator.py index 650296b495..ce85c312d3 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/IConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/IConfigurator.py @@ -18,14 +18,14 @@ import abc import json from collections.abc import Iterator, Sequence -from typing import TYPE_CHECKING, Any, NamedTuple +from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple from warnings import warn from more_itertools import value_chain from MDANSE.Core.Error import Error -from MDANSE.Core.SubclassFactory import SubclassFactory -from MDANSE.IO.IOUtils import MDANSEEncoder +from MDANSE.Core.SubclassFactory import RegisterFactory +from MDANSE.IO.IOUtils import MDANSEEncoder, UCDict from MDANSE.MLogging import LOG if TYPE_CHECKING: @@ -91,7 +91,7 @@ class PredictionSettings(NamedTuple): unit: str = "none" -class IConfigurator(dict, metaclass=SubclassFactory): +class IConfigurator(dict, RegisterFactory, abc.ABC): """The parent class for all the input parameter parsers. This class implements the base class for configurator objects. @@ -110,6 +110,8 @@ class IConfigurator(dict, metaclass=SubclassFactory): configuration. """ + registry: ClassVar[UCDict[str, IConfigurator]] = UCDict() + _default = None _encoder = MDANSEEncoder() diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/InputFileConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/InputFileConfigurator.py index 2350934d1c..17f70172b9 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/InputFileConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/InputFileConfigurator.py @@ -20,6 +20,7 @@ from MDANSE.MolecularDynamics.Trajectory import Trajectory +@IConfigurator.register("InputFileConfigurator") class InputFileConfigurator(IConfigurator): """Uses a file as input. Very general.""" diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/InstrumentResolutionConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/InstrumentResolutionConfigurator.py index 3322792c6b..1997e6bcd6 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/InstrumentResolutionConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/InstrumentResolutionConfigurator.py @@ -25,6 +25,7 @@ from .IConfigurator import PredictionSettings +@IConfigurator.register("InstrumentResolutionConfigurator") class InstrumentResolutionConfigurator(IConfigurator): r"""Defines the resolution function to use for signal broadening. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/IntegerConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/IntegerConfigurator.py index 9324b27bea..c75a340298 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/IntegerConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/IntegerConfigurator.py @@ -20,6 +20,7 @@ ) +@IConfigurator.register("IntegerConfigurator") class IntegerConfigurator(IConfigurator): """Inputs a single integer number.""" diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/InterpolationOrderConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/InterpolationOrderConfigurator.py index f6941906eb..7ce7621ee8 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/InterpolationOrderConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/InterpolationOrderConfigurator.py @@ -15,9 +15,11 @@ # from __future__ import annotations +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator from MDANSE.Framework.Configurators.IntegerConfigurator import IntegerConfigurator +@IConfigurator.register("InterpolationOrderConfigurator") class InterpolationOrderConfigurator(IntegerConfigurator): """Specifies the order of a numerical derivative used for interpolation. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/MDAnalysisCoordinateFileConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/MDAnalysisCoordinateFileConfigurator.py index e01aa93efd..cc27513b91 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/MDAnalysisCoordinateFileConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/MDAnalysisCoordinateFileConfigurator.py @@ -22,6 +22,7 @@ from .MultiInputFileConfigurator import MultiInputFileConfigurator +@IConfigurator.register("MDAnalysisCoordinateFileConfigurator") class MDAnalysisCoordinateFileConfigurator(MultiInputFileConfigurator): """Take one or more filenames of files containing atomic coordinates. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/MDAnalysisTimeStepConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/MDAnalysisTimeStepConfigurator.py index a9388c2da6..615360fe92 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/MDAnalysisTimeStepConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/MDAnalysisTimeStepConfigurator.py @@ -20,8 +20,10 @@ import MDAnalysis as mda from MDANSE.Framework.Configurators.FloatConfigurator import FloatConfigurator +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator +@IConfigurator.register("MDAnalysisTimeStepConfigurator") class MDAnalysisTimeStepConfigurator(FloatConfigurator): """Input for the trajectory time step in the MDAnalysis converter. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/MDAnalysisTopologyFileConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/MDAnalysisTopologyFileConfigurator.py index 4b464ab8bb..e72b8f5955 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/MDAnalysisTopologyFileConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/MDAnalysisTopologyFileConfigurator.py @@ -20,10 +20,12 @@ import MDAnalysis as mda from MDANSE.Framework.AtomMapping import AtomLabel +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator from .FileWithAtomDataConfigurator import FileWithAtomDataConfigurator +@IConfigurator.register("MDAnalysisTopologyFileConfigurator") class MDAnalysisTopologyFileConfigurator(FileWithAtomDataConfigurator): """Constructs and MDAnalysis.Universe from the input file. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/MDMCTrajectoryConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/MDMCTrajectoryConfigurator.py index 4da5b03eda..308913323b 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/MDMCTrajectoryConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/MDMCTrajectoryConfigurator.py @@ -18,6 +18,7 @@ from MDANSE.Framework.Configurators.IConfigurator import IConfigurator +@IConfigurator.register("MDMCTrajectoryConfigurator") class MDMCTrajectoryConfigurator(IConfigurator): """Use an MDMC CompactTrajectory IN MEMORY. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/MDTrajTimeStepConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/MDTrajTimeStepConfigurator.py index fc66b96704..66ca6572f3 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/MDTrajTimeStepConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/MDTrajTimeStepConfigurator.py @@ -20,8 +20,10 @@ import mdtraj as md from MDANSE.Framework.Configurators.FloatConfigurator import FloatConfigurator +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator +@IConfigurator.register("MDTrajTimeStepConfigurator") class MDTrajTimeStepConfigurator(FloatConfigurator): """Inputs the time step value for the MDTraj converter.""" diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/MDTrajTopologyFileConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/MDTrajTopologyFileConfigurator.py index 05b701462f..f63c8a8d93 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/MDTrajTopologyFileConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/MDTrajTopologyFileConfigurator.py @@ -23,10 +23,12 @@ from mdtraj.core.trajectory import _TOPOLOGY_EXTS from MDANSE.Framework.AtomMapping import AtomLabel +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator from .FileWithAtomDataConfigurator import FileWithAtomDataConfigurator +@IConfigurator.register("MDTrajTopologyFileConfigurator") class MDTrajTopologyFileConfigurator(FileWithAtomDataConfigurator): """Uses MDTraj to read the system topology information from a file.""" diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/MDTrajTrajectoryFileConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/MDTrajTrajectoryFileConfigurator.py index f9193ecc07..a1897557d8 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/MDTrajTrajectoryFileConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/MDTrajTrajectoryFileConfigurator.py @@ -19,9 +19,12 @@ from mdtraj.formats.registry import FormatRegistry +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator + from .MultiInputFileConfigurator import MultiInputFileConfigurator +@IConfigurator.register("MDTrajTrajectoryFileConfigurator") class MDTrajTrajectoryFileConfigurator(MultiInputFileConfigurator): """Passes one or more trajectory files to the MDTraj converter. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/MockTrajectoryConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/MockTrajectoryConfigurator.py index 4de357ba01..881472f404 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/MockTrajectoryConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/MockTrajectoryConfigurator.py @@ -19,6 +19,7 @@ from MDANSE.MolecularDynamics.MockTrajectory import MockTrajectory +@IConfigurator.register("MockTrajectoryConfigurator") class MockTrajectoryConfigurator(IConfigurator): """Uses a mock trajectory definition (from JSON) instead of Trajectory. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/MoleculeSelectionConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/MoleculeSelectionConfigurator.py index a3f94bbbe4..b8fba2ccef 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/MoleculeSelectionConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/MoleculeSelectionConfigurator.py @@ -18,6 +18,7 @@ from MDANSE.Framework.Configurators.IConfigurator import IConfigurator +@IConfigurator.register("MoleculeSelectionConfigurator") class MoleculeSelectionConfigurator(IConfigurator): """Picks a molecule type present in the trajectory. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/MultiInputFileConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/MultiInputFileConfigurator.py index 5417c7c883..fe8037ee8b 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/MultiInputFileConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/MultiInputFileConfigurator.py @@ -21,6 +21,7 @@ from MDANSE.Framework.Configurators.IConfigurator import IConfigurator +@IConfigurator.register("MultiInputFileConfigurator") class MultiInputFileConfigurator(IConfigurator): """Uses multiple files as input. Parent class for more specific inputs.""" diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/MultipleChoicesConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/MultipleChoicesConfigurator.py index b088995d22..92215faa7d 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/MultipleChoicesConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/MultipleChoicesConfigurator.py @@ -18,6 +18,7 @@ from MDANSE.Framework.Configurators.IConfigurator import IConfigurator +@IConfigurator.register("MultipleChoicesConfigurator") class MultipleChoicesConfigurator(IConfigurator): """Allows to select several items from multiple choices. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/OptionalFloatConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/OptionalFloatConfigurator.py index 9108b36a9c..463440c973 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/OptionalFloatConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/OptionalFloatConfigurator.py @@ -18,6 +18,7 @@ from MDANSE.Framework.Configurators.IConfigurator import IConfigurator +@IConfigurator.register("OptionalFloatConfigurator") class OptionalFloatConfigurator(IConfigurator): """Inputs a single floating point number. Empty input is allowed.""" diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/OutputFilesConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/OutputFilesConfigurator.py index a5c6d6b5cd..7079da0a81 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/OutputFilesConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/OutputFilesConfigurator.py @@ -22,6 +22,7 @@ from MDANSE.Framework.Formats.IFormat import IFormat +@IConfigurator.register("OutputFilesConfigurator") class OutputFilesConfigurator(IConfigurator): """Allows the user to choose the output file for writing. @@ -103,7 +104,7 @@ def configure(self, value): f"The output file format {fmt} is not a valid output format." ) - if fmt not in IFormat.indirect_subclasses(): + if fmt not in IFormat.available_names(): raise Exception( f"the output file format {fmt} is not registered as a valid file format." ) diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/OutputStructureConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/OutputStructureConfigurator.py index f9d7405906..5be39bea9b 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/OutputStructureConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/OutputStructureConfigurator.py @@ -23,6 +23,7 @@ from MDANSE.Framework.Configurators.IConfigurator import IConfigurator +@IConfigurator.register("OutputStructureConfigurator") class OutputStructureConfigurator(IConfigurator): """Defines the name of the output (average) structure file. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/OutputTrajectoryConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/OutputTrajectoryConfigurator.py index ce431173ff..1701d6c6e9 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/OutputTrajectoryConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/OutputTrajectoryConfigurator.py @@ -25,6 +25,7 @@ from MDANSE.MolecularDynamics.Trajectory import TrajectoryWriter +@IConfigurator.register("OutputTrajectoryConfigurator") class OutputTrajectoryConfigurator(IConfigurator): """Specifies how a trajectory should be output to a file. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/PartialChargeConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/PartialChargeConfigurator.py index a291a55a72..d1ba6779d7 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/PartialChargeConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/PartialChargeConfigurator.py @@ -131,6 +131,7 @@ def reset_setting(self) -> None: self._new_map = {} +@IConfigurator.register("PartialChargeConfigurator") class PartialChargeConfigurator(IConfigurator): """Assigns partial charges to atoms.""" diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/ProjectionConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/ProjectionConfigurator.py index 21835bccbc..dedde0fe4b 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/ProjectionConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/ProjectionConfigurator.py @@ -24,6 +24,7 @@ from MDANSE.Framework.Projectors.IProjector import IProjector, ProjectorError +@IConfigurator.register("ProjectionConfigurator") class ProjectionConfigurator(IConfigurator): """Projects atomic coordinates onto an axis or plane. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/QRangeConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/QRangeConfigurator.py index 1c315513aa..9d738ef140 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/QRangeConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/QRangeConfigurator.py @@ -15,6 +15,7 @@ # from __future__ import annotations +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator from MDANSE.Framework.Configurators.RangeConfigurator import ( RangeConfigurator, ) @@ -22,6 +23,7 @@ from .IConfigurator import PredictionSettings +@IConfigurator.register("QRangeConfigurator") class QRangeConfigurator(RangeConfigurator): """Range configurator for Q vector generation.""" diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/QVectorsConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/QVectorsConfigurator.py index cb60c6ef5a..69e2f291b4 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/QVectorsConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/QVectorsConfigurator.py @@ -23,6 +23,7 @@ from .IConfigurator import PredictionSettings +@IConfigurator.register("QVectorsConfigurator") class QVectorsConfigurator(IConfigurator): """Creates and configures a q-vector generator. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/RangeConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/RangeConfigurator.py index f35ccf629b..d8bf849da8 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/RangeConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/RangeConfigurator.py @@ -22,6 +22,7 @@ ) +@IConfigurator.register("RangeConfigurator") class RangeConfigurator(IConfigurator): """Inputs a range of values as 3 parameters : start, stop, step. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/RunningModeConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/RunningModeConfigurator.py index 36597fadb0..b3f4f7c8ce 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/RunningModeConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/RunningModeConfigurator.py @@ -20,6 +20,7 @@ from MDANSE.Framework.Configurators.IConfigurator import IConfigurator +@IConfigurator.register("RunningModeConfigurator") class RunningModeConfigurator(IConfigurator): """Specifies how many CPU cores will be used by this task. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/SingleChoiceConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/SingleChoiceConfigurator.py index 4d8030d3ac..2ea4c786cd 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/SingleChoiceConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/SingleChoiceConfigurator.py @@ -20,6 +20,7 @@ ) +@IConfigurator.register("SingleChoiceConfigurator") class SingleChoiceConfigurator(IConfigurator): """Selects a single item from multiple choices.""" diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/TrajectoryFilterConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/TrajectoryFilterConfigurator.py index e6a4649c39..dcaabb3bb4 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/TrajectoryFilterConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/TrajectoryFilterConfigurator.py @@ -25,6 +25,7 @@ ) +@IConfigurator.register("TrajectoryFilterConfigurator") class TrajectoryFilterConfigurator(IConfigurator): """Defines the filter that will be applied to atom positions. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/TrajectoryVariableConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/TrajectoryVariableConfigurator.py index f18476ffd8..45f82ca5ba 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/TrajectoryVariableConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/TrajectoryVariableConfigurator.py @@ -18,6 +18,7 @@ from MDANSE.Framework.Configurators.IConfigurator import IConfigurator +@IConfigurator.register("TrajectoryVariableConfigurator") class TrajectoryVariableConfigurator(IConfigurator): """Selects a variable that is present in a trajectory. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/UnitCellConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/UnitCellConfigurator.py index 4c6d657ba3..db699db523 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/UnitCellConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/UnitCellConfigurator.py @@ -21,6 +21,7 @@ from MDANSE.MLogging import LOG +@IConfigurator.register("UnitCellConfigurator") class UnitCellConfigurator(IConfigurator): """Input a unit cell definition. diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/VectorConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/VectorConfigurator.py index f69eb90aef..689f537e1f 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/VectorConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/VectorConfigurator.py @@ -21,6 +21,7 @@ from MDANSE.Mathematics.LinearAlgebra import Vector +@IConfigurator.register("VectorConfigurator") class VectorConfigurator(IConfigurator): """Inputs a vector given as 3 floating point numbers.""" diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/WeightsConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/WeightsConfigurator.py index 83a3458981..837e8c2c7f 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/WeightsConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/WeightsConfigurator.py @@ -18,11 +18,13 @@ import numpy as np from MDANSE.Chemistry import ATOMS_DATABASE +from MDANSE.Framework.Configurators.IConfigurator import IConfigurator from MDANSE.Framework.Configurators.SingleChoiceConfigurator import ( SingleChoiceConfigurator, ) +@IConfigurator.register("WeightsConfigurator") class WeightsConfigurator(SingleChoiceConfigurator): """Select the atom property to be used by the weight scheme. diff --git a/MDANSE/Src/MDANSE/Framework/Converters/ASE.py b/MDANSE/Src/MDANSE/Framework/Converters/ASE.py index f11fce678d..b80da466f2 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/ASE.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/ASE.py @@ -21,7 +21,6 @@ import numpy as np from ase import Atoms -from ase.io import iread, read from ase.io.trajectory import Trajectory as ASETrajectory from more_itertools import ilen @@ -29,6 +28,7 @@ from MDANSE.Core.Error import Error from MDANSE.Framework.AtomMapping import get_element_from_mapping from MDANSE.Framework.Converters.Converter import Converter +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Framework.Parsers import ASEParser from MDANSE.Framework.Units import INTERNAL_UNITS, UnitError, measure from MDANSE.MLogging import LOG @@ -44,6 +44,8 @@ class ASETrajectoryFileError(Error): pass +@IJob.register("ASE") +@Converter.register("ASE") class ASE(Converter): """Converts a trajectory to MDT format using ASE. diff --git a/MDANSE/Src/MDANSE/Framework/Converters/CASTEP.py b/MDANSE/Src/MDANSE/Framework/Converters/CASTEP.py index 1b10d95e34..3af4e6440f 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/CASTEP.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/CASTEP.py @@ -20,12 +20,15 @@ from MDANSE.Chemistry.ChemicalSystem import ChemicalSystem from MDANSE.Framework.AtomMapping import get_element_from_mapping from MDANSE.Framework.Converters.Converter import Converter +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Framework.Parsers import CASTEPMDFile from MDANSE.MolecularDynamics.Configuration import PeriodicRealConfiguration from MDANSE.MolecularDynamics.Trajectory import TrajectoryWriter from MDANSE.MolecularDynamics.UnitCell import UnitCell +@IJob.register("CASTEP") +@Converter.register("CASTEP") class CASTEP(Converter): """Converts a Castep Trajectory into an MDT trajectory file.""" diff --git a/MDANSE/Src/MDANSE/Framework/Converters/CHARMM.py b/MDANSE/Src/MDANSE/Framework/Converters/CHARMM.py index 8cda5d1fb8..44068ffe5a 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/CHARMM.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/CHARMM.py @@ -15,9 +15,13 @@ # from __future__ import annotations +from MDANSE.Framework.Converters.Converter import Converter from MDANSE.Framework.Converters.DCD import DCD +from MDANSE.Framework.Jobs.IJob import IJob +@IJob.register("CHARMM") +@Converter.register("CHARMM") class CHARMM(DCD): """Converts a CHARMM trajectory to an MDT trajectory.""" diff --git a/MDANSE/Src/MDANSE/Framework/Converters/CP2K.py b/MDANSE/Src/MDANSE/Framework/Converters/CP2K.py index 08763f157f..e426040dce 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/CP2K.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/CP2K.py @@ -24,9 +24,9 @@ from MDANSE.Core.Error import Error from MDANSE.Framework.AtomMapping import get_element_from_mapping from MDANSE.Framework.Converters.Converter import Converter +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Framework.Parsers import CP2KCellFile, XYZFile from MDANSE.Framework.Units import measure -from MDANSE.MLogging import LOG from MDANSE.MolecularDynamics.Configuration import PeriodicRealConfiguration from MDANSE.MolecularDynamics.Trajectory import TrajectoryWriter from MDANSE.MolecularDynamics.UnitCell import UnitCell @@ -36,6 +36,8 @@ class CP2KConverterError(Error): pass +@IJob.register("CP2K") +@Converter.register("CP2K") class CP2K(Converter): """Converts a CP2K trajectory to an MDT trajectory.""" diff --git a/MDANSE/Src/MDANSE/Framework/Converters/Converter.py b/MDANSE/Src/MDANSE/Framework/Converters/Converter.py index 4d10544c7a..499aa9672e 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/Converter.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/Converter.py @@ -15,19 +15,22 @@ # from __future__ import annotations -from abc import abstractmethod +from abc import ABC, abstractmethod +from typing import ClassVar import h5py -from MDANSE.Core.SubclassFactory import SubclassFactory +from MDANSE.Core.SubclassFactory import RegisterFactory from MDANSE.Framework.Formats.HDFFormat import write_metadata from MDANSE.Framework.Jobs.IJob import IJob +from MDANSE.IO.IOUtils import UCDict from MDANSE.MLogging import LOG -class Converter(IJob, metaclass=SubclassFactory): +class Converter(IJob, RegisterFactory, ABC): """Outputs a trajectory in the MDT format.""" + registry: ClassVar[UCDict[str, Converter]] = UCDict() category = ("Converters", "Specific") ancestor = ["empty_data"] runscript_import_line = ( diff --git a/MDANSE/Src/MDANSE/Framework/Converters/DCD.py b/MDANSE/Src/MDANSE/Framework/Converters/DCD.py index ab71dc9e7f..b9c8f44f71 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/DCD.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/DCD.py @@ -15,24 +15,23 @@ # from __future__ import annotations +import collections + import numpy as np -from MDANSE.Core.Error import Error from MDANSE.Framework.Converters.Converter import Converter +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Framework.Parsers import DCDFile, PDBFile -from MDANSE.Framework.Units import measure -from MDANSE.Mathematics.Geometry import get_basis_vectors_from_cell_parameters from MDANSE.MolecularDynamics.Configuration import PeriodicRealConfiguration -from MDANSE.MolecularDynamics.Trajectory import ( - TrajectoryWriter, -) -from MDANSE.MolecularDynamics.UnitCell import UnitCell +from MDANSE.MolecularDynamics.Trajectory import TrajectoryWriter PI_2 = 0.5 * np.pi RECSCALE32BIT = 1 RECSCALE64BIT = 2 +@IJob.register("DCD") +@Converter.register("DCD") class DCD(Converter): """Converts a DCD trajectory to an MDT trajectory.""" diff --git a/MDANSE/Src/MDANSE/Framework/Converters/DFTB.py b/MDANSE/Src/MDANSE/Framework/Converters/DFTB.py index 2177111ea0..65a1ec58f5 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/DFTB.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/DFTB.py @@ -15,10 +15,16 @@ # from __future__ import annotations +import collections + +from MDANSE.Framework.Converters.Converter import Converter from MDANSE.Framework.Converters.Forcite import Forcite +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Framework.Parsers import TrjFile, XTDFile +@IJob.register("DFTB") +@Converter.register("DFTB") class DFTB(Forcite): """Converts a DFTB trajectory to an MDT trajectory.""" diff --git a/MDANSE/Src/MDANSE/Framework/Converters/DL_POLY.py b/MDANSE/Src/MDANSE/Framework/Converters/DL_POLY.py index 4b24e05033..419e6e8350 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/DL_POLY.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/DL_POLY.py @@ -23,13 +23,13 @@ from MDANSE.Chemistry.ChemicalSystem import ChemicalSystem from MDANSE.Core.Error import Error from MDANSE.Framework.Converters.Converter import Converter +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Framework.Parsers import DLPField, DLPHistory from MDANSE.MolecularDynamics.Configuration import ( PeriodicRealConfiguration, RealConfiguration, ) from MDANSE.MolecularDynamics.Trajectory import TrajectoryWriter -from MDANSE.MolecularDynamics.UnitCell import UnitCell class HistoryFileError(Error): @@ -40,6 +40,8 @@ class DL_POLYConverterError(Error): pass +@IJob.register("DL_POLY") +@Converter.register("DL_POLY") class DL_POLY(Converter): """Converts a DL_POLY trajectory to an MDT trajectory.""" diff --git a/MDANSE/Src/MDANSE/Framework/Converters/Forcite.py b/MDANSE/Src/MDANSE/Framework/Converters/Forcite.py index 6010e1b52b..e5edf0f165 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/Forcite.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/Forcite.py @@ -20,6 +20,7 @@ from MDANSE.Chemistry.ChemicalSystem import ChemicalSystem from MDANSE.Framework.AtomMapping import get_element_from_mapping from MDANSE.Framework.Converters.Converter import Converter +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Framework.Parsers import TrjFile, XTDFile from MDANSE.Framework.Units import measure from MDANSE.MolecularDynamics.Configuration import ( @@ -31,6 +32,8 @@ FORCE_FACTOR = measure(1.0, "kcal_per_mole/ang", equivalent=True).toval("Da nm/ps2 mol") +@IJob.register("Forcite") +@Converter.register("Forcite") class Forcite(Converter): """Converts a Forcite trajectory to an MDT trajectory.""" diff --git a/MDANSE/Src/MDANSE/Framework/Converters/Gromacs.py b/MDANSE/Src/MDANSE/Framework/Converters/Gromacs.py index 5ddcdf8508..418f3ffbab 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/Gromacs.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/Gromacs.py @@ -23,6 +23,7 @@ from MDANSE.Core.Error import Error from MDANSE.Framework.Converters.Converter import Converter +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Framework.Parsers import PDBFile from MDANSE.MolecularDynamics.Configuration import PeriodicRealConfiguration from MDANSE.MolecularDynamics.Trajectory import TrajectoryWriter @@ -33,6 +34,8 @@ class GromacsConverterError(Error): pass +@IJob.register("Gromacs") +@Converter.register("Gromacs") class Gromacs(Converter): """Converts a Gromacs trajectory to an MDT trajectory.""" diff --git a/MDANSE/Src/MDANSE/Framework/Converters/LAMMPS.py b/MDANSE/Src/MDANSE/Framework/Converters/LAMMPS.py index 15ddec3764..1a1c30e287 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/LAMMPS.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/LAMMPS.py @@ -15,24 +15,14 @@ # from __future__ import annotations -from abc import ABC, abstractmethod -from collections import defaultdict -from contextlib import suppress -from enum import Enum, auto -from itertools import count -from pathlib import Path from typing import TYPE_CHECKING, Any, Literal -import h5py import numpy as np from more_itertools import consume as drop -from more_itertools import ilen, take -from numpy.typing import NDArray from MDANSE.Chemistry.ChemicalSystem import ChemicalSystem -from MDANSE.Core.Error import Error -from MDANSE.Framework.AtomMapping import get_element_from_mapping from MDANSE.Framework.Converters.Converter import Converter +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Framework.Parsers import ( LAMMPSConfigFile, LAMMPScustom, @@ -40,23 +30,11 @@ LAMMPSReader, LAMMPSxyz, ) -from MDANSE.Framework.Parsers.LAMMPS import LAMMPS_UNITS -from MDANSE.Framework.Parsers.LAMMPSConfig import ATOM_TYPES_MAP -from MDANSE.Framework.Units import measure -from MDANSE.MLogging import LOG -from MDANSE.MolecularDynamics.Configuration import ( - PeriodicBoxConfiguration, - PeriodicRealConfiguration, -) from MDANSE.MolecularDynamics.Trajectory import TrajectoryWriter -from MDANSE.MolecularDynamics.UnitCell import UnitCell - -if TYPE_CHECKING: - from MDANSE.Framework.Configurators.ConfigFileConfigurator import ( - ConfigFileConfigurator, - ) +@IJob.register("LAMMPS") +@Converter.register("LAMMPS") class LAMMPS(Converter): """Converts a LAMMPS trajectory to an MDT trajectory.""" diff --git a/MDANSE/Src/MDANSE/Framework/Converters/MDAnalysis.py b/MDANSE/Src/MDANSE/Framework/Converters/MDAnalysis.py index 1f27e45cab..69b47ff663 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/MDAnalysis.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/MDAnalysis.py @@ -20,6 +20,7 @@ from MDANSE.Chemistry.ChemicalSystem import ChemicalSystem from MDANSE.Framework.AtomMapping import get_element_from_mapping from MDANSE.Framework.Converters.Converter import Converter +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Framework.Units import measure from MDANSE.MolecularDynamics.Configuration import ( PeriodicRealConfiguration, @@ -29,6 +30,8 @@ from MDANSE.MolecularDynamics.UnitCell import UnitCell +@IJob.register("MDAnalysis") +@Converter.register("MDAnalysis") class MDAnalysis(Converter): """Converts a trajectory to the MDT format using MDAnalysis. diff --git a/MDANSE/Src/MDANSE/Framework/Converters/MDTraj.py b/MDANSE/Src/MDANSE/Framework/Converters/MDTraj.py index f7054db92c..419f323ad7 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/MDTraj.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/MDTraj.py @@ -22,6 +22,7 @@ from MDANSE.Chemistry.ChemicalSystem import ChemicalSystem from MDANSE.Framework.AtomMapping import get_element_from_mapping from MDANSE.Framework.Converters.Converter import Converter +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.MolecularDynamics.Configuration import ( PeriodicRealConfiguration, RealConfiguration, @@ -30,6 +31,8 @@ from MDANSE.MolecularDynamics.UnitCell import UnitCell +@IJob.register("MDTraj") +@Converter.register("MDTraj") class MDTraj(Converter): """Converts a trajectory to the MDT format using MDTraj. diff --git a/MDANSE/Src/MDANSE/Framework/Converters/NAMD.py b/MDANSE/Src/MDANSE/Framework/Converters/NAMD.py index 41cdda07ac..5cceac26e4 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/NAMD.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/NAMD.py @@ -15,9 +15,13 @@ # from __future__ import annotations +from MDANSE.Framework.Converters.Converter import Converter from MDANSE.Framework.Converters.DCD import DCD +from MDANSE.Framework.Jobs.IJob import IJob +@IJob.register("NAMD") +@Converter.register("NAMD") class NAMD(DCD): """Converts a NAMD trajectory to an MDT trajectory.""" diff --git a/MDANSE/Src/MDANSE/Framework/Converters/VASP.py b/MDANSE/Src/MDANSE/Framework/Converters/VASP.py index 5b235bc840..ab119c532b 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/VASP.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/VASP.py @@ -15,21 +15,24 @@ # from __future__ import annotations +import collections + from MDANSE.Chemistry.ChemicalSystem import ChemicalSystem from MDANSE.Core.Error import Error from MDANSE.Framework.AtomMapping import get_element_from_mapping from MDANSE.Framework.Converters.Converter import Converter +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Framework.Parsers import XDATCARFile -from MDANSE.Framework.Units import measure from MDANSE.MolecularDynamics.Configuration import PeriodicBoxConfiguration from MDANSE.MolecularDynamics.Trajectory import TrajectoryWriter -from MDANSE.MolecularDynamics.UnitCell import UnitCell class VASPConverterError(Error): pass +@IJob.register("VASP") +@Converter.register("VASP") class VASP(Converter): """Converts a VASP XDATCAR file to an MDT trajectory. diff --git a/MDANSE/Src/MDANSE/Framework/Converters/XPLOR.py b/MDANSE/Src/MDANSE/Framework/Converters/XPLOR.py index a0824c5c16..529d7f6942 100644 --- a/MDANSE/Src/MDANSE/Framework/Converters/XPLOR.py +++ b/MDANSE/Src/MDANSE/Framework/Converters/XPLOR.py @@ -15,9 +15,13 @@ # from __future__ import annotations +from MDANSE.Framework.Converters.Converter import Converter from MDANSE.Framework.Converters.DCD import DCD +from MDANSE.Framework.Jobs.IJob import IJob +@IJob.register("XPLOR") +@Converter.register("XPLOR") class XPLOR(DCD): """Converts an Xplor trajectory to an MDT trajectory.""" diff --git a/MDANSE/Src/MDANSE/Framework/Formats/FileInMemory.py b/MDANSE/Src/MDANSE/Framework/Formats/FileInMemory.py index 386fda0852..d0251feac6 100644 --- a/MDANSE/Src/MDANSE/Framework/Formats/FileInMemory.py +++ b/MDANSE/Src/MDANSE/Framework/Formats/FileInMemory.py @@ -27,6 +27,7 @@ from .HDFFormat import HDFFormat +@IFormat.register("FileInMemory") class FileInMemory(IFormat): """Handles the writing of output to an in-memory HDF5 structure. diff --git a/MDANSE/Src/MDANSE/Framework/Formats/HDFFormat.py b/MDANSE/Src/MDANSE/Framework/Formats/HDFFormat.py index 0e4c3c0bfb..2e557aebdf 100644 --- a/MDANSE/Src/MDANSE/Framework/Formats/HDFFormat.py +++ b/MDANSE/Src/MDANSE/Framework/Formats/HDFFormat.py @@ -126,6 +126,7 @@ def write_metadata(job: IJob, output_file: h5py.File): dgroup.create_dataset(key, (1,), data=value, dtype=string_dt) +@IFormat.register("HDFFormat") class HDFFormat(IFormat): """Handles the writing of output variables in HDF file format. diff --git a/MDANSE/Src/MDANSE/Framework/Formats/IFormat.py b/MDANSE/Src/MDANSE/Framework/Formats/IFormat.py index 35536c129c..b1da0d75d0 100644 --- a/MDANSE/Src/MDANSE/Framework/Formats/IFormat.py +++ b/MDANSE/Src/MDANSE/Framework/Formats/IFormat.py @@ -15,15 +15,16 @@ # from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, ClassVar -from MDANSE.Core.SubclassFactory import SubclassFactory +from MDANSE.Core.SubclassFactory import RegisterFactory +from MDANSE.IO.IOUtils import UCDict if TYPE_CHECKING: from MDANSE.Framework.Jobs.IJob import IJob -class IFormat(metaclass=SubclassFactory): +class IFormat(RegisterFactory): """ This is the base class for writing MDANSE output data. In MDANSE, the output of an analysis can be written in different file format. @@ -32,6 +33,8 @@ class IFormat(metaclass=SubclassFactory): and redefine the "type", "extension" and "extensions" class attributes. """ + registry: ClassVar[UCDict[str, IFormat]] = UCDict() + @classmethod def write(cls, filename, data, header="", run_instance: IJob | None = None): """ diff --git a/MDANSE/Src/MDANSE/Framework/Formats/MDAFormat.py b/MDANSE/Src/MDANSE/Framework/Formats/MDAFormat.py index 52ea06e192..52134c3a4b 100644 --- a/MDANSE/Src/MDANSE/Framework/Formats/MDAFormat.py +++ b/MDANSE/Src/MDANSE/Framework/Formats/MDAFormat.py @@ -16,8 +16,10 @@ from __future__ import annotations from MDANSE.Framework.Formats.HDFFormat import HDFFormat +from MDANSE.Framework.Formats.IFormat import IFormat +@IFormat.register("MDAFormat") class MDAFormat(HDFFormat): """ This class handles the writing of output variables in MDA file format. diff --git a/MDANSE/Src/MDANSE/Framework/Formats/MDTFormat.py b/MDANSE/Src/MDANSE/Framework/Formats/MDTFormat.py index 1c5aa5e231..ff307afdbd 100644 --- a/MDANSE/Src/MDANSE/Framework/Formats/MDTFormat.py +++ b/MDANSE/Src/MDANSE/Framework/Formats/MDTFormat.py @@ -16,8 +16,10 @@ from __future__ import annotations from MDANSE.Framework.Formats.HDFFormat import HDFFormat +from MDANSE.Framework.Formats.IFormat import IFormat +@IFormat.register("MDTFormat") class MDTFormat(HDFFormat): """ This class handles the writing of output variables in MDT file format. diff --git a/MDANSE/Src/MDANSE/Framework/Formats/TextFormat.py b/MDANSE/Src/MDANSE/Framework/Formats/TextFormat.py index b555aa29ec..d250432dc1 100644 --- a/MDANSE/Src/MDANSE/Framework/Formats/TextFormat.py +++ b/MDANSE/Src/MDANSE/Framework/Formats/TextFormat.py @@ -37,6 +37,7 @@ def length_stringio(input: io.BytesIO) -> int: return result +@IFormat.register("TextFormat") class TextFormat(IFormat): """ This class handles the writing of output variables in Text format. Each output variable is written into separate Text files which are further diff --git a/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Gaussian.py b/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Gaussian.py index 7b6b927e63..f46cf53c7d 100644 --- a/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Gaussian.py +++ b/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Gaussian.py @@ -22,6 +22,7 @@ ) +@IInstrumentResolution.register("Gaussian") class Gaussian(IInstrumentResolution): """Defines an instrument resolution with a gaussian response""" diff --git a/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/IInstrumentResolution.py b/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/IInstrumentResolution.py index 19ba920550..340cbc67b1 100644 --- a/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/IInstrumentResolution.py +++ b/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/IInstrumentResolution.py @@ -16,19 +16,23 @@ from __future__ import annotations import abc +from typing import ClassVar import numpy as np from MDANSE.Core.Error import Error -from MDANSE.Core.SubclassFactory import SubclassFactory +from MDANSE.Core.SubclassFactory import RegisterFactory from MDANSE.Framework.Configurable import Configurable +from MDANSE.IO.IOUtils import UCDict class InstrumentResolutionError(Error): pass -class IInstrumentResolution(Configurable, metaclass=SubclassFactory): +class IInstrumentResolution(Configurable, RegisterFactory, abc.ABC): + registry: ClassVar[UCDict[str, IInstrumentResolution]] = UCDict() + def __init__(self): Configurable.__init__(self) diff --git a/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Ideal.py b/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Ideal.py index d40892d656..f44e7b4450 100644 --- a/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Ideal.py +++ b/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Ideal.py @@ -22,6 +22,7 @@ ) +@IInstrumentResolution.register("Ideal") class Ideal(IInstrumentResolution): """Defines an ideal instrument resolution with a Dirac response""" diff --git a/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Lorentzian.py b/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Lorentzian.py index b18131cbd3..741f527eed 100644 --- a/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Lorentzian.py +++ b/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Lorentzian.py @@ -20,6 +20,7 @@ ) +@IInstrumentResolution.register("Lorentzian") class Lorentzian(IInstrumentResolution): """ Defines an instrument resolution with a lorentzian response diff --git a/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/PseudoVoigt.py b/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/PseudoVoigt.py index 3a311bf406..2567af2536 100644 --- a/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/PseudoVoigt.py +++ b/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/PseudoVoigt.py @@ -22,6 +22,7 @@ ) +@IInstrumentResolution.register("PseudoVoigt") class PseudoVoigt(IInstrumentResolution): """Defines an instrument resolution with a pseudo-voigt response""" diff --git a/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Square.py b/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Square.py index 7ba4c29b40..dc17bfe47f 100644 --- a/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Square.py +++ b/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Square.py @@ -22,6 +22,7 @@ ) +@IInstrumentResolution.register("Square") class Square(IInstrumentResolution): """Defines an instrument resolution with a square response""" diff --git a/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Triangular.py b/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Triangular.py index b26eee7965..00ead87a93 100644 --- a/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Triangular.py +++ b/MDANSE/Src/MDANSE/Framework/InstrumentResolutions/Triangular.py @@ -22,6 +22,7 @@ ) +@IInstrumentResolution.register("Triangular") class Triangular(IInstrumentResolution): """Defines an instrument resolution with a triangular response""" diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/AreaPerMolecule.py b/MDANSE/Src/MDANSE/Framework/Jobs/AreaPerMolecule.py index 3969ee5a42..734d320edc 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/AreaPerMolecule.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/AreaPerMolecule.py @@ -25,6 +25,7 @@ class AreaPerMoleculeError(Error): pass +@IJob.register("AreaPerMolecule") class AreaPerMolecule(IJob): """Computes the area per molecule. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/AverageStructure.py b/MDANSE/Src/MDANSE/Framework/Jobs/AverageStructure.py index cb1f0618e7..daccfb08b9 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/AverageStructure.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/AverageStructure.py @@ -26,6 +26,7 @@ from MDANSE.Framework.Units import measure +@IJob.register("AverageStructure") class AverageStructure(IJob): """Outputs a structure file of the atom positions averaged over time. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/CenterOfMassesTrajectory.py b/MDANSE/Src/MDANSE/Framework/Jobs/CenterOfMassesTrajectory.py index 6317bf82f8..f0521854be 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/CenterOfMassesTrajectory.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/CenterOfMassesTrajectory.py @@ -28,6 +28,7 @@ from MDANSE.MolecularDynamics.Trajectory import TrajectoryWriter +@IJob.register("CenterOfMassesTrajectory") class CenterOfMassesTrajectory(IJob): """Outputs a trajectory where molecules have been replaced by artificial particles. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/CoordinationNumber.py b/MDANSE/Src/MDANSE/Framework/Jobs/CoordinationNumber.py index 0fc013ac10..b8e1c74492 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/CoordinationNumber.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/CoordinationNumber.py @@ -22,13 +22,12 @@ import numpy as np import numpy.typing as npt -from MDANSE.Framework.AtomGrouping.grouping import ( - pair_labels, - update_pair_results, -) +from MDANSE.Framework.AtomGrouping.grouping import pair_labels, update_pair_results from MDANSE.Framework.Jobs.DistanceHistogram import DistanceHistogram +from MDANSE.Framework.Jobs.IJob import IJob +@IJob.register("CoordinationNumber") class CoordinationNumber(DistanceHistogram): """Calculates the coordination number for pairs of atom types. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/CurrentCorrelationFunction.py b/MDANSE/Src/MDANSE/Framework/Jobs/CurrentCorrelationFunction.py index 13d2670d37..ba90696582 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/CurrentCorrelationFunction.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/CurrentCorrelationFunction.py @@ -40,6 +40,7 @@ class CurrentCorrelationFunctionError(Exception): pass +@IJob.register("CurrentCorrelationFunction") class CurrentCorrelationFunction(IJob): """Computes the current correlation function for a set of atoms. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/Density.py b/MDANSE/Src/MDANSE/Framework/Jobs/Density.py index 85d280140c..8f2fb5da4e 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/Density.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/Density.py @@ -27,6 +27,7 @@ class DensityError(Exception): pass +@IJob.register("Density") class Density(IJob): """Computes the atom and mass densities for a given trajectory. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/DensityOfStates.py b/MDANSE/Src/MDANSE/Framework/Jobs/DensityOfStates.py index 8aa64696d4..ab2ffec07f 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/DensityOfStates.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/DensityOfStates.py @@ -19,11 +19,13 @@ import copy from MDANSE.Framework.Jobs.CartesianPowerSpectrum import CartesianPowerSpectrum +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Framework.Jobs.VelocityCorrelationFunction import ( VelocityCorrelationFunction, ) +@IJob.register("DensityOfStates") class DensityOfStates(CartesianPowerSpectrum, VelocityCorrelationFunction): """Calculate the vibrational density of states of the trajectory. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/DipoleAutoCorrelationFunction.py b/MDANSE/Src/MDANSE/Framework/Jobs/DipoleAutoCorrelationFunction.py index 841966a888..a9f33aae6a 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/DipoleAutoCorrelationFunction.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/DipoleAutoCorrelationFunction.py @@ -22,6 +22,7 @@ from MDANSE.Mathematics.Geometry import center_of_mass +@IJob.register("DipoleAutoCorrelationFunction") class DipoleAutoCorrelationFunction(IJob): """Calculates the Dipole Autocorrelation Function of a system. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/DistanceHistogram.py b/MDANSE/Src/MDANSE/Framework/Jobs/DistanceHistogram.py index 0b114a755f..0959ff0d78 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/DistanceHistogram.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/DistanceHistogram.py @@ -31,6 +31,7 @@ ) +@IJob.register("DistanceHistogram") class DistanceHistogram(IJob): """Calculates a histogram of interatomic distances. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/DynamicCoherentStructureFactor.py b/MDANSE/Src/MDANSE/Framework/Jobs/DynamicCoherentStructureFactor.py index 8376ff090e..758286ba9c 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/DynamicCoherentStructureFactor.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/DynamicCoherentStructureFactor.py @@ -38,6 +38,7 @@ class DynamicCoherentStructureFactorError(Error): pass +@IJob.register("DynamicCoherentStructureFactor") class DynamicCoherentStructureFactor(IJob): r"""Computes the dynamic coherent structure factor :math:`S_{\text{coh}}(\mathbf{q}, \omega)` for a set of atoms. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/DynamicIncoherentStructureFactor.py b/MDANSE/Src/MDANSE/Framework/Jobs/DynamicIncoherentStructureFactor.py index 38055394c2..7ee75423c4 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/DynamicIncoherentStructureFactor.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/DynamicIncoherentStructureFactor.py @@ -26,6 +26,7 @@ from MDANSE.Mathematics.Signal import get_spectrum +@IJob.register("DynamicIncoherentStructureFactor") class DynamicIncoherentStructureFactor(IJob): r"""Computes the dynamic incoherent structure factor :math:`S_{\text{inc}}(\mathbf{q},\omega)` for a set of atoms. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/Eccentricity.py b/MDANSE/Src/MDANSE/Framework/Jobs/Eccentricity.py index a3d253b55c..6932d8bfe7 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/Eccentricity.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/Eccentricity.py @@ -22,6 +22,7 @@ from MDANSE.Mathematics.Geometry import center_of_mass, moment_of_inertia +@IJob.register("Eccentricity") class Eccentricity(IJob): r"""Computes the eccentricity of a selected set of atoms. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/ElasticIncoherentStructureFactor.py b/MDANSE/Src/MDANSE/Framework/Jobs/ElasticIncoherentStructureFactor.py index 97859259b9..9913ba7465 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/ElasticIncoherentStructureFactor.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/ElasticIncoherentStructureFactor.py @@ -24,6 +24,7 @@ from MDANSE.Mathematics.Arithmetic import assign_weights, get_weights, weighted_sum +@IJob.register("ElasticIncoherentStructureFactor") class ElasticIncoherentStructureFactor(IJob): """Calculates the Elastic Incoherent Structure Factor of a trajectory. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/GaussianDynamicIncoherentStructureFactor.py b/MDANSE/Src/MDANSE/Framework/Jobs/GaussianDynamicIncoherentStructureFactor.py index 5505ff452b..b8886482a3 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/GaussianDynamicIncoherentStructureFactor.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/GaussianDynamicIncoherentStructureFactor.py @@ -26,6 +26,7 @@ from MDANSE.MolecularDynamics.Analysis import mean_square_displacement +@IJob.register("GaussianDynamicIncoherentStructureFactor") class GaussianDynamicIncoherentStructureFactor(IJob): r"""Computes the dynamic incoherent structure factor in the Gaussian approximation. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/IJob.py b/MDANSE/Src/MDANSE/Framework/Jobs/IJob.py index d9cd55bd1e..da73458b12 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/IJob.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/IJob.py @@ -15,7 +15,6 @@ # from __future__ import annotations -import abc import json import multiprocessing import os @@ -27,21 +26,23 @@ import sys import time import traceback +from abc import ABC, abstractmethod from collections.abc import Sequence from logging import FileHandler from logging.handlers import QueueHandler, QueueListener from multiprocessing import Queue from pathlib import Path -from typing import Any +from typing import Any, ClassVar from more_itertools import consumer, first_true from MDANSE import PLATFORM from MDANSE.Core.Error import Error -from MDANSE.Core.SubclassFactory import SubclassFactory +from MDANSE.Core.SubclassFactory import RegisterFactory from MDANSE.Framework.Configurable import Configurable from MDANSE.Framework.Jobs.JobStatus import JobStates, JobStatus from MDANSE.Framework.OutputVariables.IOutputVariable import OutputData +from MDANSE.IO.IOUtils import UCDict from MDANSE.MLogging import FMT, LOG RUNSCRIPT = """\ @@ -187,7 +188,7 @@ def _format_params(parameters: dict) -> str: return param_str -class IJob(Configurable, metaclass=SubclassFactory): +class IJob(Configurable, RegisterFactory, ABC): """The parent class for any MDANSE job. Both analysis runs and converters inherit from IJob, @@ -195,6 +196,8 @@ class IJob(Configurable, metaclass=SubclassFactory): be run in parallel. """ + registry: ClassVar[UCDict[str, IJob]] = UCDict() + section = "job" key_gen = key_generator(6) ancestor = [] @@ -304,7 +307,7 @@ def set_up_trajectory(self): if (grouping := self.configuration.get("grouping_level")) is not None: self.trajectory.set_grouping(grouping["level"]) - @abc.abstractmethod + @abstractmethod def run_step(self, index): pass @@ -575,7 +578,7 @@ def info(self) -> str: @classmethod def save_template(cls, shortname, classname): - if shortname in IJob.subclasses(): + if shortname in IJob.available_names(): raise KeyError( f"A job with {shortname!r} name is already stored in the registry" ) @@ -680,3 +683,8 @@ def remove_log_file_handler(self) -> None: if handler.name == self._log_filename: handler.close() LOG.removeHandler(handler) + + +class Dummy(IJob): + def run_step(self): + raise NotImplementedError(f"{type(self).__name__} is never meant to be run.") diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/Infrared.py b/MDANSE/Src/MDANSE/Framework/Jobs/Infrared.py index cf9db32b70..f53092ec7e 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/Infrared.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/Infrared.py @@ -23,6 +23,7 @@ from MDANSE.Mathematics.Signal import differentiate, get_spectrum +@IJob.register("Infrared") class Infrared(IJob): """Calculates the infrared spectrum of a system of molecules. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/MeanSquareDisplacement.py b/MDANSE/Src/MDANSE/Framework/Jobs/MeanSquareDisplacement.py index 15adf19725..1a6502eed6 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/MeanSquareDisplacement.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/MeanSquareDisplacement.py @@ -23,6 +23,7 @@ from MDANSE.MolecularDynamics.Analysis import mean_square_displacement +@IJob.register("MeanSquareDisplacement") class MeanSquareDisplacement(IJob): r"""Calculates the mean square displacement (MSD) of atoms in the trajectory. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/MolecularTrace.py b/MDANSE/Src/MDANSE/Framework/Jobs/MolecularTrace.py index 3c29c139f0..4293f7dcfb 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/MolecularTrace.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/MolecularTrace.py @@ -20,6 +20,7 @@ from MDANSE.Framework.Jobs.IJob import IJob +@IJob.register("MolecularTrace") class MolecularTrace(IJob): """Maps the volume occupied by atoms over time. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/NeutronDynamicTotalStructureFactor.py b/MDANSE/Src/MDANSE/Framework/Jobs/NeutronDynamicTotalStructureFactor.py index 93c94b33d7..71974b29bf 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/NeutronDynamicTotalStructureFactor.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/NeutronDynamicTotalStructureFactor.py @@ -31,6 +31,7 @@ class NeutronDynamicTotalStructureFactorError(Error): pass +@IJob.register("NeutronDynamicTotalStructureFactor") class NeutronDynamicTotalStructureFactor(IJob): r"""Combines the coherent and incoherent dynamic structure factors. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/PairDistributionFunction.py b/MDANSE/Src/MDANSE/Framework/Jobs/PairDistributionFunction.py index 09c2fd0c2a..cc7eb4e6b1 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/PairDistributionFunction.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/PairDistributionFunction.py @@ -25,9 +25,11 @@ update_pair_results, ) from MDANSE.Framework.Jobs.DistanceHistogram import DistanceHistogram +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Mathematics.Arithmetic import assign_weights, get_weights, weighted_sum +@IJob.register("PairDistributionFunction") class PairDistributionFunction(DistanceHistogram): """Calculates a histogram of interatomic distances. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/PositionCorrelationFunction.py b/MDANSE/Src/MDANSE/Framework/Jobs/PositionCorrelationFunction.py index a3f8227a36..c70cf15889 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/PositionCorrelationFunction.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/PositionCorrelationFunction.py @@ -20,8 +20,10 @@ from MDANSE.Framework.Jobs.CartesianCorrelationFunction import ( CartesianCorrelationFunction, ) +from MDANSE.Framework.Jobs.IJob import IJob +@IJob.register("PositionCorrelationFunction") class PositionCorrelationFunction(CartesianCorrelationFunction): """Calculates the position correlation function. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/PositionPowerSpectrum.py b/MDANSE/Src/MDANSE/Framework/Jobs/PositionPowerSpectrum.py index 3d32a9cd89..69c1b6a54d 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/PositionPowerSpectrum.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/PositionPowerSpectrum.py @@ -16,11 +16,13 @@ from __future__ import annotations from MDANSE.Framework.Jobs.CartesianPowerSpectrum import CartesianPowerSpectrum +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Framework.Jobs.PositionCorrelationFunction import ( PositionCorrelationFunction, ) +@IJob.register("PositionPowerSpectrum") class PositionPowerSpectrum(CartesianPowerSpectrum, PositionCorrelationFunction): """Calculates the position power spectrum. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/RadiusOfGyration.py b/MDANSE/Src/MDANSE/Framework/Jobs/RadiusOfGyration.py index 0269fee9d7..6735c7cf53 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/RadiusOfGyration.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/RadiusOfGyration.py @@ -21,6 +21,7 @@ from MDANSE.MolecularDynamics.Analysis import radius_of_gyration +@IJob.register("RadiusOfGyration") class RadiusOfGyration(IJob): """Calculates the radius of gyration of selected atoms. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/ReorientationalTimeCorrelationFunction.py b/MDANSE/Src/MDANSE/Framework/Jobs/ReorientationalTimeCorrelationFunction.py index ff3d1544cf..82ac5fe92b 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/ReorientationalTimeCorrelationFunction.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/ReorientationalTimeCorrelationFunction.py @@ -52,6 +52,7 @@ def correlate_legendre( return results / corr_window +@IJob.register("ReorientationalTimeCorrelationFunction") class ReorientationalTimeCorrelationFunction(IJob): r"""Correlation of molecule's orientation in time. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/RootMeanSquareDeviation.py b/MDANSE/Src/MDANSE/Framework/Jobs/RootMeanSquareDeviation.py index 85ac895f81..5c0442fc3d 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/RootMeanSquareDeviation.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/RootMeanSquareDeviation.py @@ -23,6 +23,7 @@ from MDANSE.Framework.Jobs.IJob import IJob +@IJob.register("RootMeanSquareDeviation") class RootMeanSquareDeviation(IJob): """Calculates the Root Mean Square Deviation of the selected atoms. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/RootMeanSquareFluctuation.py b/MDANSE/Src/MDANSE/Framework/Jobs/RootMeanSquareFluctuation.py index 293f403203..993ead8eed 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/RootMeanSquareFluctuation.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/RootMeanSquareFluctuation.py @@ -19,6 +19,7 @@ from MDANSE.MolecularDynamics.Analysis import mean_square_fluctuation +@IJob.register("RootMeanSquareFluctuation") class RootMeanSquareFluctuation(IJob): """Calculates the Root Mean Square Fluctuation of atom positions. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/RotationAutocorrelation.py b/MDANSE/Src/MDANSE/Framework/Jobs/RotationAutocorrelation.py index 3b7109a7ca..8a2dc123cb 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/RotationAutocorrelation.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/RotationAutocorrelation.py @@ -23,6 +23,7 @@ from MDANSE.Mathematics.Geometry import center_of_mass +@IJob.register("RotationAutocorrelation") class RotationAutocorrelation(IJob): """Calculates the rotation angle correlation of molecules. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/ScatteringLengthDensityProfile.py b/MDANSE/Src/MDANSE/Framework/Jobs/ScatteringLengthDensityProfile.py index 74d31e95ce..74cfbee977 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/ScatteringLengthDensityProfile.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/ScatteringLengthDensityProfile.py @@ -25,6 +25,7 @@ class ScatteringLengthDensityProfileError(Error): pass +@IJob.register("ScatteringLengthDensityProfile") class ScatteringLengthDensityProfile(IJob): """Produces the time-averaged scattering length density profile. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/SolventAccessibleSurface.py b/MDANSE/Src/MDANSE/Framework/Jobs/SolventAccessibleSurface.py index bf0afb1f77..b3f6d96373 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/SolventAccessibleSurface.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/SolventAccessibleSurface.py @@ -330,6 +330,7 @@ def identify_loose_atoms( return atoms_in_molecule, loose_atoms +@IJob.register("SolventAccessibleSurface") class SolventAccessibleSurface(IJob): """Calculates the accessible surface of the selected atoms. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/StaticStructureFactor.py b/MDANSE/Src/MDANSE/Framework/Jobs/StaticStructureFactor.py index b1b6f0aa5c..96e92349bc 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/StaticStructureFactor.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/StaticStructureFactor.py @@ -25,9 +25,11 @@ update_pair_results, ) from MDANSE.Framework.Jobs.DistanceHistogram import DistanceHistogram +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Mathematics.Arithmetic import assign_weights, get_weights, weighted_sum +@IJob.register("StaticStructureFactor") class StaticStructureFactor(DistanceHistogram): """Computes the static structure factor for a set of atoms. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/StructureFactorFromScatteringFunction.py b/MDANSE/Src/MDANSE/Framework/Jobs/StructureFactorFromScatteringFunction.py index 1b62d47207..50ea68e6b9 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/StructureFactorFromScatteringFunction.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/StructureFactorFromScatteringFunction.py @@ -24,6 +24,7 @@ from MDANSE.Framework.Jobs.IJob import IJob +@IJob.register("StructureFactorFromScatteringFunction") class StructureFactorFromScatteringFunction(IJob): """Computes the static structure factor from the results of another analysis. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/Temperature.py b/MDANSE/Src/MDANSE/Framework/Jobs/Temperature.py index a970e17c54..6e2a360c15 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/Temperature.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/Temperature.py @@ -24,6 +24,7 @@ KB = measure(1.380649e-23, "kg m2/s2 K").toval("Da nm2/ps2 K") +@IJob.register("Temperature") class Temperature(IJob): """Calculates the temperature of the system for every selected frame. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/TrajectoryEditor.py b/MDANSE/Src/MDANSE/Framework/Jobs/TrajectoryEditor.py index b0cad5b21e..2d697e76ed 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/TrajectoryEditor.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/TrajectoryEditor.py @@ -31,6 +31,7 @@ from MDANSE.MolecularDynamics.UnitCell import UnitCell +@IJob.register("TrajectoryEditor") class TrajectoryEditor(IJob): """Write out a modified version of the input trajectory. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/TrajectoryFilter.py b/MDANSE/Src/MDANSE/Framework/Jobs/TrajectoryFilter.py index 6e662c53ca..762e970ec5 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/TrajectoryFilter.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/TrajectoryFilter.py @@ -35,6 +35,7 @@ from MDANSE.MolecularDynamics.Trajectory import TrajectoryWriter +@IJob.register("TrajectoryFilter") class TrajectoryFilter(IJob): """Design and apply a filter for the atomic trajectories. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/VanHoveFunctionDistinct.py b/MDANSE/Src/MDANSE/Framework/Jobs/VanHoveFunctionDistinct.py index 7d71eb3807..023e8c9054 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/VanHoveFunctionDistinct.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/VanHoveFunctionDistinct.py @@ -319,6 +319,7 @@ def intramolecular_lookup_dict( return result +@IJob.register("VanHoveFunctionDistinct") class VanHoveFunctionDistinct(IJob): """Calculates the distinct van Hove function. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/VanHoveFunctionSelf.py b/MDANSE/Src/MDANSE/Framework/Jobs/VanHoveFunctionSelf.py index 5e9d528163..b70a2b663a 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/VanHoveFunctionSelf.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/VanHoveFunctionSelf.py @@ -75,6 +75,7 @@ def van_hove_self( return histograms +@IJob.register("VanHoveFunctionSelf") class VanHoveFunctionSelf(IJob): """Calculates the self part of the van Hove function. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/VelocityCorrelationFunction.py b/MDANSE/Src/MDANSE/Framework/Jobs/VelocityCorrelationFunction.py index f33b044c38..b5030775e3 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/VelocityCorrelationFunction.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/VelocityCorrelationFunction.py @@ -18,9 +18,11 @@ from MDANSE.Framework.Jobs.CartesianCorrelationFunction import ( CartesianCorrelationFunction, ) +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Mathematics.Signal import differentiate +@IJob.register("VelocityCorrelationFunction") class VelocityCorrelationFunction(CartesianCorrelationFunction): r"""Calculates the velocity correlation function of the selected atoms. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/Voronoi.py b/MDANSE/Src/MDANSE/Framework/Jobs/Voronoi.py index d5726a0b5a..3e91cac574 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/Voronoi.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/Voronoi.py @@ -38,6 +38,7 @@ class VoronoiError(Exception): pass +@IJob.register("Voronoi") class Voronoi(IJob): """Performs the Voronoi analysis of available volume per atom. diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/XRayStaticStructureFactor.py b/MDANSE/Src/MDANSE/Framework/Jobs/XRayStaticStructureFactor.py index 60d0a5592b..d49262740c 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/XRayStaticStructureFactor.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/XRayStaticStructureFactor.py @@ -26,6 +26,7 @@ update_pair_results, ) from MDANSE.Framework.Jobs.DistanceHistogram import DistanceHistogram +from MDANSE.Framework.Jobs.IJob import IJob from MDANSE.Mathematics.Arithmetic import assign_weights, get_weights, weighted_sum @@ -51,6 +52,7 @@ def atomic_scattering_factor(element, qvalues, trajectory): ) +@IJob.register("XRayStaticStructureFactor") class XRayStaticStructureFactor(DistanceHistogram): """Computes the X-ray static structure for a set of atoms. diff --git a/MDANSE/Src/MDANSE/Framework/OutputVariables/IOutputVariable.py b/MDANSE/Src/MDANSE/Framework/OutputVariables/IOutputVariable.py index c8037363dd..682e4954f9 100644 --- a/MDANSE/Src/MDANSE/Framework/OutputVariables/IOutputVariable.py +++ b/MDANSE/Src/MDANSE/Framework/OutputVariables/IOutputVariable.py @@ -17,13 +17,15 @@ import collections from collections.abc import Sequence +from typing import ClassVar import numpy as np import numpy.typing as npt from MDANSE.Core.Error import Error -from MDANSE.Core.SubclassFactory import SubclassFactory +from MDANSE.Core.SubclassFactory import RegisterFactory from MDANSE.Framework.Formats.IFormat import IFormat +from MDANSE.IO.IOUtils import UCDict class OutputVariableError(Error): @@ -44,7 +46,7 @@ def write(self, basename, formats, header=None, inputs=None): self.data_object = temp_format.write(basename, self, header, inputs) -class IOutputVariable(np.ndarray, metaclass=SubclassFactory): +class IOutputVariable(np.ndarray, RegisterFactory): """ Defines a MDANSE output variable. @@ -52,6 +54,8 @@ class IOutputVariable(np.ndarray, metaclass=SubclassFactory): Those extra attributes will be contain information necessary for the the MDANSE plotter. """ + registry: ClassVar[dict[str, IOutputVariable]] = UCDict() + def __new__( cls, value: tuple[int, ...] | npt.ArrayLike, diff --git a/MDANSE/Src/MDANSE/Framework/OutputVariables/LineOutputVariable.py b/MDANSE/Src/MDANSE/Framework/OutputVariables/LineOutputVariable.py index 3956e7c7db..ffec3bd244 100644 --- a/MDANSE/Src/MDANSE/Framework/OutputVariables/LineOutputVariable.py +++ b/MDANSE/Src/MDANSE/Framework/OutputVariables/LineOutputVariable.py @@ -18,5 +18,6 @@ from MDANSE.Framework.OutputVariables.IOutputVariable import IOutputVariable +@IOutputVariable.register("LineOutputVariable") class LineOutputVariable(IOutputVariable): _nDimensions = 1 diff --git a/MDANSE/Src/MDANSE/Framework/OutputVariables/SurfaceOutputVariable.py b/MDANSE/Src/MDANSE/Framework/OutputVariables/SurfaceOutputVariable.py index acf9e65a25..2659bf02d4 100644 --- a/MDANSE/Src/MDANSE/Framework/OutputVariables/SurfaceOutputVariable.py +++ b/MDANSE/Src/MDANSE/Framework/OutputVariables/SurfaceOutputVariable.py @@ -18,5 +18,6 @@ from MDANSE.Framework.OutputVariables.IOutputVariable import IOutputVariable +@IOutputVariable.register("SurfaceOutputVariable") class SurfaceOutputVariable(IOutputVariable): _nDimensions = 2 diff --git a/MDANSE/Src/MDANSE/Framework/OutputVariables/VolumeOutputVariable.py b/MDANSE/Src/MDANSE/Framework/OutputVariables/VolumeOutputVariable.py index 51948327d1..aad56a9ce9 100644 --- a/MDANSE/Src/MDANSE/Framework/OutputVariables/VolumeOutputVariable.py +++ b/MDANSE/Src/MDANSE/Framework/OutputVariables/VolumeOutputVariable.py @@ -18,5 +18,6 @@ from MDANSE.Framework.OutputVariables.IOutputVariable import IOutputVariable +@IOutputVariable.register("VolumeOutputVariable") class VolumeOutputVariable(IOutputVariable): _nDimensions = 3 diff --git a/MDANSE/Src/MDANSE/Framework/Projectors/AxialProjector.py b/MDANSE/Src/MDANSE/Framework/Projectors/AxialProjector.py index 727f64b8e4..272c34e432 100644 --- a/MDANSE/Src/MDANSE/Framework/Projectors/AxialProjector.py +++ b/MDANSE/Src/MDANSE/Framework/Projectors/AxialProjector.py @@ -21,6 +21,7 @@ from MDANSE.Mathematics.LinearAlgebra import Vector +@IProjector.register("AxialProjector") class AxialProjector(IProjector): def set_axis(self, axis): try: diff --git a/MDANSE/Src/MDANSE/Framework/Projectors/IProjector.py b/MDANSE/Src/MDANSE/Framework/Projectors/IProjector.py index 45824e5fa2..c277bf7be7 100644 --- a/MDANSE/Src/MDANSE/Framework/Projectors/IProjector.py +++ b/MDANSE/Src/MDANSE/Framework/Projectors/IProjector.py @@ -15,15 +15,20 @@ # from __future__ import annotations +from typing import ClassVar + from MDANSE.Core.Error import Error -from MDANSE.Core.SubclassFactory import SubclassFactory +from MDANSE.Core.SubclassFactory import RegisterFactory +from MDANSE.IO.IOUtils import UCDict class ProjectorError(Error): pass -class IProjector(metaclass=SubclassFactory): +class IProjector(RegisterFactory): + registry: ClassVar[UCDict[str, IProjector]] = UCDict() + def __init__(self): self._axis = None diff --git a/MDANSE/Src/MDANSE/Framework/Projectors/NullProjector.py b/MDANSE/Src/MDANSE/Framework/Projectors/NullProjector.py index d7a001b637..5de6d242ca 100644 --- a/MDANSE/Src/MDANSE/Framework/Projectors/NullProjector.py +++ b/MDANSE/Src/MDANSE/Framework/Projectors/NullProjector.py @@ -18,6 +18,7 @@ from MDANSE.Framework.Projectors.IProjector import IProjector +@IProjector.register("NullProjector") class NullProjector(IProjector): def set_axis(self, axis): pass diff --git a/MDANSE/Src/MDANSE/Framework/Projectors/PlanarProjector.py b/MDANSE/Src/MDANSE/Framework/Projectors/PlanarProjector.py index 468fcd32a6..660082d024 100644 --- a/MDANSE/Src/MDANSE/Framework/Projectors/PlanarProjector.py +++ b/MDANSE/Src/MDANSE/Framework/Projectors/PlanarProjector.py @@ -21,6 +21,7 @@ from MDANSE.Mathematics.LinearAlgebra import Vector +@IProjector.register("PlanarProjector") class PlanarProjector(IProjector): def set_axis(self, axis): try: diff --git a/MDANSE/Src/MDANSE/Framework/QVectors/CircularLatticeQVectors.py b/MDANSE/Src/MDANSE/Framework/QVectors/CircularLatticeQVectors.py index 8ae557aabe..287b462fb8 100644 --- a/MDANSE/Src/MDANSE/Framework/QVectors/CircularLatticeQVectors.py +++ b/MDANSE/Src/MDANSE/Framework/QVectors/CircularLatticeQVectors.py @@ -23,9 +23,11 @@ circle_of_vectors, circle_rotation_matrix, ) +from MDANSE.Framework.QVectors.IQVectors import IQVectors from MDANSE.Framework.QVectors.LatticeQVectors import LatticeQVectors +@IQVectors.register("CircularLatticeQVectors") class CircularLatticeQVectors(LatticeQVectors): """Generates Q vectors on a plane perpendicular to the 'axis' vector. diff --git a/MDANSE/Src/MDANSE/Framework/QVectors/CircularQVectors.py b/MDANSE/Src/MDANSE/Framework/QVectors/CircularQVectors.py index 904c90c96a..3db62aadcf 100644 --- a/MDANSE/Src/MDANSE/Framework/QVectors/CircularQVectors.py +++ b/MDANSE/Src/MDANSE/Framework/QVectors/CircularQVectors.py @@ -87,6 +87,7 @@ def circle_of_vectors( return np.array(all_radii)[None, :n_vecs] * regular_circle +@IQVectors.register("CircularQVectors") class CircularQVectors(IQVectors): """Generates Q vectors as concentric circles on a plane. diff --git a/MDANSE/Src/MDANSE/Framework/QVectors/DispersionLatticeQVectors.py b/MDANSE/Src/MDANSE/Framework/QVectors/DispersionLatticeQVectors.py index 893a5859e1..d8b545872a 100644 --- a/MDANSE/Src/MDANSE/Framework/QVectors/DispersionLatticeQVectors.py +++ b/MDANSE/Src/MDANSE/Framework/QVectors/DispersionLatticeQVectors.py @@ -17,9 +17,11 @@ import numpy as np +from MDANSE.Framework.QVectors.IQVectors import IQVectors from MDANSE.Framework.QVectors.LatticeQVectors import LatticeQVectors +@IQVectors.register("DispersionLatticeQVectors") class DispersionLatticeQVectors(LatticeQVectors): r"""Generates Q vectors along a line in the HKL units of the simulation box. diff --git a/MDANSE/Src/MDANSE/Framework/QVectors/DispersionQVectors.py b/MDANSE/Src/MDANSE/Framework/QVectors/DispersionQVectors.py index 3bbba207c2..6ff5a18490 100644 --- a/MDANSE/Src/MDANSE/Framework/QVectors/DispersionQVectors.py +++ b/MDANSE/Src/MDANSE/Framework/QVectors/DispersionQVectors.py @@ -60,6 +60,7 @@ def dispersion_vectors( return vects, dists * np.sign(np.dot(normal, vects)) +@IQVectors.register("DispersionQVectors") class DispersionQVectors(IQVectors): """Generates Q vectors along a path between two points. diff --git a/MDANSE/Src/MDANSE/Framework/QVectors/GridQVectors.py b/MDANSE/Src/MDANSE/Framework/QVectors/GridQVectors.py index f43b07db92..f59e240d4b 100644 --- a/MDANSE/Src/MDANSE/Framework/QVectors/GridQVectors.py +++ b/MDANSE/Src/MDANSE/Framework/QVectors/GridQVectors.py @@ -20,9 +20,11 @@ import numpy as np from more_itertools import map_reduce +from MDANSE.Framework.QVectors.IQVectors import IQVectors from MDANSE.Framework.QVectors.LatticeQVectors import LatticeQVectors +@IQVectors.register("GridQVectors") class GridQVectors(LatticeQVectors): """Generates vectors on a grid. diff --git a/MDANSE/Src/MDANSE/Framework/QVectors/IQVectors.py b/MDANSE/Src/MDANSE/Framework/QVectors/IQVectors.py index b912783c9e..6cd4470427 100644 --- a/MDANSE/Src/MDANSE/Framework/QVectors/IQVectors.py +++ b/MDANSE/Src/MDANSE/Framework/QVectors/IQVectors.py @@ -16,14 +16,15 @@ from __future__ import annotations import abc -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, ClassVar import numpy as np import numpy.typing as npt from scipy.stats import truncnorm -from MDANSE.Core.SubclassFactory import SubclassFactory +from MDANSE.Core.SubclassFactory import RegisterFactory from MDANSE.Framework.Configurable import Configurable +from MDANSE.IO.IOUtils import UCDict from MDANSE.MLogging import LOG if TYPE_CHECKING: @@ -81,9 +82,10 @@ def truncated_normal_distribution( ) -class IQVectors(Configurable, metaclass=SubclassFactory): +class IQVectors(Configurable, RegisterFactory, abc.ABC): """Parent class of all Q vector generators.""" + registry: ClassVar[UCDict[str, IQVectors]] = UCDict() is_lattice = False def __init__(self, unit_cell: UnitCell | None, status=None): diff --git a/MDANSE/Src/MDANSE/Framework/QVectors/LinearLatticeQVectors.py b/MDANSE/Src/MDANSE/Framework/QVectors/LinearLatticeQVectors.py index b9448a60a7..a187c4b36a 100644 --- a/MDANSE/Src/MDANSE/Framework/QVectors/LinearLatticeQVectors.py +++ b/MDANSE/Src/MDANSE/Framework/QVectors/LinearLatticeQVectors.py @@ -19,10 +19,12 @@ import numpy as np +from MDANSE.Framework.QVectors.IQVectors import IQVectors from MDANSE.Framework.QVectors.LatticeQVectors import LatticeQVectors from MDANSE.Framework.QVectors.LinearQVectors import linear_vectors +@IQVectors.register("LinearLatticeQVectors") class LinearLatticeQVectors(LatticeQVectors): """Generates vectors randomly on a straight line. diff --git a/MDANSE/Src/MDANSE/Framework/QVectors/LinearQVectors.py b/MDANSE/Src/MDANSE/Framework/QVectors/LinearQVectors.py index 330c399c89..cfbca89d63 100644 --- a/MDANSE/Src/MDANSE/Framework/QVectors/LinearQVectors.py +++ b/MDANSE/Src/MDANSE/Framework/QVectors/LinearQVectors.py @@ -50,6 +50,7 @@ def linear_vectors(q: float, q_width: float, n_vecs: int, axis: npt.NDArray[floa return axis[:, np.newaxis] * fact +@IQVectors.register("LinearQVectors") class LinearQVectors(IQVectors): """Generates vectors randomly on a straight line. diff --git a/MDANSE/Src/MDANSE/Framework/QVectors/MillerIndicesQVectors.py b/MDANSE/Src/MDANSE/Framework/QVectors/MillerIndicesQVectors.py index 3c2bddc25c..07823c1faf 100644 --- a/MDANSE/Src/MDANSE/Framework/QVectors/MillerIndicesQVectors.py +++ b/MDANSE/Src/MDANSE/Framework/QVectors/MillerIndicesQVectors.py @@ -17,9 +17,11 @@ import numpy as np +from MDANSE.Framework.QVectors.IQVectors import IQVectors from MDANSE.Framework.QVectors.LatticeQVectors import LatticeQVectors +@IQVectors.register("MillerIndicesQVectors") class MillerIndicesQVectors(LatticeQVectors): """Generates vectors on a grid. diff --git a/MDANSE/Src/MDANSE/Framework/QVectors/SphericalLatticeQVectors.py b/MDANSE/Src/MDANSE/Framework/QVectors/SphericalLatticeQVectors.py index 13788c4156..e87fd32009 100644 --- a/MDANSE/Src/MDANSE/Framework/QVectors/SphericalLatticeQVectors.py +++ b/MDANSE/Src/MDANSE/Framework/QVectors/SphericalLatticeQVectors.py @@ -17,10 +17,12 @@ import numpy as np +from MDANSE.Framework.QVectors.IQVectors import IQVectors from MDANSE.Framework.QVectors.LatticeQVectors import LatticeQVectors from MDANSE.Framework.QVectors.SphericalQVectors import spherical_vectors +@IQVectors.register("SphericalLatticeQVectors") class SphericalLatticeQVectors(LatticeQVectors): """Generates randomly-selected lattice vectors grouped into spheres. diff --git a/MDANSE/Src/MDANSE/Framework/QVectors/SphericalQVectors.py b/MDANSE/Src/MDANSE/Framework/QVectors/SphericalQVectors.py index be1a23bc4c..c98d9de49f 100644 --- a/MDANSE/Src/MDANSE/Framework/QVectors/SphericalQVectors.py +++ b/MDANSE/Src/MDANSE/Framework/QVectors/SphericalQVectors.py @@ -57,6 +57,7 @@ def spherical_vectors(q: float, q_width: float, n_vecs: int) -> npt.NDArray[floa ) +@IQVectors.register("SphericalQVectors") class SphericalQVectors(IQVectors): """Generates vectors randomly on a sphere. diff --git a/MDANSE/Src/MDANSE/IO/IOUtils.py b/MDANSE/Src/MDANSE/IO/IOUtils.py index c769438aec..86e3578371 100644 --- a/MDANSE/Src/MDANSE/IO/IOUtils.py +++ b/MDANSE/Src/MDANSE/IO/IOUtils.py @@ -17,11 +17,12 @@ import json import re +from collections import UserDict from enum import Enum from functools import singledispatch from itertools import filterfalse from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, TypeVar import numpy as np from more_itertools import first_true, last, take, value_chain @@ -31,9 +32,38 @@ if TYPE_CHECKING: from collections.abc import Iterable, Iterator, Sequence +K = TypeVar("K", bound="str") +V = TypeVar("V") + MAX_FILE_COUNT = 2048 +class UCDict(UserDict[K, V]): + """Case insensitive dictionary where all keys are uppercase.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._raw = {} + + def __setitem__(self, key: K, value: V): + super().__setitem__(key.upper(), value) + self._raw[key.upper()] = key + + def __getitem__(self, key: K) -> V: + return super().__getitem__(key.upper()) + + def __contains__(self, key: K) -> bool: + return super().__contains__(key.upper()) + + @property + def raw_mapping(self) -> dict[K, V]: + return self._raw.copy() + + @property + def raw_dict(self) -> dict[K, V]: + return {key: self[key] for key in self._raw.values()} + + class UCEnum(Enum): """Uppercase enumerated type. diff --git a/MDANSE/Src/MDANSE/Scripts/mdanse.py b/MDANSE/Src/MDANSE/Scripts/mdanse.py index 9241c155fd..d0aca697f4 100644 --- a/MDANSE/Src/MDANSE/Scripts/mdanse.py +++ b/MDANSE/Src/MDANSE/Scripts/mdanse.py @@ -103,7 +103,7 @@ def show_results_contents(filename: str, *, verbose: bool) -> str: def show_jobs(*, show_converters: bool = False): if show_converters: - converters = Converter.indirect_subclasses() + converters = Converter.available_names() output = "\n".join( [ "==Converters==", @@ -112,7 +112,7 @@ def show_jobs(*, show_converters: bool = False): ) else: analyses = [] - for job_name in IJob.indirect_subclasses(): + for job_name in IJob.available_names(): instance = IJob.create(job_name) if instance.category[0] != "Converters" and instance.enabled: analyses.append([*getattr(instance, "category", []), job_name]) @@ -126,9 +126,9 @@ def show_jobs(*, show_converters: bool = False): def show_single_job(job_name: str): - if job_name in IJob.indirect_subclasses(): + if job_name in IJob.available_names(): instance = IJob.create(job_name) - elif job_name in Converter.indirect_subclasses(): + elif job_name in Converter.available_names(): instance = Converter.create(job_name) else: raise KeyError(f"{job_name} is not a converter or analysis included in MDANSE.") diff --git a/MDANSE/Tests/UnitTests/Analysis/test_qvectors.py b/MDANSE/Tests/UnitTests/Analysis/test_qvectors.py index 5b97be3e19..c89c1c2ee3 100644 --- a/MDANSE/Tests/UnitTests/Analysis/test_qvectors.py +++ b/MDANSE/Tests/UnitTests/Analysis/test_qvectors.py @@ -161,7 +161,7 @@ def test_directional_hkl_vectors_for_nonorthogonal_cells(qvector_generator): rtol=1e-7, ) -@pytest.mark.parametrize("qvector_generator", IQVectors.indirect_subclasses()) +@pytest.mark.parametrize("qvector_generator", IQVectors.available_names()) def test_qvector_to_hkl_conversion(trajectory, qvector_generator): instance = IQVectors.create(qvector_generator, trajectory.unit_cell(0)) instance.setup({"shells": (5.0, 50.0, 10.0)}) @@ -186,7 +186,7 @@ def test_qvector_to_hkl_conversion(trajectory, qvector_generator): assert np.allclose(original_qvectors, recalculated_qvectors) -@pytest.mark.parametrize("qvector_generator", IQVectors.indirect_subclasses()) +@pytest.mark.parametrize("qvector_generator", IQVectors.available_names()) def test_disf(tmp_path, trajectory, qvector_generator): temp_name = tmp_path / "output" out_file = temp_name.with_suffix(".mda") diff --git a/MDANSE/Tests/UnitTests/Analysis/test_resolutions.py b/MDANSE/Tests/UnitTests/Analysis/test_resolutions.py index 270908c670..2cb4936396 100644 --- a/MDANSE/Tests/UnitTests/Analysis/test_resolutions.py +++ b/MDANSE/Tests/UnitTests/Analysis/test_resolutions.py @@ -15,7 +15,7 @@ def trajectory(): trajectory = Trajectory(short_traj) yield trajectory -@pytest.mark.parametrize("resolution_generator", IInstrumentResolution.subclasses()) +@pytest.mark.parametrize("resolution_generator", IInstrumentResolution.available_names()) def test_disf(tmp_path, trajectory, resolution_generator): temp_name = tmp_path / "output" out_file = temp_name.with_suffix(".mda") @@ -56,7 +56,7 @@ def test_disf(tmp_path, trajectory, resolution_generator): assert log_file.is_file() -@pytest.mark.parametrize("resolution_generator", IInstrumentResolution.subclasses()) +@pytest.mark.parametrize("resolution_generator", IInstrumentResolution.raw_names()) def test_dos(generate_benchmarks, tmp_path, trajectory, resolution_generator): temp_name = tmp_path / "output" out_file = temp_name.with_suffix(".mda") diff --git a/MDANSE/Tests/UnitTests/test_documentation.py b/MDANSE/Tests/UnitTests/test_documentation.py index d539acbca3..f6879b881d 100644 --- a/MDANSE/Tests/UnitTests/test_documentation.py +++ b/MDANSE/Tests/UnitTests/test_documentation.py @@ -3,7 +3,7 @@ from MDANSE.Framework.Jobs.IJob import IJob -job_list = IJob.indirect_subclasses() +job_list = IJob.available_names() @pytest.mark.parametrize("job_name", job_list) diff --git a/MDANSE/Tests/UnitTests/test_ijob.py b/MDANSE/Tests/UnitTests/test_ijob.py index 165ff4d800..ccd518d4e0 100644 --- a/MDANSE/Tests/UnitTests/test_ijob.py +++ b/MDANSE/Tests/UnitTests/test_ijob.py @@ -7,8 +7,6 @@ ALL_JOBS = [ "AreaPerMolecule", "AverageStructure", - "CartesianCorrelationFunction", - "CartesianPowerSpectrum", "CenterOfMassesTrajectory", "DistanceHistogram", "CurrentCorrelationFunction", @@ -40,7 +38,6 @@ "VanHoveFunctionSelf", "VelocityCorrelationFunction", "Voronoi", - "Converter", "CoordinationNumber", "PairDistributionFunction", "StaticStructureFactor", @@ -70,8 +67,8 @@ def test_create_template_with_the_wrong_jobname_raises_error(): IJob.create("QWERTY").save(temp_name) -def test_indirect_subclasses_creates_list_of_all_possible_jobs(): - assert set(ALL_JOBS) == set(IJob.indirect_subclasses()) +def test_available_names_creates_list_of_all_possible_jobs(): + assert set(ALL_JOBS) == set(IJob.raw_names()) @pytest.mark.parametrize("jobname", ALL_JOBS) diff --git a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/QVectorsWidget.py b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/QVectorsWidget.py index 29fac9c905..fa3418d735 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/QVectorsWidget.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/QVectorsWidget.py @@ -413,9 +413,7 @@ def __init__(self, *args, **kwargs): top_bar_layout = QHBoxLayout() top_bar_layout.addWidget(QLabel("Generator type:"), stretch=0) self._selector = QComboBox(self._base) - self._selector.addItems( - x for x in IQVectors.indirect_subclasses() if x != "LatticeQVectors" - ) + self._selector.addItems(IQVectors.raw_names()) self._model = VectorModel(self._base, trajectory=trajectory) self._view = QTableView(self._base) self._preview_button = QPushButton("Preview vector distribution") diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/JobHolder.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/JobHolder.py index eff9fa1690..2fe1d3a23c 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/JobHolder.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/JobHolder.py @@ -364,7 +364,7 @@ def startProcess( item_th.parameters = job_params item_th.free_filename.connect(self.unprotect_filename) if load_afterwards: - if job_name in Converter.subclasses(): + if job_name in Converter.available_names(): item_th.for_loading.connect(self.trajectory_for_loading) else: try: diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/JobTree.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/JobTree.py index 41c4dddc6a..6a7ffca8a4 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/JobTree.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/JobTree.py @@ -17,13 +17,17 @@ from collections import defaultdict from contextlib import suppress +from typing import TYPE_CHECKING from qtpy.QtCore import Qt, Signal from qtpy.QtGui import QStandardItem, QStandardItemModel -from MDANSE.Core.SubclassFactory import SubclassFactory from MDANSE.Framework.Converters.Converter import Converter from MDANSE.Framework.Jobs.IJob import IJob +from MDANSE.IO.IOUtils import UCDict + +if TYPE_CHECKING: + from MDANSE.Core.RegisterFactory import RegisterFactory class JobTree(QStandardItemModel): @@ -64,12 +68,16 @@ def __init__( self.populateTree(parent_class=parent_class, filter=filter) def populateTree( - self, parent_class: type[SubclassFactory] = IJob, filter: str | None = None + self, parent_class: type[RegisterFactory] = IJob, filter: str | None = None ): """This function starts the recursive process of scanning the registry tree. Only called once on startup. """ - full_dict = parent_class.indirect_subclass_dictionary() + if isinstance(parent_class.registry, UCDict): + full_dict = parent_class.raw_dict() + else: + full_dict = parent_class.registered() + sorted_keys = sorted(full_dict) cat_dicts = defaultdict(list) diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Grid.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Grid.py index 9a05fabe89..b44f3cd34e 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Grid.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Grid.py @@ -31,6 +31,7 @@ from MDANSE_GUI.Tabs.Models.PlottingContext import PlottingContext +@Plotter.register("Grid") class Grid(Plotter): """Plots each curve in its own subplot.""" diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Heatmap.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Heatmap.py index 1b0ff0f652..bd3eb22088 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Heatmap.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Heatmap.py @@ -35,6 +35,7 @@ from MDANSE_GUI.Tabs.Models.PlottingContext import PlottingContext +@Plotter.register("Heatmap") class Heatmap(Plotter): """Creates a 2D heatmap plot.""" diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Plotter.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Plotter.py index 6831a79522..6eee870920 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Plotter.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Plotter.py @@ -18,20 +18,24 @@ import copy import csv import enum -from collections.abc import Iterator from itertools import count -from typing import TYPE_CHECKING, Any, Literal, TextIO +from typing import TYPE_CHECKING, Any, ClassVar, Literal, TextIO import numpy as np from matplotlib.axes import Axes from matplotlib.lines import Line2D from more_itertools import consumer -from MDANSE.Core.SubclassFactory import SubclassFactory +from MDANSE.Core.SubclassFactory import RegisterFactory +from MDANSE.IO.IOUtils import UCDict from MDANSE.MLogging import LOG if TYPE_CHECKING: + from collections.abc import Iterator + + from matplotlib.axes import Axes from matplotlib.figure import Figure + from matplotlib.lines import Line2D from MDANSE_GUI.Tabs.Models.PlottingContext import PlottingContext @@ -94,9 +98,11 @@ def enum_to_str(operation: NormOperations) -> str: } -class Plotter(metaclass=SubclassFactory): +class Plotter(RegisterFactory): """Parent class to all classes used for displaying data.""" + registry: ClassVar[UCDict[str, Plotter]] = UCDict() + def __init__(self) -> None: """Create defaults common to all plotters.""" self._figure = None diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Single.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Single.py index f30145be8f..c4c9f28eae 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Single.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Single.py @@ -31,6 +31,7 @@ from MDANSE_GUI.Tabs.Models.PlottingContext import PlottingContext +@Plotter.register("Single") class Single(Plotter): """Plots all the datasets in the same figure.""" diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Text.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Text.py index b62ca550f0..f17ab29738 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Text.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Text.py @@ -353,6 +353,7 @@ def process_ND_data( return header_lines, temp +@Plotter.register("Text") class Text(Plotter): """Special plotter producing text instead of plots. diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Vectors.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Vectors.py index ceae469334..97fdd055a7 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Vectors.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Vectors.py @@ -35,6 +35,7 @@ def violin_plot_width(positions: npt.NDArray[float]) -> float: return np.mean(np.diff(positions)) if len(positions) > 1 else 0.5 +@Plotter.register("Vectors") class Vectors(Plotter): """Plots all the datasets in the same figure.""" diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/Action.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/Action.py index eb6e09abf4..a01ac7b0f0 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/Action.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/Action.py @@ -34,7 +34,7 @@ from MDANSE.Framework.Configurators.HDFTrajectoryConfigurator import ( HDFTrajectoryConfigurator, ) -from MDANSE.Framework.Jobs.IJob import IJob +from MDANSE.Framework.Jobs.IJob import Dummy, IJob from MDANSE.IO.IOUtils import summarise_array from MDANSE.MLogging import LOG from MDANSE.MolecularDynamics.Trajectory import Trajectory @@ -142,7 +142,7 @@ def __init__(self, *args, use_preview=False, **kwargs): self._trajectory_instance = None self._settings = None self._job_name = None - self._job_instance = IJob() + self._job_instance = Dummy() self._use_preview = use_preview self._current_instrument = None self._has_been_initialised = False @@ -178,7 +178,7 @@ def set_trajectory(self, trajectory: Trajectory | None) -> None: if new_path == self._input_traj_path: LOG.debug("Skipping set_trajectory, no change.") return - self._job_instance = IJob() + self._job_instance = Dummy() self._trajectory_instance = trajectory self._trajectory_configurator = HDFTrajectoryConfigurator( "Input Trajectory", instance=trajectory diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/InstrumentInfo.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/InstrumentInfo.py index c01c0d788a..b21cd8a670 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/InstrumentInfo.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/InstrumentInfo.py @@ -62,7 +62,7 @@ class SimpleInstrument: sample_options = ("isotropic", "crystal") technique_options = ("QENS", "INS") resolution_options = tuple(map(str, widget_text_map)) - qvector_options = tuple(map(str, IQVectors.indirect_subclasses())) + qvector_options = tuple(map(str, IQVectors.available_names())) energy_units = ("meV", "1/cm", "THz") momentum_units = ("1/ang", "1/nm", "1/Bohr") diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/PlotWidget.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/PlotWidget.py index 163252e7fb..b9183e1a5a 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/PlotWidget.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/PlotWidget.py @@ -282,7 +282,7 @@ def set_slider_values(self, reset_needed: bool): def available_plotters(self) -> list[str]: """List all the plotters supported by this widget.""" - return [str(x) for x in Plotter.indirect_subclasses() if str(x) not in ("Text")] + return [str(x) for x in Plotter.raw_names() if str(x) != "Text"] def plot_blank( self, diff --git a/MDANSE_GUI/Tests/UnitTests/test_jobs_tabs.py b/MDANSE_GUI/Tests/UnitTests/test_jobs_tabs.py index e4e29f972f..ce2b2d7e2f 100644 --- a/MDANSE_GUI/Tests/UnitTests/test_jobs_tabs.py +++ b/MDANSE_GUI/Tests/UnitTests/test_jobs_tabs.py @@ -16,11 +16,9 @@ from MDANSE_GUI.Tabs.Models.JobTree import JobTree from MDANSE_GUI.Tabs.Models.TrajectoryModel import TrajectoryModel -CONVERTER_SUBCLASSES = Converter.indirect_subclass_dictionary() -ENABLED_CONVERTERS = { - key: val for key, val in CONVERTER_SUBCLASSES.items() if val.enabled -} -IJOB_SUBCLASSES = IJob.indirect_subclass_dictionary() +CONVERTER_SUBCLASSES = Converter.raw_dict() +ENABLED_CONVERTERS = {key: val for key, val in CONVERTER_SUBCLASSES.items() if val.enabled} +IJOB_SUBCLASSES = IJob.raw_dict() ENABLED_JOBS = {key: val for key, val in IJOB_SUBCLASSES.items() if val.enabled} diff --git a/MDANSE_GUI/Tests/UnitTests/test_qvector_widget.py b/MDANSE_GUI/Tests/UnitTests/test_qvector_widget.py index 16ff2e08a7..e92391824f 100644 --- a/MDANSE_GUI/Tests/UnitTests/test_qvector_widget.py +++ b/MDANSE_GUI/Tests/UnitTests/test_qvector_widget.py @@ -16,9 +16,9 @@ from MDANSE_GUI.Tabs.Models.TrajectoryModel import TrajectoryModel -IJOB_SUBCLASSES = IJob.indirect_subclass_dictionary() +IJOB_SUBCLASSES = IJob.raw_dict() ENABLED_JOBS = {key: val for key, val in IJOB_SUBCLASSES.items() if val.enabled} -ENABLED_QVECTORS = set(IQVectors.indirect_subclasses()) - { +ENABLED_QVECTORS = set(IQVectors.raw_names()) - { "IQVectors", "LatticeQVectors", } @@ -33,6 +33,7 @@ def trajectory(): @pytest.mark.parametrize("qvector_type", ENABLED_QVECTORS) +@pytest.mark.filterwarnings("ignore:q vector shells are empty") def test_job_widgets_load(qapp, qtbot, caplog, trajectory, qvector_type): """ Test there are no major errors in constructing job widgets. @@ -41,11 +42,11 @@ def test_job_widgets_load(qapp, qtbot, caplog, trajectory, qvector_type): """ window = QMainWindow() curr_job_name = "DynamicCoherentStructureFactor" - index = [ + index = next( job_index for job_index, job_name in enumerate(sorted(ENABLED_JOBS), 1) if job_name == curr_job_name - ][0] + ) widget = JobTab.gui_instance( parent=window, @@ -78,11 +79,11 @@ def test_job_widgets_load(qapp, qtbot, caplog, trajectory, qvector_type): ind = model.indexFromItem(item) view.on_select_action(ind) - widget_index = [ + widget_index = next( windex for windex, widget_key in enumerate(action._widgets_in_layout.keys()) if widget_key == "q_vectors" - ][0] + ) qvec_widget = action._widgets[widget_index] if "SphericalLattice" in qvector_type: with qtbot.waitSignal(qvec_widget.value_changed):