stjames 0.0.48__tar.gz → 0.0.50__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.48/stjames.egg-info → stjames-0.0.50}/PKG-INFO +2 -1
- {stjames-0.0.48 → stjames-0.0.50}/pyproject.toml +2 -1
- {stjames-0.0.48 → stjames-0.0.50}/stjames/__init__.py +1 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/atom.py +4 -4
- {stjames-0.0.48 → stjames-0.0.50}/stjames/base.py +21 -1
- stjames-0.0.50/stjames/compute_settings.py +12 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/method.py +4 -2
- {stjames-0.0.48 → stjames-0.0.50}/stjames/molecule.py +50 -21
- {stjames-0.0.48 → stjames-0.0.50}/stjames/periodic_cell.py +3 -3
- {stjames-0.0.48 → stjames-0.0.50}/stjames/settings.py +2 -0
- stjames-0.0.50/stjames/types.py +98 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/__init__.py +7 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/bde.py +4 -3
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/conformer.py +6 -4
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/conformer_search.py +4 -29
- stjames-0.0.50/stjames/workflows/docking.py +80 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/electronic_properties.py +8 -17
- stjames-0.0.50/stjames/workflows/fukui.py +17 -0
- stjames-0.0.50/stjames/workflows/irc.py +90 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/pka.py +8 -6
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/redox_potential.py +5 -4
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/scan.py +5 -2
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/spin_states.py +4 -3
- stjames-0.0.50/stjames/workflows/tautomer.py +21 -0
- {stjames-0.0.48 → stjames-0.0.50/stjames.egg-info}/PKG-INFO +2 -1
- {stjames-0.0.48 → stjames-0.0.50}/stjames.egg-info/SOURCES.txt +4 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames.egg-info/requires.txt +1 -0
- stjames-0.0.50/tests/test_rounding.py +52 -0
- stjames-0.0.48/stjames/types.py +0 -10
- stjames-0.0.48/stjames/workflows/fukui.py +0 -12
- stjames-0.0.48/stjames/workflows/tautomer.py +0 -19
- {stjames-0.0.48 → stjames-0.0.50}/LICENSE +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/README.md +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/setup.cfg +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/_deprecated_solvent_settings.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/basis_set.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/calculation.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/constraint.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/correction.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/data/__init__.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/data/bragg_radii.json +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/data/elements.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/data/isotopes.json +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/data/nist_isotopes.json +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/data/read_nist_isotopes.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/data/symbol_element.json +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/diis_settings.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/grid_settings.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/int_settings.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/message.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/mode.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/opt_settings.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/pdb.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/py.typed +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/scf_settings.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/solvent.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/status.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/task.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/thermochem_settings.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/admet.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/basic_calculation.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/descriptors.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/hydrogen_bond_basicity.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/molecular_dynamics.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/multistage_opt.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames/workflows/workflow.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames.egg-info/dependency_links.txt +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/stjames.egg-info/top_level.txt +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/tests/test_constraints.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/tests/test_from_extxyz.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/tests/test_molecule.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/tests/test_pdb.py +0 -0
- {stjames-0.0.48 → stjames-0.0.50}/tests/test_settings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: stjames
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.50
|
|
4
4
|
Summary: standardized JSON atom/molecule encoding scheme
|
|
5
5
|
Author-email: Corin Wagen <corin@rowansci.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/rowansci/stjames
|
|
@@ -11,6 +11,7 @@ License-File: LICENSE
|
|
|
11
11
|
Requires-Dist: atomium<2,>=1
|
|
12
12
|
Requires-Dist: pydantic>=2.4
|
|
13
13
|
Requires-Dist: numpy
|
|
14
|
+
Requires-Dist: atomium<2.0,>=1.0
|
|
14
15
|
|
|
15
16
|
# stjames
|
|
16
17
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "stjames"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.50"
|
|
4
4
|
description = "standardized JSON atom/molecule encoding scheme"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -12,6 +12,7 @@ dependencies = [
|
|
|
12
12
|
"atomium>=1,<2",
|
|
13
13
|
"pydantic>=2.4",
|
|
14
14
|
"numpy",
|
|
15
|
+
"atomium>=1.0,<2.0",
|
|
15
16
|
]
|
|
16
17
|
|
|
17
18
|
[build-system]
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
from typing import Self, Sequence
|
|
1
|
+
from typing import Annotated, Self, Sequence
|
|
2
2
|
|
|
3
|
-
from pydantic import NonNegativeInt
|
|
3
|
+
from pydantic import AfterValidator, NonNegativeInt
|
|
4
4
|
|
|
5
5
|
from .base import Base
|
|
6
6
|
from .data import ELEMENT_SYMBOL, SYMBOL_ELEMENT
|
|
7
|
-
from .types import Vector3D
|
|
7
|
+
from .types import Vector3D, round_vector3d
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class Atom(Base):
|
|
11
11
|
atomic_number: NonNegativeInt
|
|
12
|
-
position: Vector3D # in Å
|
|
12
|
+
position: Annotated[Vector3D, AfterValidator(round_vector3d(8))] # in Å
|
|
13
13
|
|
|
14
14
|
def __repr__(self) -> str:
|
|
15
15
|
"""
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
-
from typing import Annotated, Any, Hashable, TypeVar
|
|
2
|
+
from typing import Annotated, Any, Callable, Hashable, Optional, TypeVar
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
import pydantic
|
|
@@ -7,6 +7,26 @@ import pydantic
|
|
|
7
7
|
_T = TypeVar("_T")
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
def round_float(round_to: int) -> Callable[[float], float]:
|
|
11
|
+
"""Return a function that rounds a float to a given number of decimal places."""
|
|
12
|
+
|
|
13
|
+
def inner_round(v: float) -> float:
|
|
14
|
+
return round(v, round_to)
|
|
15
|
+
|
|
16
|
+
return inner_round
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def round_optional_float(round_to: int) -> Callable[[Optional[float]], Optional[float]]:
|
|
20
|
+
"""Create a validator that rounds an optional float to a given number of decimal places."""
|
|
21
|
+
|
|
22
|
+
def rounder(value: Optional[float]) -> Optional[float]:
|
|
23
|
+
if value is None:
|
|
24
|
+
return None
|
|
25
|
+
return round(value, round_to)
|
|
26
|
+
|
|
27
|
+
return rounder
|
|
28
|
+
|
|
29
|
+
|
|
10
30
|
class Base(pydantic.BaseModel):
|
|
11
31
|
@pydantic.field_validator("*", mode="before")
|
|
12
32
|
@classmethod
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .base import Base, LowercaseStrEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ComputeType(LowercaseStrEnum):
|
|
5
|
+
CPU = "cpu"
|
|
6
|
+
GPU = "gpu"
|
|
7
|
+
AUTO = "auto"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ComputeSettings(Base):
|
|
11
|
+
requested_compute_type: ComputeType = ComputeType.CPU
|
|
12
|
+
compute_type_used: ComputeType | None = None
|
|
@@ -47,9 +47,11 @@ class Method(LowercaseStrEnum):
|
|
|
47
47
|
|
|
48
48
|
OFF_SAGE_2_2_1 = "off_sage_2_2_1"
|
|
49
49
|
|
|
50
|
+
RM1 = "rm1"
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
|
|
53
|
+
PrepackagedNNPMethod = Literal[Method.AIMNET2_WB97MD3, Method.OCP24_S, Method.OCP24_L, Method.RM1]
|
|
54
|
+
PREPACKAGED_NNP_METHODS = [Method.AIMNET2_WB97MD3, Method.OCP24_S, Method.OCP24_L, Method.RM1]
|
|
53
55
|
|
|
54
56
|
CorrectableNNPMethod = Literal[Method.MACE_MP_0B2_L, Method.ORB_V2]
|
|
55
57
|
CORRECTABLE_NNP_METHODS = [Method.MACE_MP_0B2_L, Method.ORB_V2]
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Iterable, Optional, Self
|
|
3
|
+
from typing import Annotated, Iterable, Optional, Self
|
|
4
4
|
|
|
5
5
|
import pydantic
|
|
6
|
-
from pydantic import NonNegativeInt, PositiveInt, ValidationError
|
|
6
|
+
from pydantic import AfterValidator, NonNegativeInt, PositiveInt, ValidationError
|
|
7
7
|
|
|
8
8
|
from .atom import Atom
|
|
9
|
-
from .base import Base
|
|
9
|
+
from .base import Base, round_float, round_optional_float
|
|
10
10
|
from .periodic_cell import PeriodicCell
|
|
11
|
-
from .types import
|
|
11
|
+
from .types import (
|
|
12
|
+
FloatPerAtom,
|
|
13
|
+
Matrix3x3,
|
|
14
|
+
Vector3D,
|
|
15
|
+
Vector3DPerAtom,
|
|
16
|
+
round_optional_float_per_atom,
|
|
17
|
+
round_optional_matrix3x3,
|
|
18
|
+
round_optional_vector3d,
|
|
19
|
+
round_optional_vector3d_per_atom,
|
|
20
|
+
round_vector3d_per_atom,
|
|
21
|
+
)
|
|
12
22
|
|
|
13
23
|
|
|
14
24
|
class MoleculeReadError(RuntimeError):
|
|
@@ -16,10 +26,10 @@ class MoleculeReadError(RuntimeError):
|
|
|
16
26
|
|
|
17
27
|
|
|
18
28
|
class VibrationalMode(Base):
|
|
19
|
-
frequency: float # in cm-1
|
|
20
|
-
reduced_mass: float # amu
|
|
21
|
-
force_constant: float # mDyne/Å
|
|
22
|
-
displacements: Vector3DPerAtom # Å
|
|
29
|
+
frequency: Annotated[float, AfterValidator(round_float(3))] # in cm-1
|
|
30
|
+
reduced_mass: Annotated[float, AfterValidator(round_float(3))] # amu
|
|
31
|
+
force_constant: Annotated[float, AfterValidator(round_float(3))] # mDyne/Å
|
|
32
|
+
displacements: Annotated[Vector3DPerAtom, AfterValidator(round_vector3d_per_atom(6))] # Å
|
|
23
33
|
|
|
24
34
|
|
|
25
35
|
class Molecule(Base):
|
|
@@ -30,28 +40,28 @@ class Molecule(Base):
|
|
|
30
40
|
# for periodic boundary conditions
|
|
31
41
|
cell: Optional[PeriodicCell] = None
|
|
32
42
|
|
|
33
|
-
energy: Optional[float] = None # in Hartree
|
|
43
|
+
energy: Annotated[Optional[float], AfterValidator(round_optional_float(6))] = None # in Hartree
|
|
34
44
|
scf_iterations: Optional[NonNegativeInt] = None
|
|
35
45
|
scf_completed: Optional[bool] = None
|
|
36
|
-
elapsed: Optional[float] = None # in seconds
|
|
46
|
+
elapsed: Annotated[Optional[float], AfterValidator(round_optional_float(3))] = None # in seconds
|
|
37
47
|
|
|
38
|
-
homo_lumo_gap: Optional[float] = None # in eV
|
|
48
|
+
homo_lumo_gap: Annotated[Optional[float], AfterValidator(round_optional_float(6))] = None # in eV
|
|
39
49
|
|
|
40
|
-
gradient: Optional[Vector3DPerAtom] = None # Hartree/Å
|
|
41
|
-
stress: Optional[Matrix3x3] = None # Hartree/Å
|
|
50
|
+
gradient: Annotated[Optional[Vector3DPerAtom], AfterValidator(round_optional_vector3d_per_atom(6))] = None # Hartree/Å
|
|
51
|
+
stress: Annotated[Optional[Matrix3x3], AfterValidator(round_optional_matrix3x3(6))] = None # Hartree/Å
|
|
42
52
|
|
|
43
|
-
velocities: Optional[Vector3DPerAtom] = None # Å/fs
|
|
53
|
+
velocities: Annotated[Optional[Vector3DPerAtom], AfterValidator(round_optional_vector3d_per_atom(6))] = None # Å/fs
|
|
44
54
|
|
|
45
|
-
mulliken_charges: FloatPerAtom | None = None
|
|
46
|
-
mulliken_spin_densities: FloatPerAtom | None = None
|
|
47
|
-
dipole: Optional[Vector3D] = None # in Debye
|
|
55
|
+
mulliken_charges: Annotated[FloatPerAtom | None, AfterValidator(round_optional_float_per_atom(6))] = None
|
|
56
|
+
mulliken_spin_densities: Annotated[FloatPerAtom | None, AfterValidator(round_optional_float_per_atom(6))] = None
|
|
57
|
+
dipole: Annotated[Optional[Vector3D], AfterValidator(round_optional_vector3d(6))] = None # in Debye
|
|
48
58
|
|
|
49
59
|
vibrational_modes: Optional[list[VibrationalMode]] = None
|
|
50
60
|
|
|
51
|
-
zero_point_energy: Optional[float] = None
|
|
52
|
-
thermal_energy_corr: Optional[float] = None
|
|
53
|
-
thermal_enthalpy_corr: Optional[float] = None
|
|
54
|
-
thermal_free_energy_corr: Optional[float] = None
|
|
61
|
+
zero_point_energy: Annotated[Optional[float], AfterValidator(round_optional_float(6))] = None
|
|
62
|
+
thermal_energy_corr: Annotated[Optional[float], AfterValidator(round_optional_float(6))] = None
|
|
63
|
+
thermal_enthalpy_corr: Annotated[Optional[float], AfterValidator(round_optional_float(6))] = None
|
|
64
|
+
thermal_free_energy_corr: Annotated[Optional[float], AfterValidator(round_optional_float(6))] = None
|
|
55
65
|
|
|
56
66
|
smiles: Optional[str] = None
|
|
57
67
|
|
|
@@ -72,6 +82,25 @@ class Molecule(Base):
|
|
|
72
82
|
def coordinates(self) -> Vector3DPerAtom:
|
|
73
83
|
return [a.position for a in self.atoms]
|
|
74
84
|
|
|
85
|
+
def translated(self, vector: Vector3D) -> Self:
|
|
86
|
+
r"""
|
|
87
|
+
Translate the molecule by a vector.
|
|
88
|
+
|
|
89
|
+
>>> mol = Molecule.from_xyz("H 0 0 0\nH 0 0 1")
|
|
90
|
+
>>> print(mol.translated((1, 0, 0)).to_xyz())
|
|
91
|
+
2
|
|
92
|
+
<BLANKLINE>
|
|
93
|
+
H 1.0000000000 0.0000000000 0.0000000000
|
|
94
|
+
H 1.0000000000 0.0000000000 1.0000000000
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def translated(position: Vector3D) -> Vector3D:
|
|
98
|
+
return tuple(q + v for q, v in zip(position, vector, strict=True)) # type: ignore [return-value]
|
|
99
|
+
|
|
100
|
+
atoms = [atom.copy(update={"position": translated(atom.position)}) for atom in self.atoms]
|
|
101
|
+
|
|
102
|
+
return self.copy(update={"atoms": atoms})
|
|
103
|
+
|
|
75
104
|
@property
|
|
76
105
|
def atomic_numbers(self) -> list[NonNegativeInt]:
|
|
77
106
|
return [a.atomic_number for a in self.atoms]
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
from typing import TypeAlias
|
|
1
|
+
from typing import Annotated, TypeAlias
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
import pydantic
|
|
5
5
|
|
|
6
6
|
from .base import Base
|
|
7
|
-
from .types import Matrix3x3
|
|
7
|
+
from .types import Matrix3x3, round_matrix3x3
|
|
8
8
|
|
|
9
9
|
Bool3: TypeAlias = tuple[bool, bool, bool]
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class PeriodicCell(Base):
|
|
13
|
-
lattice_vectors: Matrix3x3
|
|
13
|
+
lattice_vectors: Annotated[Matrix3x3, pydantic.AfterValidator(round_matrix3x3(6))]
|
|
14
14
|
is_periodic: Bool3 = (True, True, True)
|
|
15
15
|
|
|
16
16
|
@pydantic.field_validator("lattice_vectors")
|
|
@@ -4,6 +4,7 @@ from pydantic import computed_field, field_validator, model_validator
|
|
|
4
4
|
|
|
5
5
|
from .base import Base, UniqueList
|
|
6
6
|
from .basis_set import BasisSet
|
|
7
|
+
from .compute_settings import ComputeSettings
|
|
7
8
|
from .correction import Correction
|
|
8
9
|
from .method import CORRECTABLE_NNP_METHODS, METHODS_WITH_CORRECTION, PREPACKAGED_METHODS, Method
|
|
9
10
|
from .mode import Mode
|
|
@@ -30,6 +31,7 @@ class Settings(Base):
|
|
|
30
31
|
scf_settings: SCFSettings = SCFSettings()
|
|
31
32
|
opt_settings: OptimizationSettings = OptimizationSettings()
|
|
32
33
|
thermochem_settings: ThermochemistrySettings = ThermochemistrySettings()
|
|
34
|
+
compute_settings: ComputeSettings = ComputeSettings()
|
|
33
35
|
|
|
34
36
|
# mypy has this dead wrong (https://docs.pydantic.dev/2.0/usage/computed_fields/)
|
|
35
37
|
# Python 3.12 narrows the reason for the ignore to prop-decorator
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from typing import Callable, TypeAlias
|
|
2
|
+
|
|
3
|
+
UUID: TypeAlias = str
|
|
4
|
+
|
|
5
|
+
Vector3D: TypeAlias = tuple[float, float, float]
|
|
6
|
+
Vector3DPerAtom: TypeAlias = list[Vector3D]
|
|
7
|
+
|
|
8
|
+
FloatPerAtom: TypeAlias = list[float]
|
|
9
|
+
|
|
10
|
+
Matrix3x3: TypeAlias = tuple[Vector3D, Vector3D, Vector3D]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def round_vector3d(round_to: int = 6) -> Callable[[Vector3D], Vector3D]:
|
|
14
|
+
"""Create a validator that rounds each component of a Vector3D to a given number of decimal places."""
|
|
15
|
+
|
|
16
|
+
def rounder(vector: Vector3D) -> Vector3D:
|
|
17
|
+
return (round(vector[0], round_to), round(vector[1], round_to), round(vector[2], round_to))
|
|
18
|
+
|
|
19
|
+
return rounder
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def round_optional_vector3d(round_to: int = 6) -> Callable[[Vector3D | None], Vector3D | None]:
|
|
23
|
+
"""Create a validator that rounds each component of a Vector3D to a given number of decimal places, handling None."""
|
|
24
|
+
|
|
25
|
+
def rounder(vector: Vector3D | None) -> Vector3D | None:
|
|
26
|
+
if vector is None:
|
|
27
|
+
return None
|
|
28
|
+
return (round(vector[0], round_to), round(vector[1], round_to), round(vector[2], round_to))
|
|
29
|
+
|
|
30
|
+
return rounder
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def round_vector3d_per_atom(round_to: int = 6) -> Callable[[Vector3DPerAtom], Vector3DPerAtom]:
|
|
34
|
+
"""Create a validator that rounds each vector in Vector3DPerAtom to a given number of decimal places."""
|
|
35
|
+
vector_rounder = round_vector3d(round_to)
|
|
36
|
+
|
|
37
|
+
def rounder(vectors: Vector3DPerAtom) -> Vector3DPerAtom:
|
|
38
|
+
return [vector_rounder(v) for v in vectors]
|
|
39
|
+
|
|
40
|
+
return rounder
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def round_optional_vector3d_per_atom(round_to: int = 6) -> Callable[[Vector3DPerAtom | None], Vector3DPerAtom | None]:
|
|
44
|
+
"""Create a validator that rounds each vector in Vector3DPerAtom to a given number of decimal places, handling None."""
|
|
45
|
+
vector_rounder = round_vector3d(round_to)
|
|
46
|
+
|
|
47
|
+
def rounder(vectors: Vector3DPerAtom | None) -> Vector3DPerAtom | None:
|
|
48
|
+
if vectors is None:
|
|
49
|
+
return None
|
|
50
|
+
return [vector_rounder(v) for v in vectors]
|
|
51
|
+
|
|
52
|
+
return rounder
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def round_matrix3x3(round_to: int = 6) -> Callable[[Matrix3x3], Matrix3x3]:
|
|
56
|
+
"""Create a validator that rounds each vector in a Matrix3x3 to a given number of decimal places."""
|
|
57
|
+
|
|
58
|
+
# Use the round_vector3d function to round each Vector3D in the Matrix3x3
|
|
59
|
+
vector_rounder = round_vector3d(round_to)
|
|
60
|
+
|
|
61
|
+
def rounder(matrix: Matrix3x3) -> Matrix3x3:
|
|
62
|
+
return (vector_rounder(matrix[0]), vector_rounder(matrix[1]), vector_rounder(matrix[2]))
|
|
63
|
+
|
|
64
|
+
return rounder
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def round_optional_matrix3x3(round_to: int = 3) -> Callable[[Matrix3x3 | None], Matrix3x3 | None]:
|
|
68
|
+
"""Create a validator that rounds each vector in an Optional Matrix3x3 to a given number of decimal places."""
|
|
69
|
+
|
|
70
|
+
# Use the round_vector3d function to round each Vector3D in the Matrix3x3
|
|
71
|
+
vector_rounder = round_vector3d(round_to)
|
|
72
|
+
|
|
73
|
+
def rounder(matrix: Matrix3x3 | None) -> Matrix3x3 | None:
|
|
74
|
+
if matrix is None:
|
|
75
|
+
return None
|
|
76
|
+
return (vector_rounder(matrix[0]), vector_rounder(matrix[1]), vector_rounder(matrix[2]))
|
|
77
|
+
|
|
78
|
+
return rounder
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def round_optional_float_per_atom(round_to: int = 6) -> Callable[[FloatPerAtom | None], FloatPerAtom | None]:
|
|
82
|
+
"""Create a validator that rounds each float in FloatPerAtom to a given number of decimal places, handling None."""
|
|
83
|
+
|
|
84
|
+
def rounder(values: FloatPerAtom | None) -> FloatPerAtom | None:
|
|
85
|
+
if values is None:
|
|
86
|
+
return None
|
|
87
|
+
return [round(value, round_to) for value in values]
|
|
88
|
+
|
|
89
|
+
return rounder
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def round_float_per_atom(round_to: int = 6) -> Callable[[FloatPerAtom], FloatPerAtom]:
|
|
93
|
+
"""Create a validator that rounds each float in FloatPerAtom to a given number of decimal places, handling None."""
|
|
94
|
+
|
|
95
|
+
def rounder(values: FloatPerAtom) -> FloatPerAtom:
|
|
96
|
+
return [round(value, round_to) for value in values]
|
|
97
|
+
|
|
98
|
+
return rounder
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# ruff: noqa: F405
|
|
2
|
+
|
|
2
3
|
from typing import Literal
|
|
3
4
|
|
|
4
5
|
from .admet import *
|
|
@@ -7,9 +8,11 @@ from .bde import *
|
|
|
7
8
|
from .conformer import *
|
|
8
9
|
from .conformer_search import *
|
|
9
10
|
from .descriptors import *
|
|
11
|
+
from .docking import *
|
|
10
12
|
from .electronic_properties import *
|
|
11
13
|
from .fukui import *
|
|
12
14
|
from .hydrogen_bond_basicity import *
|
|
15
|
+
from .irc import *
|
|
13
16
|
from .molecular_dynamics import *
|
|
14
17
|
from .multistage_opt import *
|
|
15
18
|
from .pka import *
|
|
@@ -26,9 +29,11 @@ WORKFLOW_NAME = Literal[
|
|
|
26
29
|
"conformers",
|
|
27
30
|
"conformer_search",
|
|
28
31
|
"descriptors",
|
|
32
|
+
"docking",
|
|
29
33
|
"electronic_properties",
|
|
30
34
|
"fukui",
|
|
31
35
|
"hydrogen_bond_basicity",
|
|
36
|
+
"irc",
|
|
32
37
|
"molecular_dynamics",
|
|
33
38
|
"multistage_opt",
|
|
34
39
|
"pka",
|
|
@@ -45,9 +50,11 @@ WORKFLOW_MAPPING: dict[str, Workflow] = {
|
|
|
45
50
|
"conformers": ConformerWorkflow, # type: ignore [dict-item]
|
|
46
51
|
"conformer_search": ConformerSearchWorkflow, # type: ignore [dict-item]
|
|
47
52
|
"descriptors": DescriptorsWorkflow, # type: ignore [dict-item]
|
|
53
|
+
"docking": DockingWorkflow, # type: ignore [dict-item]
|
|
48
54
|
"electronic_properties": ElectronicPropertiesWorkflow, # type: ignore [dict-item]
|
|
49
55
|
"fukui": FukuiIndexWorkflow, # type: ignore [dict-item]
|
|
50
56
|
"hydrogen_bond_basicity": HydrogenBondBasicityWorkflow, # type: ignore [dict-item]
|
|
57
|
+
"irc": IRCWorkflow, # type: ignore [dict-item]
|
|
51
58
|
"molecular_dynamics": MolecularDynamicsWorkflow, # type: ignore [dict-item]
|
|
52
59
|
"multistage_opt": MultiStageOptWorkflow, # type: ignore [dict-item]
|
|
53
60
|
"pka": pKaWorkflow, # type: ignore [dict-item]
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""Bond Dissociation Energy (BDE) workflow."""
|
|
2
2
|
|
|
3
3
|
import itertools
|
|
4
|
-
from typing import Any, Iterable, Self, TypeVar
|
|
4
|
+
from typing import Annotated, Any, Iterable, Self, TypeVar
|
|
5
5
|
|
|
6
|
-
from pydantic import BaseModel, Field, PositiveInt, ValidationInfo, field_validator, model_validator
|
|
6
|
+
from pydantic import AfterValidator, BaseModel, Field, PositiveInt, ValidationInfo, field_validator, model_validator
|
|
7
7
|
|
|
8
|
+
from ..base import round_optional_float
|
|
8
9
|
from ..mode import Mode
|
|
9
10
|
from ..molecule import Molecule
|
|
10
11
|
from ..types import UUID
|
|
@@ -29,7 +30,7 @@ class BDE(BaseModel):
|
|
|
29
30
|
"""
|
|
30
31
|
|
|
31
32
|
fragment_idxs: tuple[PositiveInt, ...]
|
|
32
|
-
energy: float | None
|
|
33
|
+
energy: Annotated[float | None, AfterValidator(round_optional_float(6))]
|
|
33
34
|
fragment_energies: tuple[float | None, float | None]
|
|
34
35
|
calculation_uuids: tuple[list[UUID | None], list[UUID | None]]
|
|
35
36
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
from typing import Any, Optional
|
|
1
|
+
from typing import Annotated, Any, Optional
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from pydantic import AfterValidator
|
|
4
|
+
|
|
5
|
+
from ..base import Base, round_float, round_optional_float
|
|
4
6
|
from ..constraint import Constraint
|
|
5
7
|
from ..method import Method
|
|
6
8
|
from ..mode import Mode
|
|
@@ -33,8 +35,8 @@ class CrestConformerSettings(ConformerSettings):
|
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
class Conformer(Base):
|
|
36
|
-
energy: float
|
|
37
|
-
weight:
|
|
38
|
+
energy: Annotated[float, AfterValidator(round_float(6))]
|
|
39
|
+
weight: Annotated[float | None, AfterValidator(round_optional_float(6))] = None
|
|
38
40
|
|
|
39
41
|
# uuid, optionally
|
|
40
42
|
uuid: Optional[str] = None
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
"""Conformer Search Workflow."""
|
|
2
2
|
|
|
3
3
|
from abc import ABC
|
|
4
|
-
from typing import Self, Sequence, TypeVar
|
|
4
|
+
from typing import Annotated, Self, Sequence, TypeVar
|
|
5
5
|
|
|
6
|
-
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
6
|
+
from pydantic import AfterValidator, BaseModel, Field, field_validator, model_validator
|
|
7
7
|
|
|
8
8
|
from ..base import LowercaseStrEnum
|
|
9
9
|
from ..constraint import Constraint
|
|
10
10
|
from ..method import Method, XTBMethod
|
|
11
11
|
from ..mode import Mode
|
|
12
|
-
from ..
|
|
13
|
-
from ..types import UUID
|
|
12
|
+
from ..types import UUID, FloatPerAtom, round_float_per_atom
|
|
14
13
|
from .multistage_opt import MultiStageOptMixin
|
|
15
14
|
from .workflow import Workflow
|
|
16
15
|
|
|
@@ -323,30 +322,6 @@ class ConformerSearchMixin(ConformerGenMixin, MultiStageOptMixin):
|
|
|
323
322
|
"""Return a string representation of the ConformerSearch workflow."""
|
|
324
323
|
return f"<{type(self).__name__} {self.conf_gen_mode.name} {self.mso_mode.name}>"
|
|
325
324
|
|
|
326
|
-
@model_validator(mode="after")
|
|
327
|
-
def deduplicate(self) -> Self:
|
|
328
|
-
"""
|
|
329
|
-
Deduplicate optimizations between conf_gen and multistage_opt.
|
|
330
|
-
|
|
331
|
-
Also affects Manual Mode.
|
|
332
|
-
"""
|
|
333
|
-
cgs = self.conf_gen_settings
|
|
334
|
-
msos = self.multistage_opt_settings
|
|
335
|
-
|
|
336
|
-
if self.transition_state or not msos.optimization_settings:
|
|
337
|
-
return self
|
|
338
|
-
|
|
339
|
-
first_opt = msos.optimization_settings[0]
|
|
340
|
-
if cgs.conf_opt_method != first_opt.method or "optimize" not in first_opt.tasks:
|
|
341
|
-
return self
|
|
342
|
-
|
|
343
|
-
first_opt.tasks.remove(Task.OPTIMIZE)
|
|
344
|
-
if msos.singlepoint_settings or len(msos.optimization_settings) > 1:
|
|
345
|
-
if (not first_opt.tasks) or first_opt.tasks == ["singlepoint"]:
|
|
346
|
-
msos.optimization_settings = msos.optimization_settings[1:]
|
|
347
|
-
|
|
348
|
-
return self
|
|
349
|
-
|
|
350
325
|
@model_validator(mode="after")
|
|
351
326
|
def remove_ts_constraints(self) -> Self:
|
|
352
327
|
"""
|
|
@@ -389,4 +364,4 @@ class ConformerSearchWorkflow(ConformerSearchMixin, Workflow):
|
|
|
389
364
|
|
|
390
365
|
# Results
|
|
391
366
|
conformer_uuids: list[list[UUID | None]] = Field(default_factory=list)
|
|
392
|
-
energies:
|
|
367
|
+
energies: Annotated[FloatPerAtom, AfterValidator(round_float_per_atom(6))] = Field(default_factory=list)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Docking workflow."""
|
|
2
|
+
|
|
3
|
+
from typing import Self
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
|
|
6
|
+
|
|
7
|
+
from ..molecule import Molecule
|
|
8
|
+
from ..pdb import PDB
|
|
9
|
+
from ..types import Vector3D
|
|
10
|
+
from .workflow import Workflow
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Score(BaseModel):
|
|
14
|
+
"""
|
|
15
|
+
Pose with its score.
|
|
16
|
+
|
|
17
|
+
:param pose: conformation of the ligand when docked
|
|
18
|
+
:param score: score of the pose
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
pose: Molecule
|
|
22
|
+
score: float
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DockingWorkflow(Workflow):
|
|
26
|
+
"""
|
|
27
|
+
Docking workflow.
|
|
28
|
+
|
|
29
|
+
Inherited:
|
|
30
|
+
:param initial_molecule: Molecule of interest (currently unused)
|
|
31
|
+
:param mode: Mode for workflow (currently unused)
|
|
32
|
+
|
|
33
|
+
New:
|
|
34
|
+
:param molecules: Molecules to dock (optional)
|
|
35
|
+
:param smiles: SMILES strings of the ligands (optional)
|
|
36
|
+
:param target: PDB of the protein
|
|
37
|
+
:param pocket: center (x, y, z) and size (x, y, z) of the pocket
|
|
38
|
+
|
|
39
|
+
Results:
|
|
40
|
+
:param scores: docked poses sorted by score
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
44
|
+
|
|
45
|
+
molecules: list[Molecule] = []
|
|
46
|
+
smiles: list[str] = []
|
|
47
|
+
|
|
48
|
+
target: PDB
|
|
49
|
+
pocket: tuple[Vector3D, Vector3D]
|
|
50
|
+
|
|
51
|
+
scores: list[Score] = []
|
|
52
|
+
|
|
53
|
+
def __str__(self) -> str:
|
|
54
|
+
return repr(self)
|
|
55
|
+
|
|
56
|
+
def __repr__(self) -> str:
|
|
57
|
+
"""Return a string representation of the Docking workflow."""
|
|
58
|
+
desc = self.target.description
|
|
59
|
+
target = desc.code or desc.title
|
|
60
|
+
ligand = "".join(atom.atomic_symbol for atom in self.initial_molecule.atoms)
|
|
61
|
+
|
|
62
|
+
return f"<{type(self).__name__} {target} {ligand}>"
|
|
63
|
+
|
|
64
|
+
@model_validator(mode="after")
|
|
65
|
+
def check_molecules(self) -> Self:
|
|
66
|
+
"""Check if molecules are provided."""
|
|
67
|
+
if self.molecules and self.smiles:
|
|
68
|
+
raise ValueError("Must provide only one of molecules or smiles, not both")
|
|
69
|
+
elif not self.molecules and not self.smiles:
|
|
70
|
+
raise ValueError("Must provide either molecules or smiles")
|
|
71
|
+
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
@field_validator("pocket", mode="after")
|
|
75
|
+
def validate_pocket(cls, pocket: tuple[Vector3D, Vector3D]) -> tuple[Vector3D, Vector3D]:
|
|
76
|
+
center, size = pocket
|
|
77
|
+
if any(q <= 0 for q in size):
|
|
78
|
+
raise ValueError(f"Pocket size must be positive, got: {size}")
|
|
79
|
+
|
|
80
|
+
return pocket
|
|
@@ -1,22 +1,13 @@
|
|
|
1
|
-
from typing import Annotated
|
|
1
|
+
from typing import Annotated
|
|
2
2
|
|
|
3
3
|
from pydantic import AfterValidator, NonNegativeFloat, NonNegativeInt
|
|
4
4
|
|
|
5
|
-
from ..base import Base
|
|
5
|
+
from ..base import Base, round_float
|
|
6
6
|
from ..settings import Settings
|
|
7
|
-
from ..types import UUID, FloatPerAtom, Matrix3x3, Vector3D
|
|
7
|
+
from ..types import UUID, FloatPerAtom, Matrix3x3, Vector3D, round_optional_float_per_atom, round_optional_matrix3x3, round_optional_vector3d
|
|
8
8
|
from .workflow import Workflow
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def round_float(round_to: int) -> Callable[[float], float]:
|
|
12
|
-
"""Return a function that rounds a float to a given number of decimal places."""
|
|
13
|
-
|
|
14
|
-
def inner_round(v: float) -> float:
|
|
15
|
-
return round(v, round_to)
|
|
16
|
-
|
|
17
|
-
return inner_round
|
|
18
|
-
|
|
19
|
-
|
|
20
11
|
class PropertyCubePoint(Base):
|
|
21
12
|
"""A point in a cube file, all values rounded to 6 decimal places."""
|
|
22
13
|
|
|
@@ -40,7 +31,7 @@ class MolecularOrbitalCube(PropertyCube):
|
|
|
40
31
|
"""
|
|
41
32
|
|
|
42
33
|
occupation: NonNegativeInt
|
|
43
|
-
energy: float
|
|
34
|
+
energy: Annotated[float, AfterValidator(round_float(6))]
|
|
44
35
|
|
|
45
36
|
|
|
46
37
|
class ElectronicPropertiesWorkflow(Workflow):
|
|
@@ -88,11 +79,11 @@ class ElectronicPropertiesWorkflow(Workflow):
|
|
|
88
79
|
# Results
|
|
89
80
|
calc_uuid: UUID | None = None
|
|
90
81
|
|
|
91
|
-
dipole: Vector3D | None = None
|
|
92
|
-
quadrupole: Matrix3x3 | None = None
|
|
82
|
+
dipole: Annotated[Vector3D | None, AfterValidator(round_optional_vector3d(6))] = None
|
|
83
|
+
quadrupole: Annotated[Matrix3x3 | None, AfterValidator(round_optional_matrix3x3(6))] = None
|
|
93
84
|
|
|
94
|
-
mulliken_charges: FloatPerAtom | None = None
|
|
95
|
-
lowdin_charges: FloatPerAtom | None = None
|
|
85
|
+
mulliken_charges: Annotated[FloatPerAtom | None, AfterValidator(round_optional_float_per_atom(6))] = None
|
|
86
|
+
lowdin_charges: Annotated[FloatPerAtom | None, AfterValidator(round_optional_float_per_atom(6))] = None
|
|
96
87
|
|
|
97
88
|
wiberg_bond_orders: list[tuple[NonNegativeInt, NonNegativeInt, NonNegativeFloat]] = []
|
|
98
89
|
mayer_bond_orders: list[tuple[NonNegativeInt, NonNegativeInt, NonNegativeFloat]] = []
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
from pydantic import AfterValidator
|
|
4
|
+
|
|
5
|
+
from ..base import round_optional_float
|
|
6
|
+
from ..types import UUID, FloatPerAtom, round_optional_float_per_atom
|
|
7
|
+
from .workflow import Workflow
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FukuiIndexWorkflow(Workflow):
|
|
11
|
+
# UUID of optimization
|
|
12
|
+
optimization: UUID | None = None
|
|
13
|
+
|
|
14
|
+
global_electrophilicity_index: Annotated[float | None, AfterValidator(round_optional_float(6))] = None
|
|
15
|
+
fukui_positive: Annotated[FloatPerAtom | None, AfterValidator(round_optional_float_per_atom(6))] = None
|
|
16
|
+
fukui_negative: Annotated[FloatPerAtom | None, AfterValidator(round_optional_float_per_atom(6))] = None
|
|
17
|
+
fukui_zero: Annotated[FloatPerAtom | None, AfterValidator(round_optional_float_per_atom(6))] = None
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from typing import Self
|
|
2
|
+
|
|
3
|
+
from pydantic import Field, model_validator
|
|
4
|
+
|
|
5
|
+
from ..method import XTB_METHODS, Method
|
|
6
|
+
from ..mode import Mode
|
|
7
|
+
from ..settings import Settings
|
|
8
|
+
from ..solvent import Solvent, SolventModel, SolventSettings
|
|
9
|
+
from ..types import UUID
|
|
10
|
+
from .workflow import Workflow
|
|
11
|
+
|
|
12
|
+
_sentinel_settings: Settings = object() # type: ignore [assignment]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class IRCWorkflow(Workflow):
|
|
16
|
+
"""
|
|
17
|
+
Workflow for Intrinsic Reaction Coordinate (IRC) calculations.
|
|
18
|
+
|
|
19
|
+
Inherited:
|
|
20
|
+
:param initial_molecule: Molecule of interest
|
|
21
|
+
:param mode: Mode for workflow
|
|
22
|
+
|
|
23
|
+
New:
|
|
24
|
+
:param settings: Settings for running the IRC (only for manual mode)
|
|
25
|
+
:param solvent: Solvent for the calculation (non-Manual mode only)
|
|
26
|
+
:param preopt: whether to optimize the geometry before starting the IRC
|
|
27
|
+
:param final_opt: whether to optimize the final IRC geometry to a minimum
|
|
28
|
+
|
|
29
|
+
Results:
|
|
30
|
+
:param starting_TS: optimized TS before the IRC (==initial_molecule if preopt=False)
|
|
31
|
+
:param irc_forward: forward calculations
|
|
32
|
+
:param irc_backward: reverse calculations
|
|
33
|
+
:param opt_forward: optimization steps after the forward IRC
|
|
34
|
+
:param opt_backward: optimization steps after the reverse IRC
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
settings: Settings = _sentinel_settings
|
|
38
|
+
solvent: Solvent | None = None
|
|
39
|
+
|
|
40
|
+
preopt: bool = False
|
|
41
|
+
final_opt: bool = False
|
|
42
|
+
|
|
43
|
+
starting_TS: UUID | None = None
|
|
44
|
+
|
|
45
|
+
irc_forward: list[UUID] = Field(default_factory=list)
|
|
46
|
+
irc_backward: list[UUID] = Field(default_factory=list)
|
|
47
|
+
opt_forward: list[UUID] = Field(default_factory=list)
|
|
48
|
+
opt_backward: list[UUID] = Field(default_factory=list)
|
|
49
|
+
|
|
50
|
+
def __str__(self) -> str:
|
|
51
|
+
return repr(self)
|
|
52
|
+
|
|
53
|
+
def __repr__(self) -> str:
|
|
54
|
+
"""String representation of the workflow."""
|
|
55
|
+
if self.mode != Mode.MANUAL:
|
|
56
|
+
return f"<{type(self).__name__} {self.mode.name}>"
|
|
57
|
+
|
|
58
|
+
return f"<{type(self).__name__} {self.level_of_theory}>"
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def level_of_theory(self) -> str:
|
|
62
|
+
"""Level of theory for the workflow."""
|
|
63
|
+
return self.settings.level_of_theory
|
|
64
|
+
|
|
65
|
+
@model_validator(mode="after")
|
|
66
|
+
def validate_mode(self) -> Self:
|
|
67
|
+
"""Convert the mode to settings."""
|
|
68
|
+
if self.mode == Mode.MANUAL:
|
|
69
|
+
if self.settings is _sentinel_settings:
|
|
70
|
+
raise ValueError("Settings are required for manual calculations.")
|
|
71
|
+
return self
|
|
72
|
+
elif self.settings is not _sentinel_settings:
|
|
73
|
+
raise ValueError("Cannot specify settings and mode simultaneously.")
|
|
74
|
+
|
|
75
|
+
match self.mode:
|
|
76
|
+
case Mode.RAPID:
|
|
77
|
+
method = Method.GFN2_XTB
|
|
78
|
+
case Mode.CAREFUL:
|
|
79
|
+
method = Method.R2SCAN3C
|
|
80
|
+
case Mode.METICULOUS:
|
|
81
|
+
method = Method.WB97X3C
|
|
82
|
+
case _:
|
|
83
|
+
raise ValueError(f"Unsupported mode: {self.mode}")
|
|
84
|
+
|
|
85
|
+
model = SolventModel.ALPB if method in XTB_METHODS else SolventModel.CPCM
|
|
86
|
+
solvent_settings = SolventSettings(solvent=self.solvent, model=model) if self.solvent else None
|
|
87
|
+
|
|
88
|
+
self.settings = Settings(mode=Mode.RAPID, method=method, solvent_settings=solvent_settings)
|
|
89
|
+
|
|
90
|
+
return self
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Annotated
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from pydantic import AfterValidator
|
|
4
|
+
|
|
5
|
+
from ..base import Base, round_float, round_optional_float
|
|
4
6
|
from ..mode import Mode
|
|
5
7
|
from .workflow import DBCalculation, Workflow
|
|
6
8
|
|
|
@@ -8,8 +10,8 @@ from .workflow import DBCalculation, Workflow
|
|
|
8
10
|
class pKaMicrostate(Base):
|
|
9
11
|
atom_index: int
|
|
10
12
|
structures: list[DBCalculation] = []
|
|
11
|
-
deltaG: float
|
|
12
|
-
pka: float
|
|
13
|
+
deltaG: Annotated[float, AfterValidator(round_float(3))]
|
|
14
|
+
pka: Annotated[float, AfterValidator(round_float(3))]
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class pKaWorkflow(Workflow):
|
|
@@ -26,5 +28,5 @@ class pKaWorkflow(Workflow):
|
|
|
26
28
|
structures: list[DBCalculation] = []
|
|
27
29
|
conjugate_acids: list[pKaMicrostate] = []
|
|
28
30
|
conjugate_bases: list[pKaMicrostate] = []
|
|
29
|
-
strongest_acid:
|
|
30
|
-
strongest_base:
|
|
31
|
+
strongest_acid: Annotated[float | None, AfterValidator(round_optional_float(3))] = None
|
|
32
|
+
strongest_base: Annotated[float | None, AfterValidator(round_optional_float(3))] = None
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from typing import Any, TypeVar
|
|
1
|
+
from typing import Annotated, Any, TypeVar
|
|
2
2
|
|
|
3
|
-
from pydantic import ValidationInfo, field_validator, model_validator
|
|
3
|
+
from pydantic import AfterValidator, ValidationInfo, field_validator, model_validator
|
|
4
4
|
|
|
5
|
+
from ..base import round_optional_float
|
|
5
6
|
from ..mode import Mode
|
|
6
7
|
from ..solvent import Solvent
|
|
7
8
|
from ..types import UUID
|
|
@@ -58,8 +59,8 @@ class RedoxPotentialWorkflow(Workflow, MultiStageOptMixin):
|
|
|
58
59
|
anion_molecule: UUID | None = None
|
|
59
60
|
cation_molecule: UUID | None = None
|
|
60
61
|
|
|
61
|
-
reduction_potential: float | None = None
|
|
62
|
-
oxidation_potential: float | None = None
|
|
62
|
+
reduction_potential: Annotated[float | None, AfterValidator(round_optional_float(6))] = None
|
|
63
|
+
oxidation_potential: Annotated[float | None, AfterValidator(round_optional_float(6))] = None
|
|
63
64
|
|
|
64
65
|
@field_validator("solvent", mode="before")
|
|
65
66
|
@classmethod
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
from numpy.typing import NDArray
|
|
5
|
+
from pydantic import AfterValidator
|
|
3
6
|
|
|
4
|
-
from ..base import Base
|
|
7
|
+
from ..base import Base, round_optional_float
|
|
5
8
|
from ..molecule import Molecule
|
|
6
9
|
from ..settings import Settings
|
|
7
10
|
from ..types import UUID
|
|
@@ -11,7 +14,7 @@ from .workflow import Workflow
|
|
|
11
14
|
class ScanPoint(Base):
|
|
12
15
|
index: int
|
|
13
16
|
molecule: Molecule
|
|
14
|
-
energy: float | None = None
|
|
17
|
+
energy: Annotated[float | None, AfterValidator(round_optional_float(6))] = None
|
|
15
18
|
uuid: UUID | None = None
|
|
16
19
|
|
|
17
20
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Annotated, Any
|
|
2
2
|
|
|
3
|
-
from pydantic import BaseModel, Field, PositiveInt, field_validator, model_validator
|
|
3
|
+
from pydantic import AfterValidator, BaseModel, Field, PositiveInt, field_validator, model_validator
|
|
4
4
|
|
|
5
|
+
from ..base import round_float
|
|
5
6
|
from ..mode import Mode
|
|
6
7
|
from ..types import UUID
|
|
7
8
|
from .multistage_opt import MultiStageOptMixin
|
|
@@ -23,7 +24,7 @@ class SpinState(BaseModel):
|
|
|
23
24
|
"""
|
|
24
25
|
|
|
25
26
|
multiplicity: PositiveInt
|
|
26
|
-
energy: float
|
|
27
|
+
energy: Annotated[float, AfterValidator(round_float(6))]
|
|
27
28
|
calculation: list[UUID | None]
|
|
28
29
|
|
|
29
30
|
def __str__(self) -> str:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import Annotated, Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import AfterValidator
|
|
4
|
+
|
|
5
|
+
from ..base import Base, round_float, round_optional_float
|
|
6
|
+
from ..mode import Mode
|
|
7
|
+
from .workflow import DBCalculation, Workflow
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Tautomer(Base):
|
|
11
|
+
energy: Annotated[float, AfterValidator(round_float(6))]
|
|
12
|
+
weight: Annotated[Optional[float], AfterValidator(round_optional_float(6))] = None
|
|
13
|
+
predicted_relative_energy: Annotated[Optional[float], AfterValidator(round_optional_float(6))] = None
|
|
14
|
+
|
|
15
|
+
# UUIDs, optionally
|
|
16
|
+
structures: list[DBCalculation] = []
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TautomerWorkflow(Workflow):
|
|
20
|
+
mode: Mode = Mode.CAREFUL
|
|
21
|
+
tautomers: list[Tautomer] = []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: stjames
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.50
|
|
4
4
|
Summary: standardized JSON atom/molecule encoding scheme
|
|
5
5
|
Author-email: Corin Wagen <corin@rowansci.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/rowansci/stjames
|
|
@@ -11,6 +11,7 @@ License-File: LICENSE
|
|
|
11
11
|
Requires-Dist: atomium<2,>=1
|
|
12
12
|
Requires-Dist: pydantic>=2.4
|
|
13
13
|
Requires-Dist: numpy
|
|
14
|
+
Requires-Dist: atomium<2.0,>=1.0
|
|
14
15
|
|
|
15
16
|
# stjames
|
|
16
17
|
|
|
@@ -7,6 +7,7 @@ stjames/atom.py
|
|
|
7
7
|
stjames/base.py
|
|
8
8
|
stjames/basis_set.py
|
|
9
9
|
stjames/calculation.py
|
|
10
|
+
stjames/compute_settings.py
|
|
10
11
|
stjames/constraint.py
|
|
11
12
|
stjames/correction.py
|
|
12
13
|
stjames/diis_settings.py
|
|
@@ -46,9 +47,11 @@ stjames/workflows/bde.py
|
|
|
46
47
|
stjames/workflows/conformer.py
|
|
47
48
|
stjames/workflows/conformer_search.py
|
|
48
49
|
stjames/workflows/descriptors.py
|
|
50
|
+
stjames/workflows/docking.py
|
|
49
51
|
stjames/workflows/electronic_properties.py
|
|
50
52
|
stjames/workflows/fukui.py
|
|
51
53
|
stjames/workflows/hydrogen_bond_basicity.py
|
|
54
|
+
stjames/workflows/irc.py
|
|
52
55
|
stjames/workflows/molecular_dynamics.py
|
|
53
56
|
stjames/workflows/multistage_opt.py
|
|
54
57
|
stjames/workflows/pka.py
|
|
@@ -61,4 +64,5 @@ tests/test_constraints.py
|
|
|
61
64
|
tests/test_from_extxyz.py
|
|
62
65
|
tests/test_molecule.py
|
|
63
66
|
tests/test_pdb.py
|
|
67
|
+
tests/test_rounding.py
|
|
64
68
|
tests/test_settings.py
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from stjames import Atom, Molecule, VibrationalMode
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_vibrational_mode_rounding() -> None:
|
|
5
|
+
test_vectors = [(1.23456789345346, 2.34567890346346, 3.45678901346346436), (4.567890126343636, 5.678901236346436, 6.7890123436436436)]
|
|
6
|
+
mode = VibrationalMode(frequency=1.1234567, reduced_mass=1.1234567, force_constant=1.1234567, displacements=test_vectors)
|
|
7
|
+
|
|
8
|
+
expected_vectors = [(1.234568, 2.345679, 3.456789), (4.567890, 5.678901, 6.789012)]
|
|
9
|
+
assert mode.displacements == expected_vectors
|
|
10
|
+
assert mode.frequency == 1.123
|
|
11
|
+
assert mode.reduced_mass == 1.123
|
|
12
|
+
assert mode.force_constant == 1.123
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_atom_rounding() -> None:
|
|
16
|
+
atom = Atom(atomic_number=2, position=[0.1111111112222222, 1.1111111112222222, 2.1111111112222222])
|
|
17
|
+
|
|
18
|
+
rounded_position = (0.11111111, 1.11111111, 2.11111111)
|
|
19
|
+
|
|
20
|
+
assert atom.position == rounded_position
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_molecule_rounding() -> None:
|
|
24
|
+
mol = Molecule(
|
|
25
|
+
charge=0,
|
|
26
|
+
multiplicity=1,
|
|
27
|
+
atoms=[Atom(atomic_number=2, position=[0.1111111112222222, 1.1111111112222222, 2.1111111112222222])],
|
|
28
|
+
energy=1.23456789345346,
|
|
29
|
+
zero_point_energy=2.34567890346346,
|
|
30
|
+
thermal_energy_corr=3.45678901346346436,
|
|
31
|
+
elapsed=4.567890126343636,
|
|
32
|
+
thermal_enthalpy_corr=3.45678901346346436,
|
|
33
|
+
thermal_free_energy_corr=3.45678901346346436,
|
|
34
|
+
homo_lumo_gap=5.12345678,
|
|
35
|
+
dipole=(1.23456789345346, 2.34567890346346, 3.45678901346346436),
|
|
36
|
+
stress=(
|
|
37
|
+
(1.23456789345346, 2.34567890346346, 3.45678901346346436),
|
|
38
|
+
(4.567890126343636, 5.678901236346436, 6.7890123436436436),
|
|
39
|
+
(4.567890126343636, 5.678901236346436, 6.7890123436436436),
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
assert mol.atoms[0].position == (0.11111111, 1.11111111, 2.11111111)
|
|
44
|
+
assert mol.energy == 1.234568
|
|
45
|
+
assert mol.zero_point_energy == 2.345679
|
|
46
|
+
assert mol.thermal_energy_corr == 3.456789
|
|
47
|
+
assert mol.elapsed == 4.568
|
|
48
|
+
assert mol.thermal_enthalpy_corr == 3.456789
|
|
49
|
+
assert mol.thermal_free_energy_corr == 3.456789
|
|
50
|
+
assert mol.homo_lumo_gap == 5.123457
|
|
51
|
+
assert mol.dipole == (1.234568, 2.345679, 3.456789)
|
|
52
|
+
assert mol.stress == ((1.234568, 2.345679, 3.456789), (4.567890, 5.678901, 6.789012), (4.567890, 5.678901, 6.789012))
|
stjames-0.0.48/stjames/types.py
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
from ..types import UUID
|
|
2
|
-
from .workflow import Workflow
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class FukuiIndexWorkflow(Workflow):
|
|
6
|
-
# UUID of optimization
|
|
7
|
-
optimization: UUID | None = None
|
|
8
|
-
|
|
9
|
-
global_electrophilicity_index: float | None = None
|
|
10
|
-
fukui_positive: list[float] | None = None
|
|
11
|
-
fukui_negative: list[float] | None = None
|
|
12
|
-
fukui_zero: list[float] | None = None
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
|
-
from ..base import Base
|
|
4
|
-
from ..mode import Mode
|
|
5
|
-
from .workflow import DBCalculation, Workflow
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class Tautomer(Base):
|
|
9
|
-
energy: float
|
|
10
|
-
weight: Optional[float] = None
|
|
11
|
-
predicted_relative_energy: Optional[float] = None
|
|
12
|
-
|
|
13
|
-
# UUIDs, optionally
|
|
14
|
-
structures: list[DBCalculation] = []
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class TautomerWorkflow(Workflow):
|
|
18
|
-
mode: Mode = Mode.CAREFUL
|
|
19
|
-
tautomers: list[Tautomer] = []
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|