stjames 0.0.49__py3-none-any.whl → 0.0.50__py3-none-any.whl

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/atom.py CHANGED
@@ -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
  """
stjames/base.py CHANGED
@@ -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
stjames/method.py CHANGED
@@ -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]
stjames/molecule.py CHANGED
@@ -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]
stjames/periodic_cell.py CHANGED
@@ -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")
stjames/types.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import TypeAlias
1
+ from typing import Callable, TypeAlias
2
2
 
3
3
  UUID: TypeAlias = str
4
4
 
@@ -8,3 +8,91 @@ Vector3DPerAtom: TypeAlias = list[Vector3D]
8
8
  FloatPerAtom: TypeAlias = list[float]
9
9
 
10
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]
stjames/workflows/bde.py CHANGED
@@ -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]] = []
@@ -1,4 +1,9 @@
1
- from ..types import UUID
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
2
7
  from .workflow import Workflow
3
8
 
4
9
 
@@ -6,7 +11,7 @@ class FukuiIndexWorkflow(Workflow):
6
11
  # UUID of optimization
7
12
  optimization: UUID | None = None
8
13
 
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
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
stjames/workflows/pka.py CHANGED
@@ -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
stjames/workflows/scan.py CHANGED
@@ -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:
@@ -1,14 +1,16 @@
1
- from typing import Optional
1
+ from typing import Annotated, 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 ..mode import Mode
5
7
  from .workflow import DBCalculation, Workflow
6
8
 
7
9
 
8
10
  class Tautomer(Base):
9
- energy: float
10
- weight: Optional[float] = None
11
- predicted_relative_energy: Optional[float] = None
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
12
14
 
13
15
  # UUIDs, optionally
14
16
  structures: list[DBCalculation] = []
@@ -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,22 +1,22 @@
1
1
  stjames/__init__.py,sha256=8kqGRUcrOQY07DHp6VJN7GEttPPTHw49QpieMeQqkwE,608
2
2
  stjames/_deprecated_solvent_settings.py,sha256=gj5j9p3zakIwSTK5_ndqBXJx--IzjZNxZ75z-wipLOo,450
3
- stjames/atom.py,sha256=w7q-x9xpBw4sJ1WGrWt65WAaStxhz-m7dugXCYEOpq4,2064
4
- stjames/base.py,sha256=hroQjvC8G88UiK520i1TDqnOlgw_ihmzevQGTO5yeUY,1215
3
+ stjames/atom.py,sha256=hQCTMu3y30dvqjyCInnSs47DkUEnwhm_M2qB2jTD4Ik,2153
4
+ stjames/base.py,sha256=YhmNZqFdWYD_lCw8rD2fZtt17nh8g7lUFBnGMezB-u8,1837
5
5
  stjames/basis_set.py,sha256=wI3M2q9uPf9jhKpAi4E2DrsyKzloDGLRjAlk7krdYgc,949
6
6
  stjames/calculation.py,sha256=O2LwwQ_cOLmDOGXTHA9J71YbUZXigUSbvbLA-fSVm3w,915
7
- stjames/compute_settings.py,sha256=N312PevuI4V2EswJSimitLB_JC92thTCGoZJjXFccxo,191
7
+ stjames/compute_settings.py,sha256=wuYE6W4WqP3oFHwAeEp7XQ0oMz_MNLucWgrsuJ4kEFQ,268
8
8
  stjames/constraint.py,sha256=B6oV0rYjmAWr8gpi5f03gRy_uuqjUURVDVwoez5Cfbg,2442
9
9
  stjames/correction.py,sha256=ZVErCcj4TPyZeKrdvXVjHa0tFynsCaoy96QZUVxWFM8,413
10
10
  stjames/diis_settings.py,sha256=4m1EQQWBlpHhMnWopix8qOqJv7QCluvdnV9jSKJDFtE,552
11
11
  stjames/grid_settings.py,sha256=WrSNGc-8_f87YBZYt9Hh7RbhM4MweADoVzwBMcSqcsE,640
12
12
  stjames/int_settings.py,sha256=5HXp8opt5ZyY1UpmfaK7NVloWVLM5jkG0elEEqpVLUo,896
13
13
  stjames/message.py,sha256=Rq6QqmHZKecWxYH8fVyXmuoCCPZv8YinvgykSeorXSU,216
14
- stjames/method.py,sha256=dWlDoB5xxWqPg6pdQyEeLG7aPsg4cux9ckfaA6GVsD0,2280
14
+ stjames/method.py,sha256=5hBHk2xQLpxZ52LwJ9FHWaqQMdFKnsbQEOxaVe6O4Go,2321
15
15
  stjames/mode.py,sha256=xw46Cc7f3eTS8i35qECi-8DocAlANhayK3w4akD4HBU,496
16
- stjames/molecule.py,sha256=v8NikFHfwOahXSo4VKGSqeHKI2HIoRdNjGE0GkZgNS4,10554
16
+ stjames/molecule.py,sha256=DeNYmFdvbuKeXvLqlu-UxHMyZVK6y4j-Lw3HITGMnHw,12406
17
17
  stjames/opt_settings.py,sha256=gxXGtjy9l-Q5Wen9eO6T6HHRCuS8rfOofdVQIJj0JcI,550
18
18
  stjames/pdb.py,sha256=goigN-UO72y-nnHmNF4HWp8K_ZYfJorg6TCqWpsNQIU,4519
19
- stjames/periodic_cell.py,sha256=JDCyynpamggTNi_HnTnnotRbeSMBfYc-srhD-IwUnrg,996
19
+ stjames/periodic_cell.py,sha256=eV_mArsY_MPEFSrFEsTC-CyCc6V8ITAXdk7yhjjNI7M,1080
20
20
  stjames/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  stjames/scf_settings.py,sha256=WotVgVrayQ_8PUHP39zVtG7iLT9PV41lpzruttFACP8,2356
22
22
  stjames/settings.py,sha256=NrNtHq-78BNp4RMP0tsABv4BjS5EscANakCyRdImI-A,8967
@@ -24,7 +24,7 @@ stjames/solvent.py,sha256=u037tmu-9oa21s-WEDZ7VC7nuNVjkqR2ML4JWjWSME4,1158
24
24
  stjames/status.py,sha256=wTKNcNxStoEHrxxgr_zTyN90NITa3rxMQZzOgrCifEw,332
25
25
  stjames/task.py,sha256=OLINRqe66o7t8arffilwmggrF_7TH0L79u6DhGruxV8,329
26
26
  stjames/thermochem_settings.py,sha256=ZTLz31v8Ltutde5Nfm0vH5YahWjcfFWfr_R856KffxE,517
27
- stjames/types.py,sha256=CPKR0g_kdFejMjGdKBjtuJRQqfmAZ-uIaSuGR1vBzCQ,245
27
+ stjames/types.py,sha256=hw-3UBikESvN3DzfK5doZB030kIEfx9gC3yBkIbebsI,3764
28
28
  stjames/data/__init__.py,sha256=O59Ksp7AIqwOELCWymfCx7YeBzwNOGCMlGQi7tNLqiE,24
29
29
  stjames/data/bragg_radii.json,sha256=hhbn-xyZNSdmnULIjN2Cvq-_BGIZIqG243Ls_mey61w,1350
30
30
  stjames/data/elements.py,sha256=9BW01LZlyJ0H5s7Q26vUmjZIST41fwOYYrGvmPd7q0w,858
@@ -32,26 +32,28 @@ stjames/data/isotopes.json,sha256=5ba8QnLrHD_Ypv2xekv2cIRwYrX3MQ19-1FOFtt0RuU,83
32
32
  stjames/data/nist_isotopes.json,sha256=d5DNk1dX0iB1waEYIRR6JMHuA7AuYwSBEgBvb4EKyhM,14300
33
33
  stjames/data/read_nist_isotopes.py,sha256=y10FNjW43QpC45qib7VHsIghEwT7GG5rsNwHdc9osRI,3309
34
34
  stjames/data/symbol_element.json,sha256=vl_buFusTqBd-muYQtMLtTDLy2OtBI6KkBeqkaWRQrg,1186
35
- stjames/workflows/__init__.py,sha256=XQ89vfQxqz2cWmwcOMp_GKZFQ-RDm8B1s8PqAeQTK3g,1999
35
+ stjames/workflows/__init__.py,sha256=cndGGw9ZGhvbbrT_1mJ1w7gNAjgwmkYGq2Fqt8ml6Hg,2180
36
36
  stjames/workflows/admet.py,sha256=V8noO0Eb7h2bDFSnj6Pxv4ILm0lGxyVRCi13hE0zmEQ,149
37
37
  stjames/workflows/basic_calculation.py,sha256=q48bpab7ZqmRTR4PsGC6bWkuxqkVdJRM8gysevTYXP0,212
38
- stjames/workflows/bde.py,sha256=iNrBiAUJA-VaAB-eFddApUO2xIc5PyPYXNtC2stQ_OU,9667
39
- stjames/workflows/conformer.py,sha256=YYwL3l7OaVeea4N9-ihghwa_ieKY6hia9LNbiTraMb0,2732
40
- stjames/workflows/conformer_search.py,sha256=y64WpdVZcrUit9ub-yWwJ0zNK8ofPlcjOG4K6pV6UEM,14241
38
+ stjames/workflows/bde.py,sha256=cWsZNiNdUy5DGvu8o6bBepLZIB5ohiAD6RKoK3YSq1I,9786
39
+ stjames/workflows/conformer.py,sha256=LnpvH5HmLCn-ddH3r1WhEYONUMX5ohZswdESEPCR6o4,2907
40
+ stjames/workflows/conformer_search.py,sha256=ZQ3VxQoaR_aZ-KSblXooAnGMTFbvOj4IQnM70K0OXhc,13497
41
41
  stjames/workflows/descriptors.py,sha256=lRRCsGzad3nIg0wI1090ffaXB0FVh0nRRb2lNxCY0kI,281
42
- stjames/workflows/electronic_properties.py,sha256=0PqS04CfLM4pzx67Ph3JJTrzc1LCwDTT5E8x4_wW-g8,3888
43
- stjames/workflows/fukui.py,sha256=CsJ3_gvzqEqcxwYN7bnNIa37F3KlLm7obsU77TGmDgo,348
42
+ stjames/workflows/docking.py,sha256=MvqfxCEZva91GB_w0qd68v7Dnb-F6AxzhQBsKJXkaeY,2239
43
+ stjames/workflows/electronic_properties.py,sha256=uAIcGKKLhqoHyDgcOZulEXwTU2EjidyvOndZDYyeJEk,4003
44
+ stjames/workflows/fukui.py,sha256=2J23RjkSOZ-40AM3AdnbJkRBGaCevkjkhnV3pVfa6lo,738
44
45
  stjames/workflows/hydrogen_bond_basicity.py,sha256=Luvov2DlDvZN06W-mU6YaN7wcIrTLwzdoWww-jNE3x4,517
46
+ stjames/workflows/irc.py,sha256=y4KXeHPfPoT9jsroABxWyFdg6k5y3r99hdtvo8g26_A,3148
45
47
  stjames/workflows/molecular_dynamics.py,sha256=4HmYETU1VT2BA4-PqAayRZLjnj1WuYxd5bqpIyH9g5k,2465
46
48
  stjames/workflows/multistage_opt.py,sha256=0ou-UYMGIrewZIg3QZIgwS_eweYdsh2pRplxgRCqLcE,13572
47
- stjames/workflows/pka.py,sha256=zpR90Yv2L-D56o2mGArM8027DWpnFFnay31UR9Xh5Nc,774
48
- stjames/workflows/redox_potential.py,sha256=e7WOyTIC_1NPfh7amvW7YzqQezcswX9YaXT-qPiePAo,3651
49
- stjames/workflows/scan.py,sha256=cQ29DKlLCH7_6_1TFATnaM3ac9G1asUoRoDDlQYAfG0,860
50
- stjames/workflows/spin_states.py,sha256=TXHqB7ClTkkCy1Yfcsv99v2woAhT8oG1-uaF20-oMbQ,4592
51
- stjames/workflows/tautomer.py,sha256=owZOOfGlohxlaOG1pGpx7dVPvas8ZEnl_9Ms5F0Kpms,421
49
+ stjames/workflows/pka.py,sha256=vSbMc7wuUKATNLq2kQyfCyX6aUthCj-XGSoXnuk4GMo,1031
50
+ stjames/workflows/redox_potential.py,sha256=oLx3IyezOkJSXTVhtXkveC5GlsD6crR0kNDcNuSoIsg,3822
51
+ stjames/workflows/scan.py,sha256=uNSuUmVMAV4exNvcv1viVe7930i7GZMn7RtEimnwEE8,1000
52
+ stjames/workflows/spin_states.py,sha256=b-uCf-pHjF_JHbExeb5GdRToE0pIxP0JTd50U130ckI,4693
53
+ stjames/workflows/tautomer.py,sha256=x3TC8hkMs87ZUodLyhce5EUzYoV276ePfPMi7ISWyNU,651
52
54
  stjames/workflows/workflow.py,sha256=tIu5naADYgYS7kdW8quvGEWHWosBcrIdcD7L86v-uMQ,976
53
- stjames-0.0.49.dist-info/LICENSE,sha256=i7ehYBS-6gGmbTcgU4mgk28pyOx2kScJ0kcx8n7bWLM,1084
54
- stjames-0.0.49.dist-info/METADATA,sha256=NFSiP9l2CXcYhOCEGaOQ95nXTxRxwQHSewRZit1FW88,1656
55
- stjames-0.0.49.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
56
- stjames-0.0.49.dist-info/top_level.txt,sha256=FYCwxl6quhYOAgG-mnPQcCK8vsVM7B8rIUrO-WrQ_PI,8
57
- stjames-0.0.49.dist-info/RECORD,,
55
+ stjames-0.0.50.dist-info/LICENSE,sha256=i7ehYBS-6gGmbTcgU4mgk28pyOx2kScJ0kcx8n7bWLM,1084
56
+ stjames-0.0.50.dist-info/METADATA,sha256=RP-MVxDFYu3Ofca3znVgBwmNdbCLGVXsYzd-nniBE7c,1689
57
+ stjames-0.0.50.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
58
+ stjames-0.0.50.dist-info/top_level.txt,sha256=FYCwxl6quhYOAgG-mnPQcCK8vsVM7B8rIUrO-WrQ_PI,8
59
+ stjames-0.0.50.dist-info/RECORD,,