Skip to content

filters

Filters for the jfchemistry package.

EnergyFilter dataclass

Bases: Filter, PymatGenMaker[InputType, OutputType]


              flowchart TD
              jfchemistry.filters.EnergyFilter[EnergyFilter]
              jfchemistry.filters.base.Filter[Filter]
              jfchemistry.core.makers.pymatgen_maker.PymatGenMaker[PymatGenMaker]
              jfchemistry.core.makers.jfchem_maker.JFChemMaker[JFChemMaker]
              jfchemistry.core.makers.core_maker.CoreMaker[CoreMaker]

                              jfchemistry.filters.base.Filter --> jfchemistry.filters.EnergyFilter
                
                jfchemistry.core.makers.pymatgen_maker.PymatGenMaker --> jfchemistry.filters.EnergyFilter
                                jfchemistry.core.makers.jfchem_maker.JFChemMaker --> jfchemistry.core.makers.pymatgen_maker.PymatGenMaker
                                jfchemistry.core.makers.core_maker.CoreMaker --> jfchemistry.core.makers.jfchem_maker.JFChemMaker
                




              click jfchemistry.filters.EnergyFilter href "" "jfchemistry.filters.EnergyFilter"
              click jfchemistry.filters.base.Filter href "" "jfchemistry.filters.base.Filter"
              click jfchemistry.core.makers.pymatgen_maker.PymatGenMaker href "" "jfchemistry.core.makers.pymatgen_maker.PymatGenMaker"
              click jfchemistry.core.makers.jfchem_maker.JFChemMaker href "" "jfchemistry.core.makers.jfchem_maker.JFChemMaker"
              click jfchemistry.core.makers.core_maker.CoreMaker href "" "jfchemistry.core.makers.core_maker.CoreMaker"
            

Base class for energy filters.

Units

Pass a float in the listed unit or a pint Quantity (e.g. jfchemistry.ureg or jfchemistry.Q_):

  • threshold: [kcal/mol]
ATTRIBUTE DESCRIPTION
name

The name of the energy filter.

TYPE: str

threshold

The threshold for the energy filter [kcal/mol]. Accepts float in [kcal/mol] or pint Quantity; stored as magnitude in [kcal/mol].

TYPE: float | Quantity

Source code in jfchemistry/filters/energy_filter.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@dataclass
class EnergyFilter[InputType: Structure | Molecule, OutputType: Structure | Molecule](
    Filter, PymatGenMaker[InputType, OutputType]
):
    """Base class for energy filters.

    Units:
        Pass a float in the listed unit or a pint Quantity (e.g. ``jfchemistry.ureg``
        or ``jfchemistry.Q_``):

        - threshold: [kcal/mol]

    Attributes:
        name: The name of the energy filter.
        threshold: The threshold for the energy filter [kcal/mol]. Accepts float
            in [kcal/mol] or pint Quantity; stored as magnitude in [kcal/mol].
    """

    name: str = "Energy Filter"
    threshold: float | Quantity = field(
        default=0.0,
        metadata={
            "description": "The threshold for the energy filter [kcal/mol]. "
            "Accepts float in [kcal/mol] or pint Quantity.",
            "unit": "kcal/mol",
        },
    )

    def __post_init__(self):
        """Normalize threshold to magnitude and set _ensemble."""
        if isinstance(self.threshold, Quantity):
            object.__setattr__(self, "threshold", to_magnitude(self.threshold, "kcal_per_mol"))
        super().__post_init__()
        self._ensemble = True

    def _operation(self, input: InputType, **kwargs) -> tuple[OutputType, Properties]:
        """Perform the energy filter operation on an ensemble."""
        assert "properties" in kwargs, "Properties are required for the energy filter."
        properties = kwargs["properties"]
        parsed_properties = [
            Properties.model_validate(property, extra="allow", strict=False)
            for property in properties
            if property is not None
        ]
        energies = np.array(
            [
                te.value.magnitude
                for prop in parsed_properties
                if prop.system is not None
                and (te := getattr(prop.system, "total_energy", None)) is not None
                and te.value is not None
            ]
        ) * next(
            (
                te.value.units
                for prop in parsed_properties
                if prop.system is not None
                and (te := getattr(prop.system, "total_energy", None)) is not None
                and te.value is not None
            ),
            ureg.dimensionless,
        )
        threshold = Q_(self.threshold, "kcal_per_mol")
        minimum_energy = np.min(energies)
        delta_energy = energies - minimum_energy
        delta_energy = delta_energy.to("kcal_per_mol")
        filtered_indices = np.where(delta_energy <= threshold)
        filtered_ensemble = np.array(input)[filtered_indices].tolist()
        filtered_ensemble = [
            type(input[idx]).from_sites(atoms) for idx, atoms in enumerate(filtered_ensemble)
        ]
        filtered_properties = np.array(parsed_properties)[filtered_indices]
        return cast("OutputType", filtered_ensemble), cast(
            "Properties", filtered_properties.tolist()
        )

