stjames 0.0.43__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.43/stjames.egg-info → stjames-0.0.45}/PKG-INFO +1 -1
- {stjames-0.0.43 → stjames-0.0.45}/pyproject.toml +1 -1
- stjames-0.0.45/stjames/constraint.py +70 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/correction.py +3 -0
- stjames-0.0.45/stjames/method.py +60 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/molecule.py +7 -7
- stjames-0.0.45/stjames/settings.py +216 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/types.py +2 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/workflows/__init__.py +1 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/workflows/bde.py +18 -25
- 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.43 → stjames-0.0.45}/stjames/workflows/molecular_dynamics.py +12 -3
- {stjames-0.0.43 → stjames-0.0.45}/stjames/workflows/multistage_opt.py +91 -14
- stjames-0.0.45/stjames/workflows/redox_potential.py +102 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/workflows/spin_states.py +2 -14
- {stjames-0.0.43 → stjames-0.0.45}/stjames/workflows/workflow.py +18 -22
- {stjames-0.0.43 → stjames-0.0.45/stjames.egg-info}/PKG-INFO +1 -1
- {stjames-0.0.43 → stjames-0.0.45}/stjames.egg-info/SOURCES.txt +5 -1
- stjames-0.0.45/tests/test_constraints.py +29 -0
- stjames-0.0.45/tests/test_settings.py +34 -0
- stjames-0.0.43/stjames/constraint.py +0 -36
- stjames-0.0.43/stjames/method.py +0 -71
- stjames-0.0.43/stjames/settings.py +0 -209
- stjames-0.0.43/stjames/workflows/redox_potential.py +0 -38
- {stjames-0.0.43 → stjames-0.0.45}/LICENSE +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/README.md +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/setup.cfg +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/__init__.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/_deprecated_solvent_settings.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/atom.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/base.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/basis_set.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/calculation.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/data/__init__.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/data/bragg_radii.json +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/data/elements.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/data/isotopes.json +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/data/nist_isotopes.json +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/data/read_nist_isotopes.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/data/symbol_element.json +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/diis_settings.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/grid_settings.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/int_settings.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/message.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/mode.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/opt_settings.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/periodic_cell.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/py.typed +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/scf_settings.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/solvent.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/status.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/task.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/thermochem_settings.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/workflows/admet.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/workflows/basic_calculation.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/workflows/conformer.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/workflows/descriptors.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/workflows/fukui.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/workflows/pka.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/workflows/scan.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames/workflows/tautomer.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames.egg-info/dependency_links.txt +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames.egg-info/requires.txt +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/stjames.egg-info/top_level.txt +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/tests/test_from_extxyz.py +0 -0
- {stjames-0.0.43 → stjames-0.0.45}/tests/test_molecule.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
|
|
@@ -55,6 +53,8 @@ class Molecule(Base):
|
|
|
55
53
|
thermal_enthalpy_corr: Optional[float] = None
|
|
56
54
|
thermal_free_energy_corr: Optional[float] = None
|
|
57
55
|
|
|
56
|
+
smiles: Optional[str] = None
|
|
57
|
+
|
|
58
58
|
def __len__(self) -> int:
|
|
59
59
|
return len(self.atoms)
|
|
60
60
|
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
from typing import Any, Optional, Self, TypeVar
|
|
2
|
+
|
|
3
|
+
from pydantic import computed_field, field_validator, model_validator
|
|
4
|
+
|
|
5
|
+
from .base import Base, UniqueList
|
|
6
|
+
from .basis_set import BasisSet
|
|
7
|
+
from .correction import Correction
|
|
8
|
+
from .method import METHODS_WITH_CORRECTION, PREPACKAGED_METHODS, Method
|
|
9
|
+
from .mode import Mode
|
|
10
|
+
from .opt_settings import OptimizationSettings
|
|
11
|
+
from .scf_settings import SCFSettings
|
|
12
|
+
from .solvent import SolventSettings
|
|
13
|
+
from .task import Task
|
|
14
|
+
from .thermochem_settings import ThermochemistrySettings
|
|
15
|
+
|
|
16
|
+
_T = TypeVar("_T")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Settings(Base):
|
|
20
|
+
mode: Mode = Mode.AUTO
|
|
21
|
+
|
|
22
|
+
method: Method = Method.HARTREE_FOCK
|
|
23
|
+
basis_set: Optional[BasisSet] = None
|
|
24
|
+
tasks: UniqueList[Task] = [Task.ENERGY, Task.CHARGE, Task.DIPOLE]
|
|
25
|
+
corrections: UniqueList[Correction] = []
|
|
26
|
+
|
|
27
|
+
solvent_settings: Optional[SolventSettings] = None
|
|
28
|
+
|
|
29
|
+
# scf/opt settings will be set automatically based on mode, but can be overridden manually
|
|
30
|
+
scf_settings: SCFSettings = SCFSettings()
|
|
31
|
+
opt_settings: OptimizationSettings = OptimizationSettings()
|
|
32
|
+
thermochem_settings: ThermochemistrySettings = ThermochemistrySettings()
|
|
33
|
+
|
|
34
|
+
# mypy has this dead wrong (https://docs.pydantic.dev/2.0/usage/computed_fields/)
|
|
35
|
+
# Python 3.12 narrows the reason for the ignore to prop-decorator
|
|
36
|
+
@computed_field # type: ignore[misc, prop-decorator, unused-ignore]
|
|
37
|
+
@property
|
|
38
|
+
def level_of_theory(self) -> str:
|
|
39
|
+
corrections = list(filter(lambda x: x not in (None, ""), self.corrections))
|
|
40
|
+
|
|
41
|
+
if self.method in PREPACKAGED_METHODS or self.basis_set is None:
|
|
42
|
+
method = self.method.value
|
|
43
|
+
elif self.method in METHODS_WITH_CORRECTION or len(corrections) == 0:
|
|
44
|
+
method = f"{self.method.value}/{self.basis_set.name.lower()}"
|
|
45
|
+
else:
|
|
46
|
+
method = f"{self.method.value}-{'-'.join([c.value for c in corrections])}/{self.basis_set.name.lower()}"
|
|
47
|
+
|
|
48
|
+
if self.solvent_settings is not None:
|
|
49
|
+
method += f"/{self.solvent_settings.model.value}({self.solvent_settings.solvent.value})"
|
|
50
|
+
|
|
51
|
+
return method
|
|
52
|
+
|
|
53
|
+
@field_validator("mode")
|
|
54
|
+
@classmethod
|
|
55
|
+
def set_mode_auto(cls, mode: Mode) -> Mode:
|
|
56
|
+
"""Set the mode to RAPID if AUTO is selected."""
|
|
57
|
+
if mode == Mode.AUTO:
|
|
58
|
+
return Mode.RAPID
|
|
59
|
+
|
|
60
|
+
return mode
|
|
61
|
+
|
|
62
|
+
@model_validator(mode="after")
|
|
63
|
+
def validate_and_build(self) -> Self:
|
|
64
|
+
if self.mode == Mode.AUTO:
|
|
65
|
+
self.mode = Mode.RAPID
|
|
66
|
+
|
|
67
|
+
self.scf_settings = _assign_scf_settings_by_mode(self.mode, self.scf_settings)
|
|
68
|
+
self.opt_settings = _assign_opt_settings_by_mode(self.mode, self.opt_settings)
|
|
69
|
+
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
def model_post_init(self, __context: Any) -> None:
|
|
73
|
+
# figure out `optimize_ts`
|
|
74
|
+
if Task.OPTIMIZE_TS in self.tasks:
|
|
75
|
+
self.tasks.pop(self.tasks.index(Task.OPTIMIZE_TS))
|
|
76
|
+
self.tasks.append(Task.OPTIMIZE)
|
|
77
|
+
self.opt_settings.transition_state = True
|
|
78
|
+
|
|
79
|
+
# composite methods have their own basis sets, so overwrite user stuff
|
|
80
|
+
if self.method == Method.HF3C:
|
|
81
|
+
self.basis_set = BasisSet(name="minix")
|
|
82
|
+
elif self.method == Method.B973C:
|
|
83
|
+
self.basis_set = BasisSet(name="def2-mTZVP")
|
|
84
|
+
elif self.method == Method.R2SCAN3C:
|
|
85
|
+
self.basis_set = BasisSet(name="def2-mTZVPP")
|
|
86
|
+
elif self.method == Method.WB97X3C:
|
|
87
|
+
self.basis_set = BasisSet(name="vDZP")
|
|
88
|
+
|
|
89
|
+
@field_validator("basis_set", mode="before")
|
|
90
|
+
@classmethod
|
|
91
|
+
def parse_basis_set(cls, v: Any) -> BasisSet | dict[str, Any] | None:
|
|
92
|
+
"""Turn a string into a ``BasisSet`` object. (This is a little crude.)"""
|
|
93
|
+
if isinstance(v, BasisSet):
|
|
94
|
+
return None if v.name is None else v
|
|
95
|
+
elif isinstance(v, dict):
|
|
96
|
+
return None if v.get("name") is None else v
|
|
97
|
+
elif isinstance(v, str):
|
|
98
|
+
if len(v):
|
|
99
|
+
return BasisSet(name=v)
|
|
100
|
+
# "" is basically None, let's be real here...
|
|
101
|
+
return None
|
|
102
|
+
elif v is None:
|
|
103
|
+
return None
|
|
104
|
+
else:
|
|
105
|
+
raise ValueError(f"invalid value ``{v}`` for ``basis_set``")
|
|
106
|
+
|
|
107
|
+
@field_validator("corrections", mode="before")
|
|
108
|
+
@classmethod
|
|
109
|
+
def remove_empty_string(cls, v: list[_T]) -> list[_T]:
|
|
110
|
+
"""Remove empty string values."""
|
|
111
|
+
return [c for c in v if c] if v is not None else v
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _assign_scf_settings_by_mode(mode: Mode, scf_settings: SCFSettings) -> SCFSettings:
|
|
115
|
+
"""
|
|
116
|
+
Assign SCF settings based on the mode.
|
|
117
|
+
|
|
118
|
+
Values based off of the following sources:
|
|
119
|
+
QChem:
|
|
120
|
+
- https://manual.q-chem.com/5.2/Ch4.S3.SS2.html
|
|
121
|
+
- https://manual.q-chem.com/5.2/Ch4.S5.SS2.html
|
|
122
|
+
|
|
123
|
+
Gaussian:
|
|
124
|
+
- https://gaussian.com/integral/
|
|
125
|
+
- https://gaussian.com/overlay5/
|
|
126
|
+
|
|
127
|
+
Orca:
|
|
128
|
+
- manual 4.2.1, §9.6.1 and §9.7.3
|
|
129
|
+
|
|
130
|
+
Psi4:
|
|
131
|
+
- https://psicode.org/psi4manual/master/autodir_options_c/module__scf.html
|
|
132
|
+
- https://psicode.org/psi4manual/master/autodoc_glossary_options_c.html
|
|
133
|
+
|
|
134
|
+
TeraChem:
|
|
135
|
+
- Manual, it's easy to locate everything.
|
|
136
|
+
|
|
137
|
+
The below values are my best attempt at homogenizing various sources.
|
|
138
|
+
In general, eri_threshold should be 3 OOM lower than SCF convergence.
|
|
139
|
+
"""
|
|
140
|
+
if mode == Mode.MANUAL:
|
|
141
|
+
return scf_settings
|
|
142
|
+
|
|
143
|
+
match mode:
|
|
144
|
+
case Mode.RECKLESS:
|
|
145
|
+
scf_settings.energy_threshold = 1e-5
|
|
146
|
+
scf_settings.rms_error_threshold = 1e-7
|
|
147
|
+
scf_settings.max_error_threshold = 1e-5
|
|
148
|
+
scf_settings.rebuild_frequency = 100
|
|
149
|
+
scf_settings.int_settings.eri_threshold = 1e-8
|
|
150
|
+
scf_settings.int_settings.csam_multiplier = 3.0
|
|
151
|
+
scf_settings.int_settings.pair_overlap_threshold = 1e-8
|
|
152
|
+
case Mode.RAPID | Mode.CAREFUL:
|
|
153
|
+
scf_settings.energy_threshold = 1e-6
|
|
154
|
+
scf_settings.rms_error_threshold = 1e-9
|
|
155
|
+
scf_settings.max_error_threshold = 1e-7
|
|
156
|
+
scf_settings.rebuild_frequency = 10
|
|
157
|
+
scf_settings.int_settings.eri_threshold = 1e-10
|
|
158
|
+
scf_settings.int_settings.csam_multiplier = 1.0
|
|
159
|
+
scf_settings.int_settings.pair_overlap_threshold = 1e-10
|
|
160
|
+
case Mode.METICULOUS:
|
|
161
|
+
scf_settings.energy_threshold = 1e-8
|
|
162
|
+
scf_settings.rms_error_threshold = 1e-9
|
|
163
|
+
scf_settings.max_error_threshold = 1e-7
|
|
164
|
+
scf_settings.rebuild_frequency = 5
|
|
165
|
+
scf_settings.int_settings.eri_threshold = 1e-12
|
|
166
|
+
scf_settings.int_settings.csam_multiplier = 1.0
|
|
167
|
+
scf_settings.int_settings.pair_overlap_threshold = 1e-12
|
|
168
|
+
case Mode.DEBUG:
|
|
169
|
+
scf_settings.energy_threshold = 1e-9
|
|
170
|
+
scf_settings.rms_error_threshold = 1e-10
|
|
171
|
+
scf_settings.max_error_threshold = 1e-9
|
|
172
|
+
scf_settings.rebuild_frequency = 1
|
|
173
|
+
scf_settings.int_settings.eri_threshold = 1e-14
|
|
174
|
+
scf_settings.int_settings.csam_multiplier = 1e10 # in other words, disable CSAM
|
|
175
|
+
scf_settings.int_settings.pair_overlap_threshold = 1e-14
|
|
176
|
+
case _:
|
|
177
|
+
raise ValueError(f"Unknown mode ``{mode.value}``!")
|
|
178
|
+
|
|
179
|
+
return scf_settings
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _assign_opt_settings_by_mode(mode: Mode, opt_settings: OptimizationSettings) -> OptimizationSettings:
|
|
183
|
+
"""
|
|
184
|
+
Assign optimization settings based on the mode.
|
|
185
|
+
|
|
186
|
+
Constraints lead to a lot of noise, so we need to loosen the thresholds.
|
|
187
|
+
|
|
188
|
+
cf. DLFIND manual, and https://www.cup.uni-muenchen.de/ch/compchem/geom/basic.html
|
|
189
|
+
and the discussion at https://geometric.readthedocs.io/en/latest/how-it-works.html
|
|
190
|
+
in periodic systems, "normal" is 0.05 eV/Å ~= 2e-3 Hartree/Å, and "careful" is 0.01 ~= 4e-4
|
|
191
|
+
|
|
192
|
+
Note: thresholds here are in units of Hartree/Å, not Hartree/Bohr as listed in many places.
|
|
193
|
+
"""
|
|
194
|
+
opt_settings.energy_threshold = 1e-6
|
|
195
|
+
match mode:
|
|
196
|
+
case Mode.RECKLESS:
|
|
197
|
+
opt_settings.energy_threshold = 2e-5
|
|
198
|
+
opt_settings.max_gradient_threshold = 7e-3
|
|
199
|
+
opt_settings.rms_gradient_threshold = 6e-3
|
|
200
|
+
case Mode.RAPID:
|
|
201
|
+
opt_settings.energy_threshold = 5e-5
|
|
202
|
+
opt_settings.max_gradient_threshold = 5e-3
|
|
203
|
+
opt_settings.rms_gradient_threshold = 3.5e-3
|
|
204
|
+
case Mode.CAREFUL:
|
|
205
|
+
opt_settings.max_gradient_threshold = 9e-4
|
|
206
|
+
opt_settings.rms_gradient_threshold = 6e-4
|
|
207
|
+
case Mode.METICULOUS:
|
|
208
|
+
opt_settings.max_gradient_threshold = 3e-5
|
|
209
|
+
opt_settings.rms_gradient_threshold = 2e-5
|
|
210
|
+
case Mode.DEBUG:
|
|
211
|
+
opt_settings.max_gradient_threshold = 4e-6
|
|
212
|
+
opt_settings.rms_gradient_threshold = 2e-6
|
|
213
|
+
case _:
|
|
214
|
+
raise ValueError(f"Unknown mode ``{mode.value}``!")
|
|
215
|
+
|
|
216
|
+
return opt_settings
|
|
@@ -56,8 +56,9 @@ class BDEWorkflow(Workflow, MultiStageOptMixin):
|
|
|
56
56
|
|
|
57
57
|
Inherited:
|
|
58
58
|
:param initial_molecule: Molecule of interest
|
|
59
|
+
:param mode: Mode for workflow
|
|
59
60
|
:param multistage_opt_settings: set by mode unless mode=MANUAL (ignores additional settings if set)
|
|
60
|
-
:param solvent: solvent to use
|
|
61
|
+
:param solvent: solvent to use for singlepoint
|
|
61
62
|
:param xtb_preopt: pre-optimize with xtb (sets based on mode when None)
|
|
62
63
|
|
|
63
64
|
Overridden:
|
|
@@ -69,7 +70,6 @@ class BDEWorkflow(Workflow, MultiStageOptMixin):
|
|
|
69
70
|
:param transition_state: whether this is a transition state (not supported)
|
|
70
71
|
|
|
71
72
|
New:
|
|
72
|
-
:param mode: Mode for workflow
|
|
73
73
|
:param optimize_fragments: whether to optimize the fragments, or just the starting molecule (default depends on mode)
|
|
74
74
|
:param atoms: atoms to dissociate (1-indexed)
|
|
75
75
|
:param fragment_indices: fragments to dissociate (all fields feed into this, 1-indexed)
|
|
@@ -80,7 +80,6 @@ class BDEWorkflow(Workflow, MultiStageOptMixin):
|
|
|
80
80
|
:param bdes: BDE results
|
|
81
81
|
"""
|
|
82
82
|
|
|
83
|
-
mode: Mode
|
|
84
83
|
mso_mode: Mode = _sentinel_mso_mode # type: ignore [assignment]
|
|
85
84
|
frequencies: bool = False
|
|
86
85
|
optimize_fragments: bool = None # type: ignore [assignment]
|
|
@@ -107,14 +106,9 @@ class BDEWorkflow(Workflow, MultiStageOptMixin):
|
|
|
107
106
|
"""
|
|
108
107
|
return f"{type(self).__name__} {self.mode.name}\n" + "\n".join(map(str, self.fragment_indices))
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
>>> BDEWorkflow(initial_molecule=Molecule.from_xyz("He 0 0 0"), mode=Mode.METICULOUS, atoms=[])
|
|
115
|
-
<BDEWorkflow METICULOUS>
|
|
116
|
-
"""
|
|
117
|
-
return f"<{type(self).__name__} {self.mode.name}>"
|
|
109
|
+
@property
|
|
110
|
+
def level_of_theory(self) -> str:
|
|
111
|
+
return self.multistage_opt_settings.level_of_theory
|
|
118
112
|
|
|
119
113
|
@property
|
|
120
114
|
def energies(self) -> tuple[float | None, ...]:
|
|
@@ -128,22 +122,21 @@ class BDEWorkflow(Workflow, MultiStageOptMixin):
|
|
|
128
122
|
|
|
129
123
|
return value
|
|
130
124
|
|
|
131
|
-
@field_validator("mode")
|
|
132
|
-
@classmethod
|
|
133
|
-
def set_mode_auto(cls, mode: Mode) -> Mode:
|
|
134
|
-
if mode == Mode.AUTO:
|
|
135
|
-
return Mode.RAPID
|
|
136
|
-
|
|
137
|
-
return mode
|
|
138
|
-
|
|
139
125
|
@field_validator("initial_molecule", mode="before")
|
|
140
126
|
@classmethod
|
|
141
|
-
def no_charge_or_spin(cls,
|
|
127
|
+
def no_charge_or_spin(cls, val: Molecule | dict[str, Any]) -> Molecule | dict[str, Any]:
|
|
142
128
|
"""Ensure the molecule has no charge or spin."""
|
|
129
|
+
if isinstance(val, dict):
|
|
130
|
+
mol = Molecule(**val)
|
|
131
|
+
elif isinstance(val, Molecule):
|
|
132
|
+
mol = val
|
|
133
|
+
else:
|
|
134
|
+
raise ValueError(f"{val=} is not a Molecule.")
|
|
135
|
+
|
|
143
136
|
if mol.charge != 0 or mol.multiplicity != 1:
|
|
144
137
|
raise ValueError("Charge and spin partitioning undefined for BDE, only neutral singlet molecules supported.")
|
|
145
138
|
|
|
146
|
-
return
|
|
139
|
+
return val
|
|
147
140
|
|
|
148
141
|
@model_validator(mode="before")
|
|
149
142
|
@classmethod
|
|
@@ -159,10 +152,10 @@ class BDEWorkflow(Workflow, MultiStageOptMixin):
|
|
|
159
152
|
self.fragment_indices = tuple(map(tuple, self.fragment_indices))
|
|
160
153
|
|
|
161
154
|
match self.mode:
|
|
162
|
-
case Mode.RECKLESS
|
|
163
|
-
#
|
|
164
|
-
self.optimize_fragments =
|
|
165
|
-
case Mode.CAREFUL | Mode.METICULOUS:
|
|
155
|
+
case Mode.RECKLESS:
|
|
156
|
+
# GFN-FF doesn't support open-shell species
|
|
157
|
+
self.optimize_fragments = False
|
|
158
|
+
case Mode.RAPID | Mode.CAREFUL | Mode.METICULOUS:
|
|
166
159
|
# Default on
|
|
167
160
|
self.optimize_fragments = self.optimize_fragments or self.optimize_fragments is None
|
|
168
161
|
case _:
|