Skip to content

optimizers

Geometry optimization methods for molecular structures.

This module provides geometry optimization workflows using various computational methods including neural network potentials, machine learning force fields, and semi-empirical quantum chemistry.

Available Optimizers
  • GeometryOptimization: Base class for geometry optimization
  • ASEOptimizer: Base class for ASE-based optimizers
  • AimNet2Optimizer: Neural network potential optimizer
  • ORBModelOptimizer: Machine learning force field optimizer
  • TBLiteOptimizer: GFN-xTB semi-empirical optimizer

ASEOptimizer dataclass

Bases: PymatGenMaker[InputType, OutputType], GeometryOptimization


              flowchart TD
              jfchemistry.optimizers.ASEOptimizer[ASEOptimizer]
              jfchemistry.core.makers.pymatgen_maker.PymatGenMaker[PymatGenMaker]
              jfchemistry.core.makers.jfchem_maker.JFChemMaker[JFChemMaker]
              jfchemistry.core.makers.core_maker.CoreMaker[CoreMaker]
              jfchemistry.optimizers.base.GeometryOptimization[GeometryOptimization]

                              jfchemistry.core.makers.pymatgen_maker.PymatGenMaker --> jfchemistry.optimizers.ASEOptimizer
                                jfchemistry.core.makers.jfchem_maker.JFChemMaker --> jfchemistry.core.makers.pymatgen_maker.PymatGenMaker
                                jfchemistry.core.makers.core_maker.CoreMaker --> jfchemistry.core.makers.jfchem_maker.JFChemMaker
                


                jfchemistry.optimizers.base.GeometryOptimization --> jfchemistry.optimizers.ASEOptimizer
                


              click jfchemistry.optimizers.ASEOptimizer href "" "jfchemistry.optimizers.ASEOptimizer"
              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"
              click jfchemistry.optimizers.base.GeometryOptimization href "" "jfchemistry.optimizers.base.GeometryOptimization"
            

Base class for geometry optimization using ASE optimizers.

Combines geometry optimization workflows with ASE calculator interfaces. This class provides the framework for optimizing molecular structures using various ASE optimization algorithms (LBFGS, BFGS, FIRE, etc.) and different calculators (neural networks, machine learning, semi-empirical).

Units

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

  • fmax: [eV/Å]

Subclasses should inherit from both a specific ASECalculator implementation and ASEOptimizer to create complete optimization workflows.

ATTRIBUTE DESCRIPTION
name

Name of the optimizer (default: "ASE Optimizer").

TYPE: str

optimizer

ASE optimization algorithm to use: - "LBFGS": Limited-memory BFGS (default, recommended) - "BFGS": Broyden-Fletcher-Goldfarb-Shanno - "GPMin": Conjugate gradient - "MDMin": Molecular dynamics minimization - "FIRE": Fast Inertial Relaxation Engine - "FIRE2": FIRE version 2 - "QuasiNewton": Quasi-Newton method

TYPE: Literal['LBFGS', 'BFGS', 'GPMin', 'MDMin', 'FIRE', 'FIRE2', 'QuasiNewton']

fmax

Maximum force convergence criterion [eV/Å] (default: 0.05). Accepts float in [eV/Å] or pint Quantity.

TYPE: float | Quantity

steps

Maximum number of optimization steps (default: 250000).

TYPE: int

Examples:

>>> from ase.build import molecule
>>> from pymatgen.core import Molecule
>>> from jfchemistry.optimizers import ASEOptimizer
>>> from jfchemistry.calculators import TBLiteCalculator
>>> molecule = Molecule.from_ase_atoms(molecule("C2H6"))
>>> # Create custom optimizer by inheriting
>>> class MyOptimizer(ASEOptimizer, TBLiteCalculator):
...     pass
>>> opt = MyOptimizer(optimizer="LBFGS", fmax=0.01)
>>> job = opt.make(molecule)
Source code in jfchemistry/optimizers/ase.py
 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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