make

make(input: InputType | list[InputType], **kwargs) -> Response[_output_model]

Create a workflow job for processing structure(s).

Automatically handles job distribution for lists of structures. Each structure in a list is processed as a separate job for parallel execution.

PARAMETER DESCRIPTION
input

Single Pymatgen SiteCollection or list of SiteCollections.

TYPE: InputType | list[InputType]

**kwargs

Additional kwargs to pass to the operation.

DEFAULT: {}

RETURNS DESCRIPTION
Response[_output_model]

Response containing: - structure: Processed structure(s) - files: XYZ format file(s) of the structure(s) - properties: Computed properties from the operation

Examples:

>>> from jfchemistry.conformers import CRESTConformers
>>> from pymatgen.core import Molecule
>>> molecule = Molecule.from_ase_atoms(molecule("C2H6"))
>>> # Generate conformers
>>> conformer_gen = CRESTConformers(ewin=6.0)
>>> job = conformer_gen.make(input)
Source code in jfchemistry/core/makers/jfchem_maker.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
@jfchem_job()
def make(
    self,
    input: InputType | list[InputType],
    **kwargs,
) -> Response[_output_model]:
    """Create a workflow job for processing structure(s).

    Automatically handles job distribution for lists of structures. Each
    structure in a list is processed as a separate job for parallel execution.

    Args:
        input: Single Pymatgen SiteCollection or list of SiteCollections.
        **kwargs: Additional kwargs to pass to the operation.

    Returns:
        Response containing:
            - structure: Processed structure(s)
            - files: XYZ format file(s) of the structure(s)
            - properties: Computed properties from the operation

    Examples:
        >>> from jfchemistry.conformers import CRESTConformers # doctest: +SKIP
        >>> from pymatgen.core import Molecule # doctest: +SKIP
        >>> molecule = Molecule.from_ase_atoms(molecule("C2H6")) # doctest: +SKIP
        >>> # Generate conformers
        >>> conformer_gen = CRESTConformers(ewin=6.0) # doctest: +SKIP
        >>> job = conformer_gen.make(input) # doctest: +SKIP
    """
    return self._run_job(input, **kwargs)

PrismPrunerFilter dataclass

Bases: Filter, PymatGenMaker[InputType, OutputType]


              flowchart TD
              jfchemistry.filters.PrismPrunerFilter[PrismPrunerFilter]
              jfchemistry.filters.base.Filter[Filter]
              jfchemistry.core.makers.pymatgen_maker.PymatGenMaker[PymatGenMaker]
              jfchemistry.core.makers.jfchem_maker.JFChemMaker[JFChemMaker]
              jfchemistry.core.makers.core_maker.CoreMaker[CoreMaker]

                              jfchemistry.filters.base.Filter --> jfchemistry.filters.PrismPrunerFilter
                
                jfchemistry.core.makers.pymatgen_maker.PymatGenMaker --> jfchemistry.filters.PrismPrunerFilter
                                jfchemistry.core.makers.jfchem_maker.JFChemMaker --> jfchemistry.core.makers.pymatgen_maker.PymatGenMaker
                                jfchemistry.core.makers.core_maker.CoreMaker --> jfchemistry.core.makers.jfchem_maker.JFChemMaker
                




              click jfchemistry.filters.PrismPrunerFilter href "" "jfchemistry.filters.PrismPrunerFilter"
              click jfchemistry.filters.base.Filter href "" "jfchemistry.filters.base.Filter"
              click jfchemistry.core.makers.pymatgen_maker.PymatGenMaker href "" "jfchemistry.core.makers.pymatgen_maker.PymatGenMaker"
              click jfchemistry.core.makers.jfchem_maker.JFChemMaker href "" "jfchemistry.core.makers.jfchem_maker.JFChemMaker"
              click jfchemistry.core.makers.core_maker.CoreMaker href "" "jfchemistry.core.makers.core_maker.CoreMaker"
            

