stjames 0.0.49__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.

Files changed (73) hide show
  1. {stjames-0.0.49/stjames.egg-info → stjames-0.0.50}/PKG-INFO +2 -1
  2. {stjames-0.0.49 → stjames-0.0.50}/pyproject.toml +2 -1
  3. {stjames-0.0.49 → stjames-0.0.50}/stjames/atom.py +4 -4
  4. {stjames-0.0.49 → stjames-0.0.50}/stjames/base.py +21 -1
  5. {stjames-0.0.49 → stjames-0.0.50}/stjames/compute_settings.py +3 -1
  6. {stjames-0.0.49 → stjames-0.0.50}/stjames/method.py +4 -2
  7. {stjames-0.0.49 → stjames-0.0.50}/stjames/molecule.py +50 -21
  8. {stjames-0.0.49 → stjames-0.0.50}/stjames/periodic_cell.py +3 -3
  9. stjames-0.0.50/stjames/types.py +98 -0
  10. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/__init__.py +7 -0
  11. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/bde.py +4 -3
  12. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/conformer.py +6 -4
  13. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/conformer_search.py +4 -29
  14. stjames-0.0.50/stjames/workflows/docking.py +80 -0
  15. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/electronic_properties.py +8 -17
  16. stjames-0.0.50/stjames/workflows/fukui.py +17 -0
  17. stjames-0.0.50/stjames/workflows/irc.py +90 -0
  18. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/pka.py +8 -6
  19. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/redox_potential.py +5 -4
  20. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/scan.py +5 -2
  21. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/spin_states.py +4 -3
  22. stjames-0.0.50/stjames/workflows/tautomer.py +21 -0
  23. {stjames-0.0.49 → stjames-0.0.50/stjames.egg-info}/PKG-INFO +2 -1
  24. {stjames-0.0.49 → stjames-0.0.50}/stjames.egg-info/SOURCES.txt +3 -0
  25. {stjames-0.0.49 → stjames-0.0.50}/stjames.egg-info/requires.txt +1 -0
  26. stjames-0.0.50/tests/test_rounding.py +52 -0
  27. stjames-0.0.49/stjames/types.py +0 -10
  28. stjames-0.0.49/stjames/workflows/fukui.py +0 -12
  29. stjames-0.0.49/stjames/workflows/tautomer.py +0 -19
  30. {stjames-0.0.49 → stjames-0.0.50}/LICENSE +0 -0
  31. {stjames-0.0.49 → stjames-0.0.50}/README.md +0 -0
  32. {stjames-0.0.49 → stjames-0.0.50}/setup.cfg +0 -0
  33. {stjames-0.0.49 → stjames-0.0.50}/stjames/__init__.py +0 -0
  34. {stjames-0.0.49 → stjames-0.0.50}/stjames/_deprecated_solvent_settings.py +0 -0
  35. {stjames-0.0.49 → stjames-0.0.50}/stjames/basis_set.py +0 -0
  36. {stjames-0.0.49 → stjames-0.0.50}/stjames/calculation.py +0 -0
  37. {stjames-0.0.49 → stjames-0.0.50}/stjames/constraint.py +0 -0
  38. {stjames-0.0.49 → stjames-0.0.50}/stjames/correction.py +0 -0
  39. {stjames-0.0.49 → stjames-0.0.50}/stjames/data/__init__.py +0 -0
  40. {stjames-0.0.49 → stjames-0.0.50}/stjames/data/bragg_radii.json +0 -0
  41. {stjames-0.0.49 → stjames-0.0.50}/stjames/data/elements.py +0 -0
  42. {stjames-0.0.49 → stjames-0.0.50}/stjames/data/isotopes.json +0 -0
  43. {stjames-0.0.49 → stjames-0.0.50}/stjames/data/nist_isotopes.json +0 -0
  44. {stjames-0.0.49 → stjames-0.0.50}/stjames/data/read_nist_isotopes.py +0 -0
  45. {stjames-0.0.49 → stjames-0.0.50}/stjames/data/symbol_element.json +0 -0
  46. {stjames-0.0.49 → stjames-0.0.50}/stjames/diis_settings.py +0 -0
  47. {stjames-0.0.49 → stjames-0.0.50}/stjames/grid_settings.py +0 -0
  48. {stjames-0.0.49 → stjames-0.0.50}/stjames/int_settings.py +0 -0
  49. {stjames-0.0.49 → stjames-0.0.50}/stjames/message.py +0 -0
  50. {stjames-0.0.49 → stjames-0.0.50}/stjames/mode.py +0 -0
  51. {stjames-0.0.49 → stjames-0.0.50}/stjames/opt_settings.py +0 -0
  52. {stjames-0.0.49 → stjames-0.0.50}/stjames/pdb.py +0 -0
  53. {stjames-0.0.49 → stjames-0.0.50}/stjames/py.typed +0 -0
  54. {stjames-0.0.49 → stjames-0.0.50}/stjames/scf_settings.py +0 -0
  55. {stjames-0.0.49 → stjames-0.0.50}/stjames/settings.py +0 -0
  56. {stjames-0.0.49 → stjames-0.0.50}/stjames/solvent.py +0 -0
  57. {stjames-0.0.49 → stjames-0.0.50}/stjames/status.py +0 -0
  58. {stjames-0.0.49 → stjames-0.0.50}/stjames/task.py +0 -0
  59. {stjames-0.0.49 → stjames-0.0.50}/stjames/thermochem_settings.py +0 -0
  60. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/admet.py +0 -0
  61. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/basic_calculation.py +0 -0
  62. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/descriptors.py +0 -0
  63. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/hydrogen_bond_basicity.py +0 -0
  64. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/molecular_dynamics.py +0 -0
  65. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/multistage_opt.py +0 -0
  66. {stjames-0.0.49 → stjames-0.0.50}/stjames/workflows/workflow.py +0 -0
  67. {stjames-0.0.49 → stjames-0.0.50}/stjames.egg-info/dependency_links.txt +0 -0
  68. {stjames-0.0.49 → stjames-0.0.50}/stjames.egg-info/top_level.txt +0 -0
  69. {stjames-0.0.49 → stjames-0.0.50}/tests/test_constraints.py +0 -0
  70. {stjames-0.0.49 → stjames-0.0.50}/tests/test_from_extxyz.py +0 -0
  71. {stjames-0.0.49 → stjames-0.0.50}/tests/test_molecule.py +0 -0
  72. {stjames-0.0.49 → stjames-0.0.50}/tests/test_pdb.py +0 -0
  73. {stjames-0.0.49 → 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.49
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.49"
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
@@ -4,7 +4,9 @@ from .base import Base, LowercaseStrEnum
4
4
  class ComputeType(LowercaseStrEnum):
5
5
  CPU = "cpu"
6
6
  GPU = "gpu"
7
+ AUTO = "auto"
7
8
 
8
9
 
9
10
  class ComputeSettings(Base):
10
- compute_type: ComputeType = ComputeType.CPU
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
- PrepackagedNNPMethod = Literal[Method.AIMNET2_WB97MD3, Method.OCP24_S, Method.OCP24_L]
52
- PREPACKAGED_NNP_METHODS = [Method.AIMNET2_WB97MD3, Method.OCP24_S, Method.OCP24_L]
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 FloatPerAtom, Matrix3x3, Vector3D, Vector3DPerAtom
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")
@@ -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 ..base import Base
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: Optional[float] = None
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 ..task import Task
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: list[float] = Field(default_factory=list)
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, Callable
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 Optional
1
+ from typing import Annotated
2
2
 
3
- from ..base import Base
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: Optional[float] = None
30
- strongest_base: Optional[float] = None
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.49
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
 
@@ -47,9 +47,11 @@ stjames/workflows/bde.py
47
47
  stjames/workflows/conformer.py
48
48
  stjames/workflows/conformer_search.py
49
49
  stjames/workflows/descriptors.py
50
+ stjames/workflows/docking.py
50
51
  stjames/workflows/electronic_properties.py
51
52
  stjames/workflows/fukui.py
52
53
  stjames/workflows/hydrogen_bond_basicity.py
54
+ stjames/workflows/irc.py
53
55
  stjames/workflows/molecular_dynamics.py
54
56
  stjames/workflows/multistage_opt.py
55
57
  stjames/workflows/pka.py
@@ -62,4 +64,5 @@ tests/test_constraints.py
62
64
  tests/test_from_extxyz.py
63
65
  tests/test_molecule.py
64
66
  tests/test_pdb.py
67
+ tests/test_rounding.py
65
68
  tests/test_settings.py
@@ -1,3 +1,4 @@
1
1
  atomium<2,>=1
2
2
  pydantic>=2.4
3
3
  numpy
4
+ atomium<2.0,>=1.0
@@ -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))
@@ -1,10 +0,0 @@
1
- from typing import 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]
@@ -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