@dataclass
class ASEOptimizer[InputType: Structure | Molecule, OutputType: Structure | Molecule](
    PymatGenMaker[InputType, OutputType], GeometryOptimization
):
    """Base class for geometry optimization using ASE optimizers.

    Combines geometry optimization workflows with ASE calculator interfaces.
    This class provides the framework for optimizing molecular structures
    using various ASE optimization algorithms (LBFGS, BFGS, FIRE, etc.) and
    different calculators (neural networks, machine learning, semi-empirical).

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

        - fmax: [eV/Å]

    Subclasses should inherit from both a specific ASECalculator implementation
    and ASEOptimizer to create complete optimization workflows.

    Attributes:
        name: Name of the optimizer (default: "ASE Optimizer").
        optimizer: ASE optimization algorithm to use:
            - "LBFGS": Limited-memory BFGS (default, recommended)
            - "BFGS": Broyden-Fletcher-Goldfarb-Shanno
            - "GPMin": Conjugate gradient
            - "MDMin": Molecular dynamics minimization
            - "FIRE": Fast Inertial Relaxation Engine
            - "FIRE2": FIRE version 2
            - "QuasiNewton": Quasi-Newton method
        fmax: Maximum force convergence criterion [eV/Å] (default: 0.05).
            Accepts float in [eV/Å] or pint Quantity.
        steps: Maximum number of optimization steps (default: 250000).

    Examples:
        >>> from ase.build import molecule # doctest: +SKIP
        >>> from pymatgen.core import Molecule # doctest: +SKIP
        >>> from jfchemistry.optimizers import ASEOptimizer # doctest: +SKIP
        >>> from jfchemistry.calculators import TBLiteCalculator # doctest: +SKIP
        >>> molecule = Molecule.from_ase_atoms(molecule("C2H6")) # doctest: +SKIP
        >>> # Create custom optimizer by inheriting
        >>> class MyOptimizer(ASEOptimizer, TBLiteCalculator): # doctest: +SKIP
        ...     pass # doctest: +SKIP
        >>> opt = MyOptimizer(optimizer="LBFGS", fmax=0.01) # doctest: +SKIP
        >>> job = opt.make(molecule) # doctest: +SKIP
    """

    name: str = "ASE Optimizer"
    calculator: ASECalculator = field(
        default_factory=lambda: ASECalculator,
        metadata={"description": "the calculator to use for the calculation"},
    )
    optimizer: Literal["LBFGS", "BFGS", "GPMin", "MDMin", "FIRE", "FIRE2", "QuasiNewton"] = field(
        default="LBFGS",
        metadata={"description": "the ASE optimizer to use for the calculation"},
    )
    unit_cell_optimizer: Optional[
        Literal["UnitCellFilter", "ExpCellFilter", "FrechetCellFilter"]
    ] = field(
        default=None,
        metadata={"description": "the ASE unit cell optimizer to use for the calculation"},
    )
    fmax: float | Quantity = field(
        default=0.05,
        metadata={
            "description": "the maximum force convergence criterion [eV/Å]. "
            "Accepts float in [eV/Å] or pint Quantity.",
            "unit": "eV/Å",
        },
    )
    steps: int = field(
        default=250000,
        metadata={"description": "the maximum number of optimization steps"},
    )
    trajectory: Optional[str] = field(
        default=None,
        metadata={"description": "the trajectory file to save the optimization"},
    )
    logfile: Optional[str] = field(
        default=None,
        metadata={"description": "the log file to save the optimization"},
    )

    def __post_init__(self):
        """Post-initialization hook."""
        if isinstance(self.fmax, Quantity):
            object.__setattr__(self, "fmax", to_magnitude(self.fmax, "eV/angstrom"))
        self.name = f"{self.name} with {self.calculator.name}"
        super().__post_init__()

    def _operation(
        self, input: InputType, **kwargs
    ) -> tuple[OutputType | list[OutputType], Properties | list[Properties] | None]:
        """Optimize molecular structure using ASE.

        Performs geometry optimization by:
        1. Converting structure to ASE Atoms
        2. Setting up the calculator with charge and spin
        3. Running the specified ASE optimizer
        4. Converting back to Pymatgen Molecule
        5. Extracting properties from the calculation

        Args:
            input: Input molecular structure with 3D coordinates.
            **kwargs: Additional kwargs to pass to the operation.

        Returns:
            Tuple containing:
                - Optimized Pymatgen Molecule
                - Dictionary of computed properties from calculator

        Examples:
            >>> from ase.build import molecule # doctest: +SKIP
            >>> from pymatgen.core import Molecule # doctest: +SKIP
            >>> from jfchemistry.optimizers import TBLiteOptimizer # doctest: +SKIP
            >>> ethane = Molecule.from_ase_atoms(molecule("C2H6")) # doctest: +SKIP
            >>> opt = TBLiteOptimizer(optimizer="LBFGS", fmax=0.01) # doctest: +SKIP
            >>> structures, properties = opt.operation(ethane) # doctest: +SKIP
        """
        atoms = input.to_ase_atoms()
        charge = int(input.charge)
        if type(input) is Molecule:
            spin_multiplicity = int(input.spin_multiplicity)
        else:
            spin_multiplicity = 1
        atoms = self.calculator._set_calculator(
            atoms, charge=charge, spin_multiplicity=spin_multiplicity
        )

        if type(input) is Structure and self.unit_cell_optimizer is not None:
            opt_atoms = getattr(filters, self.unit_cell_optimizer)(atoms)
        else:
            opt_atoms = atoms

        opt_func = getattr(ase.optimize, self.optimizer)
        opt = opt_func(opt_atoms, logfile=self.logfile, trajectory=self.trajectory)
        opt.run(self.fmax, self.steps)
        if type(input) is Structure:
            if self.unit_cell_optimizer is not None and isinstance(opt_atoms, Filter):
                opt_atoms = opt_atoms.atoms

        properties = self.calculator._get_properties(opt_atoms)

        if isinstance(input, Structure):
            opt_structure = Structure.from_ase_atoms(opt_atoms)
        elif isinstance(input, Molecule):
            opt_structure = Molecule.from_ase_atoms(opt_atoms)
        else:
            raise ValueError(f"Unsupported structure type: {type(input)}")

        return cast("OutputType", opt_structure), properties

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)