PrismPruner-based structural and energy filter for molecular ensembles.

Units

Pass a float in the listed unit or a pint Quantity (e.g. jfchemistry.ureg or jfchemistry.Q_):

  • energy_threshold: [kcal/mol]
ATTRIBUTE DESCRIPTION
name

The name of the filter (default: "PrismPruner Structural Filter").

TYPE: str

structural_threshold

The threshold for the structural filter (default: 0.0). Accepts float or pint Quantity.

TYPE: float | Quantity

energy_threshold

The threshold for the energy filter [kcal/mol] (default: None). Accepts float in [kcal/mol] or pint Quantity.

TYPE: float | Quantity | None

methods

Pruning methods to apply: "MOI", "RMSD", "RMSD_RC" (default: ["MOI", "RMSD"]).

TYPE: list[type[PruningOptions]]

Source code in jfchemistry/filters/prism_filter.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
@dataclass
class PrismPrunerFilter[InputType: Molecule, OutputType: RecursiveMoleculeList](
    Filter,
    PymatGenMaker[InputType, OutputType],
):
    """PrismPruner-based structural and energy filter for molecular ensembles.

    Units:
        Pass a float in the listed unit or a pint Quantity (e.g. ``jfchemistry.ureg``
        or ``jfchemistry.Q_``):

        - energy_threshold: [kcal/mol]

    Attributes:
        name: The name of the filter (default: "PrismPruner Structural Filter").
        structural_threshold: The threshold for the structural filter (default: 0.0).
            Accepts float or pint Quantity.
        energy_threshold: The threshold for the energy filter [kcal/mol] (default: None).
            Accepts float in [kcal/mol] or pint Quantity.
        methods: Pruning methods to apply: "MOI", "RMSD", "RMSD_RC" (default: ["MOI", "RMSD"]).
    """

    name: str = "PrismPruner Structural Filter"
    structural_threshold: float | Quantity = field(
        default=0.0,
        metadata={
            "description": "The threshold for the structural filter. "
            "Accepts float or pint Quantity.",
            "unit": "dimensionless",
        },
    )
    energy_threshold: Optional[float | Quantity] = field(
        default=None,
        metadata={
            "description": "The threshold for the energy filter [kcal/mol]. "
            "Accepts float in [kcal/mol] or pint Quantity.",
            "unit": "kcal/mol",
        },
    )
    methods: list[type[PruningOptions]] = field(
        default_factory=lambda: ["MOI", "RMSD"],
        metadata={"description": "The method for the prism pruner."},
    )
    _method_dict: ClassVar[dict[str, str]] = {
        "MOI": "prune_by_moment_of_inertia",
        "RMSD": "prune_by_rmsd",
        "RMSD_RC": "prune_by_rmsd_rot_corr",
    }

    def __post_init__(self):
        """Normalize unit-bearing attributes and set _ensemble."""
        if isinstance(self.structural_threshold, Quantity):
            object.__setattr__(
                self,
                "structural_threshold",
                to_magnitude(self.structural_threshold, "dimensionless"),
            )
        if self.energy_threshold is not None and isinstance(self.energy_threshold, Quantity):
            object.__setattr__(
                self, "energy_threshold", to_magnitude(self.energy_threshold, "kcal_per_mol")
            )
        super().__post_init__()
        self._ensemble = True

    def _operation(
        self, input: InputType, **kwargs
    ) -> tuple[OutputType | list[OutputType], Properties | list[Properties] | None]:
        """Perform the energy filter operation on an ensemble."""
        properties = kwargs.get("properties", None)
        energies = None
        if self.energy_threshold is not None:
            assert "properties" in kwargs, "Properties are required for the prism pruner."
            if properties is not None:
                parsed_properties = [
                    Properties.model_validate(property, extra="allow", strict=False)
                    for property in properties
                ]
                energies = np.array(
                    [
                        te.value.magnitude
                        for prop in parsed_properties
                        if prop.system is not None
                        and (te := getattr(prop.system, "total_energy", None)) is not None
                        and te.value is not None
                    ]
                ) * next(
                    (
                        te.value.units
                        for prop in parsed_properties
                        if prop.system is not None
                        and (te := getattr(prop.system, "total_energy", None)) is not None
                        and te.value is not None
                    ),
                    ureg.dimensionless,
                )
        coords = np.array([molecule.cart_coords for molecule in input])
        atoms = np.array([s.name for s in input[0].species])
        threshold_hartree = (
            Q_(self.energy_threshold, "kcal_per_mol").to("hartree").magnitude
            if self.energy_threshold is not None
            else 0.0
        )
        _, mask = prune(
            coords,
            atoms,
            energies=energies.to("hartree").magnitude if energies is not None else None,
            max_dE=threshold_hartree,
            debugfunction=None,
            logfunction=None,
            moi_pruning="MOI" in self.methods,
            rmsd_pruning="RMSD" in self.methods,
            rot_corr_rmsd_pruning="RMSD_RC" in self.methods,
        )

        filtered_ensemble = [item for item, keep in zip(input, mask, strict=False) if keep]
        if properties is not None:
            filtered_properties = [
                item for item, keep in zip(properties, mask, strict=False) if keep
            ]
            return filtered_ensemble, filtered_properties
        else:
            return cast("OutputType", filtered_ensemble), []

