stjames 0.0.44__tar.gz → 0.0.45__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of stjames might be problematic. Click here for more details.
- {stjames-0.0.44/stjames.egg-info → stjames-0.0.45}/PKG-INFO +1 -1
- {stjames-0.0.44 → stjames-0.0.45}/pyproject.toml +1 -1
- stjames-0.0.45/stjames/constraint.py +70 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/correction.py +3 -0
- stjames-0.0.45/stjames/method.py +60 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/molecule.py +5 -7
- {stjames-0.0.44 → stjames-0.0.45}/stjames/types.py +2 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/workflows/__init__.py +1 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/workflows/bde.py +5 -1
- stjames-0.0.45/stjames/workflows/conformer_search.py +345 -0
- stjames-0.0.45/stjames/workflows/electronic_properties.py +86 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/workflows/molecular_dynamics.py +10 -3
- {stjames-0.0.44 → stjames-0.0.45}/stjames/workflows/multistage_opt.py +83 -12
- stjames-0.0.45/stjames/workflows/redox_potential.py +102 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/workflows/spin_states.py +1 -1
- {stjames-0.0.44 → stjames-0.0.45/stjames.egg-info}/PKG-INFO +1 -1
- {stjames-0.0.44 → stjames-0.0.45}/stjames.egg-info/SOURCES.txt +3 -0
- stjames-0.0.45/tests/test_constraints.py +29 -0
- stjames-0.0.44/stjames/constraint.py +0 -36
- stjames-0.0.44/stjames/method.py +0 -71
- stjames-0.0.44/stjames/workflows/redox_potential.py +0 -38
- {stjames-0.0.44 → stjames-0.0.45}/LICENSE +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/README.md +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/setup.cfg +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/__init__.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/_deprecated_solvent_settings.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/atom.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/base.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/basis_set.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/calculation.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/data/__init__.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/data/bragg_radii.json +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/data/elements.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/data/isotopes.json +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/data/nist_isotopes.json +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/data/read_nist_isotopes.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/data/symbol_element.json +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/diis_settings.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/grid_settings.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/int_settings.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/message.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/mode.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/opt_settings.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/periodic_cell.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/py.typed +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/scf_settings.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/settings.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/solvent.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/status.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/task.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/thermochem_settings.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/workflows/admet.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/workflows/basic_calculation.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/workflows/conformer.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/workflows/descriptors.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/workflows/fukui.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/workflows/pka.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/workflows/scan.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/workflows/tautomer.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames/workflows/workflow.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames.egg-info/dependency_links.txt +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames.egg-info/requires.txt +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/stjames.egg-info/top_level.txt +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/tests/test_from_extxyz.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/tests/test_molecule.py +0 -0
- {stjames-0.0.44 → stjames-0.0.45}/tests/test_settings.py +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from typing import Optional, Self
|
|
2
|
+
|
|
3
|
+
from pydantic import PositiveFloat, PositiveInt, model_validator
|
|
4
|
+
|
|
5
|
+
from .base import Base, LowercaseStrEnum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConstraintType(LowercaseStrEnum):
|
|
9
|
+
"""Different sorts of constraints."""
|
|
10
|
+
|
|
11
|
+
BOND = "bond"
|
|
12
|
+
ANGLE = "angle"
|
|
13
|
+
DIHEDRAL = "dihedral"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Constraint(Base):
|
|
17
|
+
"""
|
|
18
|
+
Represents a single (absolute) constraint.
|
|
19
|
+
|
|
20
|
+
:param constraint_type: which type
|
|
21
|
+
:param atoms: the atoms in question
|
|
22
|
+
:param value: the value to constrain this to, leaving this blank sets the current value
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
constraint_type: ConstraintType
|
|
26
|
+
atoms: list[PositiveInt] # 1-indexed
|
|
27
|
+
value: Optional[float] = None
|
|
28
|
+
|
|
29
|
+
@model_validator(mode="after")
|
|
30
|
+
def check_atom_list_length(self) -> Self:
|
|
31
|
+
match self.constraint_type:
|
|
32
|
+
case ConstraintType.BOND:
|
|
33
|
+
if len(self.atoms) != 2:
|
|
34
|
+
raise ValueError("Bond constraint needs two atom indices!")
|
|
35
|
+
case ConstraintType.ANGLE:
|
|
36
|
+
if len(self.atoms) != 3:
|
|
37
|
+
raise ValueError("Angle constraint needs three atom indices!")
|
|
38
|
+
case ConstraintType.DIHEDRAL:
|
|
39
|
+
if len(self.atoms) != 4:
|
|
40
|
+
raise ValueError("Dihedral constraint needs four atom indices!")
|
|
41
|
+
case _:
|
|
42
|
+
raise ValueError("Unknown constraint_type!")
|
|
43
|
+
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PairwiseHarmonicConstraint(Base):
|
|
48
|
+
"""
|
|
49
|
+
Represents a harmonic constraint, with a characteristic spring constant.
|
|
50
|
+
|
|
51
|
+
:param atoms: whch atoms to apply to
|
|
52
|
+
:param force_constant: the strength of the attraction, in kcal/mol/Å
|
|
53
|
+
:param equilibrium: the distance at which force is zero
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
atoms: tuple[PositiveInt, PositiveInt] # 1-indexed
|
|
57
|
+
force_constant: PositiveFloat # kcal/mol / Å**2
|
|
58
|
+
equilibrium: PositiveFloat # Å
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SphericalHarmonicConstraint(Base):
|
|
62
|
+
"""
|
|
63
|
+
Represents a spherical harmonic constraint to keep a system near the origin.
|
|
64
|
+
|
|
65
|
+
:param confining radius: the confining radius, in Å
|
|
66
|
+
:param force_constant: the strength of the confinement, in kcal/mol/Å
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
confining_radius: PositiveFloat
|
|
70
|
+
force_constant: PositiveFloat = 10 # kcal/mol / Å**2
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from .base import LowercaseStrEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Method(LowercaseStrEnum):
|
|
7
|
+
HARTREE_FOCK = "hf"
|
|
8
|
+
HF3C = "hf_3c"
|
|
9
|
+
|
|
10
|
+
PBE = "pbe"
|
|
11
|
+
B973C = "b97_3c"
|
|
12
|
+
B97D3BJ = "b97_d3bj"
|
|
13
|
+
R2SCAN = "r2scan"
|
|
14
|
+
R2SCAN3C = "r2scan_3c"
|
|
15
|
+
TPSS = "tpss"
|
|
16
|
+
M06L = "m06l"
|
|
17
|
+
|
|
18
|
+
PBE0 = "pbe0"
|
|
19
|
+
B3LYP = "b3lyp"
|
|
20
|
+
TPSSH = "tpssh"
|
|
21
|
+
M06 = "m06"
|
|
22
|
+
M062X = "m062x"
|
|
23
|
+
|
|
24
|
+
CAMB3LYP = "camb3lyp"
|
|
25
|
+
WB97XD3 = "wb97x_d3"
|
|
26
|
+
WB97XV = "wb97x_v"
|
|
27
|
+
WB97MV = "wb97m_v"
|
|
28
|
+
WB97MD3BJ = "wb97m_d3bj"
|
|
29
|
+
WB97X3C = "wb97x_3c"
|
|
30
|
+
|
|
31
|
+
DSDBLYPD3BJ = "dsd_blyp_d3bj"
|
|
32
|
+
|
|
33
|
+
AIMNET2_WB97MD3 = "aimnet2_wb97md3"
|
|
34
|
+
MACE_MP_0 = "mace_mp_0"
|
|
35
|
+
OCP24_S = "ocp24_s"
|
|
36
|
+
OCP24_L = "ocp24_l"
|
|
37
|
+
|
|
38
|
+
GFN_FF = "gfn_ff"
|
|
39
|
+
GFN0_XTB = "gfn0_xtb"
|
|
40
|
+
GFN1_XTB = "gfn1_xtb"
|
|
41
|
+
GFN2_XTB = "gfn2_xtb"
|
|
42
|
+
|
|
43
|
+
# this was going to be removed, but Jonathon wrote such a nice basis set test... it's off the front end.
|
|
44
|
+
BP86 = "bp86"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
NNPMethod = Literal[Method.AIMNET2_WB97MD3]
|
|
48
|
+
NNP_METHODS = [Method.AIMNET2_WB97MD3]
|
|
49
|
+
|
|
50
|
+
XTBMethod = Literal[Method.GFN_FF, Method.GFN0_XTB, Method.GFN1_XTB, Method.GFN2_XTB]
|
|
51
|
+
XTB_METHODS = [Method.GFN_FF, Method.GFN0_XTB, Method.GFN1_XTB, Method.GFN2_XTB]
|
|
52
|
+
|
|
53
|
+
CompositeMethod = Literal[Method.HF3C, Method.B973C, Method.R2SCAN3C, Method.WB97X3C]
|
|
54
|
+
COMPOSITE_METHODS = [Method.HF3C, Method.B973C, Method.R2SCAN3C, Method.WB97X3C]
|
|
55
|
+
|
|
56
|
+
PrepackagedMethod = XTBMethod | CompositeMethod | NNPMethod
|
|
57
|
+
PREPACKAGED_METHODS = [*XTB_METHODS, *COMPOSITE_METHODS]
|
|
58
|
+
|
|
59
|
+
MethodWithCorrection = Literal[Method.WB97XD3, Method.WB97XV, Method.WB97MV, Method.WB97MD3BJ, Method.DSDBLYPD3BJ]
|
|
60
|
+
METHODS_WITH_CORRECTION = [Method.WB97XD3, Method.WB97XV, Method.WB97MV, Method.WB97MD3BJ, Method.DSDBLYPD3BJ, Method.B97D3BJ]
|
|
@@ -8,7 +8,7 @@ from pydantic import NonNegativeInt, PositiveInt, ValidationError
|
|
|
8
8
|
from .atom import Atom
|
|
9
9
|
from .base import Base
|
|
10
10
|
from .periodic_cell import PeriodicCell
|
|
11
|
-
from .types import Matrix3x3, Vector3D, Vector3DPerAtom
|
|
11
|
+
from .types import FloatPerAtom, Matrix3x3, Vector3D, Vector3DPerAtom
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class MoleculeReadError(RuntimeError):
|
|
@@ -18,10 +18,8 @@ class MoleculeReadError(RuntimeError):
|
|
|
18
18
|
class VibrationalMode(Base):
|
|
19
19
|
frequency: float # in cm-1
|
|
20
20
|
reduced_mass: float # amu
|
|
21
|
-
|
|
22
|
-
#
|
|
23
|
-
force_constant: float
|
|
24
|
-
displacements: Vector3DPerAtom
|
|
21
|
+
force_constant: float # mDyne/Å
|
|
22
|
+
displacements: Vector3DPerAtom # Å
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
class Molecule(Base):
|
|
@@ -44,8 +42,8 @@ class Molecule(Base):
|
|
|
44
42
|
|
|
45
43
|
velocities: Optional[Vector3DPerAtom] = None # Å/fs
|
|
46
44
|
|
|
47
|
-
mulliken_charges:
|
|
48
|
-
mulliken_spin_densities:
|
|
45
|
+
mulliken_charges: FloatPerAtom | None = None
|
|
46
|
+
mulliken_spin_densities: FloatPerAtom | None = None
|
|
49
47
|
dipole: Optional[Vector3D] = None # in Debye
|
|
50
48
|
|
|
51
49
|
vibrational_modes: Optional[list[VibrationalMode]] = None
|
|
@@ -58,7 +58,7 @@ class BDEWorkflow(Workflow, MultiStageOptMixin):
|
|
|
58
58
|
:param initial_molecule: Molecule of interest
|
|
59
59
|
:param mode: Mode for workflow
|
|
60
60
|
:param multistage_opt_settings: set by mode unless mode=MANUAL (ignores additional settings if set)
|
|
61
|
-
:param solvent: solvent to use
|
|
61
|
+
:param solvent: solvent to use for singlepoint
|
|
62
62
|
:param xtb_preopt: pre-optimize with xtb (sets based on mode when None)
|
|
63
63
|
|
|
64
64
|
Overridden:
|
|
@@ -106,6 +106,10 @@ class BDEWorkflow(Workflow, MultiStageOptMixin):
|
|
|
106
106
|
"""
|
|
107
107
|
return f"{type(self).__name__} {self.mode.name}\n" + "\n".join(map(str, self.fragment_indices))
|
|
108
108
|
|
|
109
|
+
@property
|
|
110
|
+
def level_of_theory(self) -> str:
|
|
111
|
+
return self.multistage_opt_settings.level_of_theory
|
|
112
|
+
|
|
109
113
|
@property
|
|
110
114
|
def energies(self) -> tuple[float | None, ...]:
|
|
111
115
|
return tuple(bde.energy for bde in self.bdes)
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""Conformer Search Workflow."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from typing import Self, Sequence, TypeVar
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
7
|
+
|
|
8
|
+
from ..base import LowercaseStrEnum
|
|
9
|
+
from ..constraint import Constraint
|
|
10
|
+
from ..method import Method, XTBMethod
|
|
11
|
+
from ..mode import Mode
|
|
12
|
+
from ..types import UUID
|
|
13
|
+
from .multistage_opt import MultiStageOptMixin
|
|
14
|
+
from .workflow import Workflow
|
|
15
|
+
|
|
16
|
+
_sentinel = object()
|
|
17
|
+
|
|
18
|
+
_T = TypeVar("_T")
|
|
19
|
+
_U = TypeVar("_U")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def check_sentinel(value: _T, default: _U) -> _T | _U:
|
|
23
|
+
"""Return value unless _sentinel, then return default."""
|
|
24
|
+
return default if value is _sentinel else value
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ScreeningSettings(BaseModel):
|
|
28
|
+
"""
|
|
29
|
+
Settings for determing unique and useful conformers.
|
|
30
|
+
|
|
31
|
+
:param energy_threshhold: maximum relative energy for screening
|
|
32
|
+
:param rotational_constants_threshhold: maximum difference in rotational constants for screening
|
|
33
|
+
:param rmsd: cartesian RMSD for screening
|
|
34
|
+
:param max_confs: maximum number of conformers to keep
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
energy_threshhold: float | None = None # kcal/mol
|
|
38
|
+
rotational_constants_threshhold: float | None = 0.02
|
|
39
|
+
rmsd: float | None = 0.25
|
|
40
|
+
max_confs: int | None = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ConformerGenSettings(BaseModel):
|
|
44
|
+
"""
|
|
45
|
+
Conformer generation settings.
|
|
46
|
+
|
|
47
|
+
Conformers are generated and an initial screening is performed to remove duplicates and high-energy conformers.
|
|
48
|
+
|
|
49
|
+
:param mode: Mode for calculations
|
|
50
|
+
:param conf_opt_method: method for the optimization
|
|
51
|
+
:param screening: post-generation screening settings
|
|
52
|
+
:param constraints: constraints for conformer generation
|
|
53
|
+
:param nci: add a constraining potential for non-covalent interactions
|
|
54
|
+
:param max_confs: maximum number of conformers to keep
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
mode: Mode = Mode.RAPID
|
|
58
|
+
conf_opt_method: XTBMethod = Method.GFN_FF
|
|
59
|
+
screening: ScreeningSettings | None = None
|
|
60
|
+
constraints: Sequence[Constraint] = tuple()
|
|
61
|
+
nci: bool = False
|
|
62
|
+
max_confs: int | None = None
|
|
63
|
+
|
|
64
|
+
def __str__(self) -> str:
|
|
65
|
+
"""Return a string representation of the ConformerGenSettings."""
|
|
66
|
+
return repr(self)
|
|
67
|
+
|
|
68
|
+
def __repr__(self) -> str:
|
|
69
|
+
"""Return a string representation of the ConformerGenSettings."""
|
|
70
|
+
return f"<{type(self).__name__} {self.mode.name}>"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ETKDGSettings(ConformerGenSettings):
|
|
74
|
+
"""
|
|
75
|
+
Settings for ETKDG conformer generation.
|
|
76
|
+
|
|
77
|
+
Inherited:
|
|
78
|
+
:param mode: Mode for calculations
|
|
79
|
+
:param screening: post-generation screening settings
|
|
80
|
+
:param constraints: constraints for conformer generation
|
|
81
|
+
:param nci: add a constraining potential for non-covalent interactions (not supported in ETKDG)
|
|
82
|
+
:param conf_opt_method: method for the optimization
|
|
83
|
+
|
|
84
|
+
New:
|
|
85
|
+
:param num_initial_confs: number of initial conformers to generate
|
|
86
|
+
:param num_confs_considered: number of conformers to consider for optimization
|
|
87
|
+
:param num_confs_taken: number of final conformers to take
|
|
88
|
+
:param max_mmff_energy: MMFF energy cutoff
|
|
89
|
+
:param max_mmff_iterations: MMFF optimization iterations
|
|
90
|
+
:param max_confs: maximum number of conformers to keep
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
num_initial_confs: int = 300
|
|
94
|
+
num_confs_considered: int = 100
|
|
95
|
+
max_mmff_iterations: int = 500
|
|
96
|
+
max_mmff_energy: float | None = 30
|
|
97
|
+
|
|
98
|
+
@field_validator("constraints")
|
|
99
|
+
def check_constraints(cls, constraints: Sequence[Constraint]) -> Sequence[Constraint]:
|
|
100
|
+
if constraints:
|
|
101
|
+
raise ValueError("ETKDG does not support constraints")
|
|
102
|
+
|
|
103
|
+
return tuple(constraints)
|
|
104
|
+
|
|
105
|
+
@field_validator("nci")
|
|
106
|
+
def check_nci(cls, nci: bool) -> bool:
|
|
107
|
+
if nci:
|
|
108
|
+
raise ValueError("ETKDG does not support NCI")
|
|
109
|
+
|
|
110
|
+
return nci
|
|
111
|
+
|
|
112
|
+
@model_validator(mode="after")
|
|
113
|
+
def validate_and_build(self) -> Self:
|
|
114
|
+
match self.mode:
|
|
115
|
+
case Mode.MANUAL:
|
|
116
|
+
pass
|
|
117
|
+
case Mode.RECKLESS:
|
|
118
|
+
self.num_initial_confs = 200
|
|
119
|
+
self.num_confs_considered = 50
|
|
120
|
+
self.max_confs = 20 if self.max_confs is _sentinel else self.max_confs
|
|
121
|
+
self.max_mmff_energy = 20
|
|
122
|
+
case Mode.RAPID:
|
|
123
|
+
self.max_confs = 50 if self.max_confs is _sentinel else self.max_confs
|
|
124
|
+
self.conf_opt_method = Method.GFN0_XTB
|
|
125
|
+
case _:
|
|
126
|
+
raise NotImplementedError(f"Unsupported mode: {self.mode}")
|
|
127
|
+
|
|
128
|
+
return self
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class iMTDSpeeds(LowercaseStrEnum):
|
|
132
|
+
MEGAQUICK = "megaquick"
|
|
133
|
+
SUPERQUICK = "superquick"
|
|
134
|
+
QUICK = "quick"
|
|
135
|
+
NORMAL = "normal"
|
|
136
|
+
EXTENSIVE = "extensive"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class iMTDSettings(ConformerGenSettings, ABC):
|
|
140
|
+
"""
|
|
141
|
+
Settings for iMTD style conformer generation.
|
|
142
|
+
|
|
143
|
+
RECKLESS:
|
|
144
|
+
- GFN-FF//MTD(GFN-FF)
|
|
145
|
+
- Megaquick
|
|
146
|
+
- No GC
|
|
147
|
+
- No rotamer metadynamics
|
|
148
|
+
- Energy window = 5.0
|
|
149
|
+
- Run scaling factor = 0.5
|
|
150
|
+
- 6 MTD runs
|
|
151
|
+
RAPID:
|
|
152
|
+
- GFN0//MTD(GFN-FF)
|
|
153
|
+
- Superquick
|
|
154
|
+
- No GC
|
|
155
|
+
- No rotamer metadynamics
|
|
156
|
+
- Energy window = 5.0
|
|
157
|
+
- Run scaling factor = 0.5
|
|
158
|
+
- 6 MTD runs
|
|
159
|
+
CAREFUL:
|
|
160
|
+
- GFN2//MTD(GFN-FF)
|
|
161
|
+
- Quick
|
|
162
|
+
- GC (for iMTD-GC)
|
|
163
|
+
- Rotamer metadynamics (for iMTD-GC)
|
|
164
|
+
- Energy window = 5.0
|
|
165
|
+
- Run scaling factor = 0.5
|
|
166
|
+
- 6 MTD runs
|
|
167
|
+
METICULOUS:
|
|
168
|
+
- GFN2//MTD(GFN2)
|
|
169
|
+
- "Normal"
|
|
170
|
+
- GC (for iMTD-GC)
|
|
171
|
+
- Rotamer metadynamics (for iMTD-GC)
|
|
172
|
+
- Energy window = 6.0
|
|
173
|
+
- Run scaling factor = 1
|
|
174
|
+
- 14 MTD runs (2 with extreme values)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
See https://github.com/crest-lab/crest/blob/5ca82feb2ec4df30a0129db957163c934f085952/src/choose_settings.f90#L202
|
|
178
|
+
and https://github.com/crest-lab/crest/blob/5ca82feb2ec4df30a0129db957163c934f085952/src/confparse.f90#L825
|
|
179
|
+
for how quick, superquick, and megaquick are defined.
|
|
180
|
+
|
|
181
|
+
Additional notes:
|
|
182
|
+
Extensive mode
|
|
183
|
+
- GC
|
|
184
|
+
- Rotamer metadynamics
|
|
185
|
+
- Energy window = 8.0
|
|
186
|
+
- Run scaling factor = 2
|
|
187
|
+
- 14 MTD runs (2 with extreme values)
|
|
188
|
+
|
|
189
|
+
--NCI may switch things to QUICK?
|
|
190
|
+
|
|
191
|
+
Inherited:
|
|
192
|
+
:param mode: Mode for calculations
|
|
193
|
+
:param conf_opt_method: method for the optimization
|
|
194
|
+
:param screening: post-generation screening settings (not used)
|
|
195
|
+
:param constraints: constraints to add
|
|
196
|
+
:param nci: add an ellipsoide potential around the input structure
|
|
197
|
+
|
|
198
|
+
New:
|
|
199
|
+
:param mtd_method: method for the metadynamics
|
|
200
|
+
:param speed: speed of the calculations (CREST specific setting)
|
|
201
|
+
:param reopt: re-optimize conformers (corrects for the lack of rotamer metadynamics and GC)
|
|
202
|
+
:param free_energy_weights: calculate frequencies and re-weight based on free energies
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
mtd_method: XTBMethod = Method.GFN_FF
|
|
206
|
+
mtd_runtype: str = "imtd-gc"
|
|
207
|
+
|
|
208
|
+
speed: iMTDSpeeds = iMTDSpeeds.QUICK
|
|
209
|
+
reopt: bool = _sentinel # type: ignore [assignment]
|
|
210
|
+
free_energy_weights: bool = False
|
|
211
|
+
|
|
212
|
+
@model_validator(mode="after")
|
|
213
|
+
def validate_and_build_imtdgc_settings(self) -> Self:
|
|
214
|
+
match self.mode:
|
|
215
|
+
case Mode.MANUAL:
|
|
216
|
+
if self.reopt is _sentinel:
|
|
217
|
+
raise ValueError("Must specify reopt with MANUAL mode")
|
|
218
|
+
case Mode.RECKLESS: # GFN-FF//MTD(GFN-FF)
|
|
219
|
+
self.max_confs = 20 if self.max_confs is _sentinel else self.max_confs
|
|
220
|
+
self.speed = iMTDSpeeds.MEGAQUICK
|
|
221
|
+
self.reopt = check_sentinel(self.reopt, True)
|
|
222
|
+
case Mode.RAPID: # GFN0//MTD(GFN-FF)
|
|
223
|
+
self.max_confs = 50 if self.max_confs is _sentinel else self.max_confs
|
|
224
|
+
self.speed = iMTDSpeeds.SUPERQUICK
|
|
225
|
+
self.conf_opt_method = Method.GFN0_XTB
|
|
226
|
+
self.reopt = check_sentinel(self.reopt, True)
|
|
227
|
+
case Mode.CAREFUL: # GFN2//MTD(GFN-FF)
|
|
228
|
+
self.speed = iMTDSpeeds.QUICK
|
|
229
|
+
self.conf_opt_method = Method.GFN2_XTB
|
|
230
|
+
self.reopt = check_sentinel(self.reopt, False)
|
|
231
|
+
case Mode.METICULOUS: # GFN2//MTD(GFN2)
|
|
232
|
+
self.speed = iMTDSpeeds.NORMAL
|
|
233
|
+
self.mtd_method = Method.GFN2_XTB
|
|
234
|
+
self.conf_opt_method = Method.GFN2_XTB
|
|
235
|
+
self.reopt = check_sentinel(self.reopt, False)
|
|
236
|
+
# case Mode.EXTREME: # GFN2//MTD(GFN2)
|
|
237
|
+
# self.mtd_method = Method.GFN2_XTB
|
|
238
|
+
# self.conf_opt_method = Method.GFN2_XTB
|
|
239
|
+
# self.speed = iMTDSpeeds.EXTENSIVE
|
|
240
|
+
# self.reopt = check_sentinel(self.reopt, False)
|
|
241
|
+
case _:
|
|
242
|
+
raise NotImplementedError(f"Unsupported mode: {self.mode}")
|
|
243
|
+
|
|
244
|
+
return self
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class iMTDGCSettings(iMTDSettings):
|
|
248
|
+
run_type: str = "imtdgc"
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class iMTDsMTDSettings(iMTDSettings):
|
|
252
|
+
run_type: str = "imtd-smtd"
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class ConformerGenMixin(BaseModel):
|
|
256
|
+
"""
|
|
257
|
+
Mixin for workflows that need conformer generation.
|
|
258
|
+
|
|
259
|
+
:param conf_gen_mode: Mode for calculations
|
|
260
|
+
:param conf_gen_settings: settings for conformer generation
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
conf_gen_mode: Mode = Mode.RAPID
|
|
264
|
+
conf_gen_settings: ConformerGenSettings = _sentinel # type: ignore [assignment]
|
|
265
|
+
constraints: Sequence[Constraint] = tuple()
|
|
266
|
+
|
|
267
|
+
@model_validator(mode="after")
|
|
268
|
+
def validate_and_build_conf_gen_settings(self) -> Self:
|
|
269
|
+
"""Validate and build the ConformerGenSettings."""
|
|
270
|
+
if self.conf_gen_settings is not _sentinel and self.conf_gen_mode != Mode.MANUAL:
|
|
271
|
+
raise ValueError("Cannot specify conf_gen_settings with non-MANUAL mode")
|
|
272
|
+
|
|
273
|
+
match self.conf_gen_mode:
|
|
274
|
+
case Mode.MANUAL:
|
|
275
|
+
if self.conf_gen_settings is _sentinel:
|
|
276
|
+
raise ValueError("Must specify conf_gen_settings with MANUAL mode")
|
|
277
|
+
|
|
278
|
+
case Mode.RECKLESS | Mode.RAPID:
|
|
279
|
+
# ETKDGSettings will error if constraints added
|
|
280
|
+
self.conf_gen_settings = ETKDGSettings(mode=self.conf_gen_mode, constraints=self.constraints)
|
|
281
|
+
case Mode.CAREFUL | Mode.METICULOUS:
|
|
282
|
+
self.conf_gen_settings = iMTDSettings(mode=self.conf_gen_mode, constraints=self.constraints)
|
|
283
|
+
|
|
284
|
+
case _:
|
|
285
|
+
raise NotImplementedError(f"Unsupported mode: {self.conf_gen_mode}")
|
|
286
|
+
|
|
287
|
+
return self
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class ConformerSearchMixin(ConformerGenMixin, MultiStageOptMixin):
|
|
291
|
+
"""
|
|
292
|
+
Mixin for workflows that need conformer search—a combination of conformer generation and optimization.
|
|
293
|
+
|
|
294
|
+
Inherited:
|
|
295
|
+
:param conf_gen_mode: Mode for conformer generation
|
|
296
|
+
:param mso_mode: Mode for MultiStageOptSettings
|
|
297
|
+
:param conf_gen_settings: settings for conformer generation
|
|
298
|
+
:param multistage_opt_settings: set by mso_mode unless mode=MANUAL (ignores additional settings if set)
|
|
299
|
+
:param solvent: solvent to use
|
|
300
|
+
:param xtb_preopt: pre-optimize with xtb (sets based on mode when None)
|
|
301
|
+
:param constraints: constraints to add (diamond inheritance, works as expected)
|
|
302
|
+
:param transition_state: whether this is a transition state
|
|
303
|
+
|
|
304
|
+
Overridden:
|
|
305
|
+
:param frequencies: whether to calculate frequencies (turned off)
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
frequencies: bool = False
|
|
309
|
+
|
|
310
|
+
def __str__(self) -> str:
|
|
311
|
+
"""Return a string representation of the ConformerSearch workflow."""
|
|
312
|
+
return repr(self)
|
|
313
|
+
|
|
314
|
+
def __repr__(self) -> str:
|
|
315
|
+
"""Return a string representation of the ConformerSearch workflow."""
|
|
316
|
+
return f"<{type(self).__name__} {self.conf_gen_mode.name} {self.mso_mode.name}>"
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class ConformerSearchWorkflow(ConformerSearchMixin, Workflow):
|
|
320
|
+
"""
|
|
321
|
+
ConformerSearch Workflow.
|
|
322
|
+
|
|
323
|
+
Inherited:
|
|
324
|
+
:param initial_molecule: Molecule of interest
|
|
325
|
+
:param conf_gen_mode: Mode for calculations
|
|
326
|
+
:param conf_gen_settings: settings for conformer generation
|
|
327
|
+
:param mso_mode: Mode for MultiStageOptSettings
|
|
328
|
+
:param multistage_opt_settings: set by mode unless mode=MANUAL (ignores additional settings if set)
|
|
329
|
+
:param solvent: solvent to use
|
|
330
|
+
:param xtb_preopt: pre-optimize with xtb (sets based on mode when None)
|
|
331
|
+
:param constraints: constraints to add
|
|
332
|
+
:param transition_state: whether this is a transition state
|
|
333
|
+
:param frequencies: whether to calculate frequencies (turned off)
|
|
334
|
+
|
|
335
|
+
Ignored:
|
|
336
|
+
:param mode: Mode to use (not used)
|
|
337
|
+
|
|
338
|
+
New:
|
|
339
|
+
:param conformer_uuids: list of UUIDs of the Molecules generated
|
|
340
|
+
:param energies: energies of the molecules
|
|
341
|
+
"""
|
|
342
|
+
|
|
343
|
+
# Results
|
|
344
|
+
conformer_uuids: list[list[UUID | None]] = Field(default_factory=list)
|
|
345
|
+
energies: list[float] = Field(default_factory=list)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from pydantic import NonNegativeFloat, NonNegativeInt
|
|
2
|
+
|
|
3
|
+
from ..base import Base
|
|
4
|
+
from ..settings import Settings
|
|
5
|
+
from ..types import UUID, FloatPerAtom, Matrix3x3, Vector3D
|
|
6
|
+
from .workflow import Workflow
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PropertyCubePoint(Base):
|
|
10
|
+
x: float
|
|
11
|
+
y: float
|
|
12
|
+
z: float
|
|
13
|
+
val: float
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PropertyCube(Base):
|
|
17
|
+
"""
|
|
18
|
+
Represents a "cubefile" of some property.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
cube_data: list[PropertyCubePoint]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MolecularOrbitalCube(PropertyCube):
|
|
25
|
+
"""
|
|
26
|
+
Inherits `cube_data`.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
occupation: NonNegativeInt
|
|
30
|
+
energy: float
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ElectronicPropertiesWorkflow(Workflow):
|
|
34
|
+
"""
|
|
35
|
+
Workflow for computing electronic properties!
|
|
36
|
+
|
|
37
|
+
Inherited
|
|
38
|
+
:param initial_molecule: molecule of interest
|
|
39
|
+
|
|
40
|
+
Config settings:
|
|
41
|
+
:param settings: the level of theory to use
|
|
42
|
+
:param compute_density_cube: whether or not to compute the density on a cube
|
|
43
|
+
:param compute_electrostatic_potential_cube: whether or not to compute the electrostatic potential on a cube
|
|
44
|
+
:param compute_num_occupied_orbitals: how many occupied orbitals to save
|
|
45
|
+
:param compute_num_virtual_orbitals: how many virtual orbitals to save
|
|
46
|
+
|
|
47
|
+
Populated while running:
|
|
48
|
+
:param calculation: the UUID of the calculation
|
|
49
|
+
:param dipole: the dipole moment
|
|
50
|
+
:param quadrupole: the quadrupole moment
|
|
51
|
+
:param mulliken_charges: the Mulliken charges
|
|
52
|
+
:param lowdin_charges: the Lowdin charges
|
|
53
|
+
:param wiberg_bond_orders: the Wiberg bond orders (`atom1`, `atom2`, `order`)
|
|
54
|
+
:param mayer_bond_orders: the Mayer bond orders (`atom1`, `atom2`, `order`)
|
|
55
|
+
:param density_cube: the electron density, as a cube
|
|
56
|
+
:param electrostatic_potential_cube: the electrostatic potential, as a cube
|
|
57
|
+
:param molecular_orbitals_alpha: for open-shell species (UHF/ROHF), a dict containing the alpha MOs
|
|
58
|
+
(The key is the absolute orbital index.)
|
|
59
|
+
:param molecular_orbitals_beta: for open-shell species (UHF/ROHF), a dict containing the beta MOs
|
|
60
|
+
(The key is the absolute orbital index.)
|
|
61
|
+
:param molecular_orbitals: for closed-shell species (RHF), a dict containing the MOs
|
|
62
|
+
(The key is the absolute orbital index.)
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
settings: Settings
|
|
66
|
+
compute_density_cube: bool = True
|
|
67
|
+
compute_electrostatic_potential_cube: bool = True
|
|
68
|
+
compute_num_occupied_orbitals: NonNegativeInt = 1
|
|
69
|
+
compute_num_virtual_orbitals: NonNegativeInt = 1
|
|
70
|
+
|
|
71
|
+
calculation: UUID | None = None
|
|
72
|
+
|
|
73
|
+
dipole: Vector3D | None = None
|
|
74
|
+
quadrupole: Matrix3x3 | None = None
|
|
75
|
+
|
|
76
|
+
mulliken_charges: FloatPerAtom | None = None
|
|
77
|
+
lowdin_charges: FloatPerAtom | None = None
|
|
78
|
+
|
|
79
|
+
wiberg_bond_orders: list[tuple[NonNegativeInt, NonNegativeInt, NonNegativeFloat]] = []
|
|
80
|
+
mayer_bond_orders: list[tuple[NonNegativeInt, NonNegativeInt, NonNegativeFloat]] = []
|
|
81
|
+
|
|
82
|
+
density_cube: PropertyCube | None = None
|
|
83
|
+
electrostatic_potential_cube: PropertyCube | None = None
|
|
84
|
+
molecular_orbitals_alpha: dict[NonNegativeInt, MolecularOrbitalCube] = {}
|
|
85
|
+
molecular_orbitals_beta: dict[NonNegativeInt, MolecularOrbitalCube] = {}
|
|
86
|
+
molecular_orbitals: dict[NonNegativeInt, MolecularOrbitalCube] = {}
|