ORCAOptimizer dataclass

Bases: ORCACalculator, GeometryOptimization, PymatGenMaker[InputType, OutputType]


              flowchart TD
              jfchemistry.optimizers.ORCAOptimizer[ORCAOptimizer]
              jfchemistry.calculators.orca.orca_calculator.ORCACalculator[ORCACalculator]
              jfchemistry.calculators.base.WavefunctionCalculator[WavefunctionCalculator]
              jfchemistry.calculators.base.Calculator[Calculator]
              jfchemistry.optimizers.base.GeometryOptimization[GeometryOptimization]
              jfchemistry.core.makers.pymatgen_maker.PymatGenMaker[PymatGenMaker]
              jfchemistry.core.makers.jfchem_maker.JFChemMaker[JFChemMaker]
              jfchemistry.core.makers.core_maker.CoreMaker[CoreMaker]

                              jfchemistry.calculators.orca.orca_calculator.ORCACalculator --> jfchemistry.optimizers.ORCAOptimizer
                                jfchemistry.calculators.base.WavefunctionCalculator --> jfchemistry.calculators.orca.orca_calculator.ORCACalculator
                                jfchemistry.calculators.base.Calculator --> jfchemistry.calculators.base.WavefunctionCalculator
                


                jfchemistry.optimizers.base.GeometryOptimization --> jfchemistry.optimizers.ORCAOptimizer
                
                jfchemistry.core.makers.pymatgen_maker.PymatGenMaker --> jfchemistry.optimizers.ORCAOptimizer
                                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.optimizers.ORCAOptimizer href "" "jfchemistry.optimizers.ORCAOptimizer"
              click jfchemistry.calculators.orca.orca_calculator.ORCACalculator href "" "jfchemistry.calculators.orca.orca_calculator.ORCACalculator"
              click jfchemistry.calculators.base.WavefunctionCalculator href "" "jfchemistry.calculators.base.WavefunctionCalculator"
              click jfchemistry.calculators.base.Calculator href "" "jfchemistry.calculators.base.Calculator"
              click jfchemistry.optimizers.base.GeometryOptimization href "" "jfchemistry.optimizers.base.GeometryOptimization"
              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"
            

Optimize molecular structures using ORCA DFT calculator.

Inherits all attributes from ORCACalculator.

ATTRIBUTE DESCRIPTION
name

Name of the optimizer (default: "Orca Optimizer").

TYPE: str

opt

The ORCA optimizer to use for the calculation (default: ["OPT"]).

TYPE: list[OptModelType] | None