make

make(input: InputType | list[InputType], **kwargs) -> Response[_output_model]

Create a workflow job for processing structure(s).

Automatically handles job distribution for lists of structures. Each structure in a list is processed as a separate job for parallel execution.

PARAMETER DESCRIPTION
input

Single Pymatgen SiteCollection or list of SiteCollections.

TYPE: InputType | list[InputType]

**kwargs

Additional kwargs to pass to the operation.

DEFAULT: {}

RETURNS DESCRIPTION
Response[_output_model]

Response containing: - structure: Processed structure(s) - files: XYZ format file(s) of the structure(s) - properties: Computed properties from the operation

Examples:

>>> from jfchemistry.conformers import CRESTConformers
>>> from pymatgen.core import Molecule
>>> molecule = Molecule.from_ase_atoms(molecule("C2H6"))
>>> # Generate conformers
>>> conformer_gen = CRESTConformers(ewin=6.0)
>>> job = conformer_gen.make(input)
Source code in jfchemistry/core/makers/jfchem_maker.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
@jfchem_job()
def make(
    self,
    input: InputType | list[InputType],
    **kwargs,
) -> Response[_output_model]:
    """Create a workflow job for processing structure(s).

    Automatically handles job distribution for lists of structures. Each
    structure in a list is processed as a separate job for parallel execution.

    Args:
        input: Single Pymatgen SiteCollection or list of SiteCollections.
        **kwargs: Additional kwargs to pass to the operation.

    Returns:
        Response containing:
            - structure: Processed structure(s)
            - files: XYZ format file(s) of the structure(s)
            - properties: Computed properties from the operation

    Examples:
        >>> from jfchemistry.conformers import CRESTConformers # doctest: +SKIP
        >>> from pymatgen.core import Molecule # doctest: +SKIP
        >>> molecule = Molecule.from_ase_atoms(molecule("C2H6")) # doctest: +SKIP
        >>> # Generate conformers
        >>> conformer_gen = CRESTConformers(ewin=6.0) # doctest: +SKIP
        >>> job = conformer_gen.make(input) # doctest: +SKIP
    """
    return self._run_job(input, **kwargs)