Source code in jfchemistry/optimizers/orca.py
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
@dataclass
class ORCAOptimizer[InputType: Molecule, OutputType: Molecule](
    ORCACalculator, GeometryOptimization, PymatGenMaker[InputType, OutputType]
):
    """Optimize molecular structures using ORCA DFT calculator.

    Inherits all attributes from ORCACalculator.

    Attributes:
        name: Name of the optimizer (default: "Orca Optimizer").
        opt: The ORCA optimizer to use for the calculation (default: ["OPT"]).
    """

    name: str = "Orca Optimizer"
    opt: Optional[list[OptModelType]] = field(
        default_factory=lambda: ["OPT"],
        metadata={"description": "the ORCA optimizer to use for the calculation"},
    )
    steps: int = field(
        default=250000,
        metadata={
            "description": "Maximum optimization steps. Set to 0 for fixed-geometry evaluation."
        },
    )
    _basename: str = "orca_optimizer"

    def _operation(
        self, input: InputType, **kwargs
    ) -> tuple[OutputType | list[OutputType], Properties | list[Properties] | None]:
        """Optimize a molecule using ORCA DFT calculator."""
        input.to("input.xyz", fmt="xyz")
        sk_list = super()._set_keywords()
        if self.steps != 0:
            for opt_kw in self.opt or []:
                sk_list.append(getattr(Opt, opt_kw))
        calc = super()._build_calculator(self._basename)
        calc.structure = Structure.from_xyz("input.xyz")
        super()._set_structure_charge_and_spin(calc, input.charge, input.spin_multiplicity)
        super()._configure_calculator_input(calc, sk_list)
        calc.write_input()
        calc.run()
        output = calc.get_output()
        properties = super()._parse_output(output)
        final_molecule = Molecule.from_file(output.get_file(".xyz"))
        final_molecule = cast("OutputType", final_molecule)
        return final_molecule, properties

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)

TorchSimOptimizer dataclass

Bases: PymatGenMaker[InputType, OutputType], GeometryOptimization


              flowchart TD
              jfchemistry.optimizers.TorchSimOptimizer[TorchSimOptimizer]
              jfchemistry.core.makers.pymatgen_maker.PymatGenMaker[PymatGenMaker]
              jfchemistry.core.makers.jfchem_maker.JFChemMaker[JFChemMaker]
              jfchemistry.core.makers.core_maker.CoreMaker[CoreMaker]
              jfchemistry.optimizers.base.GeometryOptimization[GeometryOptimization]

                              jfchemistry.core.makers.pymatgen_maker.PymatGenMaker --> jfchemistry.optimizers.TorchSimOptimizer
                                jfchemistry.core.makers.jfchem_maker.JFChemMaker --> jfchemistry.core.makers.pymatgen_maker.PymatGenMaker
                                jfchemistry.core.makers.core_maker.CoreMaker --> jfchemistry.core.makers.jfchem_maker.JFChemMaker
                


                jfchemistry.optimizers.base.GeometryOptimization --> jfchemistry.optimizers.TorchSimOptimizer
                


              click jfchemistry.optimizers.TorchSimOptimizer href "" "jfchemistry.optimizers.TorchSimOptimizer"
              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"
              click jfchemistry.optimizers.base.GeometryOptimization href "" "jfchemistry.optimizers.base.GeometryOptimization"
            

Base class for geometry optimization using TorchSim calculators.

Combines geometry optimization with TorchSim calculator interfaces. This class provides the framework for optimizing structures using various TorchSim calculators (neural networks, machine learning, semi-empirical, etc.).

ATTRIBUTE DESCRIPTION
name

Name of the optimizer (default: "TorchSim Optimizer").

TYPE: str

Examples:

>>> from ase.build import molecule
>>> from pymatgen.core import Molecule
>>> from jfchemistry.optimizers import TorchSimOptimizer
>>> from jfchemistry.calculators.torchsim import OrbCalculator
>>> molecule = Molecule.from_ase_atoms(molecule("C2H6"))
>>> # Create custom optimizer by inheriting
>>> class MyOptimizer(TorchSimOptimizer, OrbCalculator):
...     pass
>>> opt = MyOptimizer(optimizer="FIRE")
>>> job = opt.make(molecule)
Source code in jfchemistry/optimizers/torchsim.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
@dataclass
class TorchSimOptimizer[InputType: Molecule | Structure, OutputType: Molecule | Structure](
    PymatGenMaker[InputType, OutputType], GeometryOptimization
):
    """Base class for geometry optimization using TorchSim calculators.

    Combines geometry optimization with TorchSim calculator interfaces.
    This class provides the framework for optimizing structures
    using various TorchSim calculators (neural networks, machine learning,
    semi-empirical, etc.).

    Attributes:
        name: Name of the optimizer (default: "TorchSim Optimizer").

    Examples:
        >>> from ase.build import molecule # doctest: +SKIP
        >>> from pymatgen.core import Molecule # doctest: +SKIP
        >>> from jfchemistry.optimizers import TorchSimOptimizer # doctest: +SKIP
        >>> from jfchemistry.calculators.torchsim import OrbCalculator # doctest: +SKIP
        >>> molecule = Molecule.from_ase_atoms(molecule("C2H6")) # doctest: +SKIP
        >>> # Create custom optimizer by inheriting
        >>> class MyOptimizer(TorchSimOptimizer, OrbCalculator): # doctest: +SKIP
        ...     pass # doctest: +SKIP
        >>> opt = MyOptimizer(optimizer="FIRE") # doctest: +SKIP
        >>> job = opt.make(molecule) # doctest: +SKIP
    """

    name: str = "TorchSim Optimizer"
    calculator: TorchSimCalculator = field(
        default_factory=lambda: TorchSimCalculator,
        metadata={"description": "the calculator to use for the calculation"},
    )
    optimizer: Literal["FIRE", "Gradient Descent"] = field(
        default="FIRE", metadata={"description": "The optimizer to use"}
    )
    autobatcher: bool = field(
        default=True, metadata={"description": "Whether to use the autobatcher"}
    )
    steps: int = field(
        default=10_000,
        metadata={
            "description": "The maximum number of steps to take. Set to 0 for fixed geometry."
        },
    )
    max_steps: Optional[int] = field(
        default=None,
        metadata={"description": "Deprecated alias for steps; if set, overrides steps."},
    )
    steps_between_swaps: int = field(
        default=5,
        metadata={
            "description": "Number of steps to take before checking convergence\
                 and swapping out states."
        },
    )

    def __post_init__(self):
        """Post-initialization hook."""
        if self.max_steps is not None:
            self.steps = self.max_steps
        self.max_steps = self.steps
        self.name = f"{self.name} with {self.calculator.name}"
        super().__post_init__()

    def _operation(
        self, input: InputType, **kwargs
    ) -> tuple[OutputType | list[OutputType], Properties | list[Properties] | None]:
        """Optimize molecular structure using TorchSim.

        Performs geometry optimization by:
        1. Converting structure to TorchSim state
        2. Setting up the calculator with charge and spin
        3. Running the optimizer
        4. Converting back to Pymatgen Molecule
        5. Extracting properties from the calculation

        Args:
            input: Input molecular structure with 3D coordinates.
            **kwargs: Additional kwargs to pass to the operation.

        Returns:
            Tuple containing:
                - Optimized Pymatgen Molecule
                - Dictionary of computed properties from calculator

        Examples:
            >>> from ase.build import molecule # doctest: +SKIP
            >>> from pymatgen.core import Molecule # doctest: +SKIP
            >>> from jfchemistry.optimizers import TorchSimOptimizer # doctest: +SKIP
            >>> ethane = Molecule.from_ase_atoms(molecule("C2H6")) # doctest: +SKIP
            >>> opt = TorchSimOptimizer(optimizer="FIRE") # doctest: +SKIP
            >>> structures, properties = opt.operation(ethane) # doctest: +SKIP
        """
        optimizer = getattr(ts.Optimizer, self.optimizer.lower().replace(" ", "_"))
        model = self.calculator._get_model()
        input.to_ase_atoms().write("initial_structure.xyz")
        if self.steps == 0:
            final_structure = input
            properties = self.calculator._get_properties(final_structure)
        else:
            final_state = ts.optimize(
                system=input.to_ase_atoms(),
                model=model,
                optimizer=optimizer,
                max_steps=self.steps,
                steps_between_swaps=self.steps_between_swaps,
                autobatcher=self.autobatcher,
                pbar=True,
            )
            final_atoms = final_state.to_atoms()[0]
            final_structure = type(input).from_ase_atoms(final_atoms)
            properties = self.calculator._get_properties(final_structure)
        final_structure.to_ase_atoms().write("final_structure.xyz")
        return cast("OutputType", final_structure), properties

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)