stjames 0.0.61__py3-none-any.whl → 0.0.63__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.

@@ -2,17 +2,19 @@
2
2
 
3
3
  import builtins
4
4
  import gzip
5
- from typing import Any
5
+ from pathlib import Path
6
+ from typing import Any, Callable
6
7
 
7
- from requests import get # type: ignore [import-untyped]
8
+ from requests import get # type: ignore[import-untyped]
8
9
 
9
10
  from .mmcif import mmcif_dict_to_data_dict, mmcif_string_to_mmcif_dict
10
11
  from .pdb import pdb_dict_to_data_dict, pdb_string_to_pdb_dict
11
12
 
12
13
 
13
- def open(path: str, *args, **kwargs) -> Any: # type: ignore [no-untyped-def]
14
- """Opens a file at a given path, works out what filetype it is, and parses
15
- it accordingly.
14
+ def open(path: Path | str, file_dict: bool = False, data_dict: bool = False) -> dict[str, Any]:
15
+ """
16
+ Opens a file at a given path, works out what filetype it is, and parses it
17
+ accordingly.
16
18
 
17
19
  For example:
18
20
  open('/path/to/file.pdb', data_dict=True)
@@ -26,26 +28,29 @@ def open(path: str, *args, **kwargs) -> Any: # type: ignore [no-untyped-def]
26
28
  :param bool file_dict: if ``True``, parsing will stop at the file ``dict``.
27
29
  :param bool data_dict: if ``True``, parsing will stop at the data ``dict``.
28
30
  :rtype: ``File``"""
31
+ path = Path(path)
29
32
 
30
- if str(path)[-3:] == ".gz":
33
+ if path.suffix == ".gz":
31
34
  try:
32
35
  with gzip.open(path) as f:
33
36
  filestring = f.read().decode()
34
37
  except Exception:
35
38
  with gzip.open(path, "rt") as f:
36
39
  filestring = f.read()
37
- return parse_string(filestring, path[:-3], *args, **kwargs)
38
- else:
39
- try:
40
- with builtins.open(path) as f:
41
- filestring = f.read()
42
- except Exception:
43
- with builtins.open(path, "rb") as f:
44
- filestring = f.read() # type: ignore [assignment]
45
- return parse_string(filestring, path, *args, **kwargs)
46
40
 
41
+ return parse_string(filestring, path.suffix, file_dict=file_dict, data_dict=data_dict)
42
+
43
+ try:
44
+ with builtins.open(path) as f:
45
+ filestring = f.read()
46
+ except Exception:
47
+ with builtins.open(path, "rb") as f:
48
+ filestring = f.read() # type: ignore [assignment]
47
49
 
48
- def fetch(code: str, *args, **kwargs) -> Any: # type: ignore [no-untyped-def]
50
+ return parse_string(filestring, path, file_dict=file_dict, data_dict=data_dict)
51
+
52
+
53
+ def fetch(code: str, file_dict: bool = False, data_dict: bool = False) -> dict[str, Any]:
49
54
  """Fetches a file from a remote location via HTTP.
50
55
 
51
56
  If a PDB code is given, the .cif form of that struture will be fetched from
@@ -59,11 +64,11 @@ def fetch(code: str, *args, **kwargs) -> Any: # type: ignore [no-untyped-def]
59
64
  This will get the .mmtf version of structure 1LOL, but only go as far as
60
65
  converting it to an atomium file dictionary.
61
66
 
62
- :param str code: the file to fetch.
63
- :param bool file_dict: if ``True``, parsing will stop at the file ``dict``.
64
- :param bool data_dict: if ``True``, parsing will stop at the data ``dict``.
65
- :raises ValueError: if no file is found.
66
- :rtype: ``File``"""
67
+ :param code: the file to fetch.
68
+ :param file_dict: if ``True``, parsing will stop at the file ``dict``
69
+ :param data_dict: if ``True``, parsing will stop at the data ``dict``
70
+ :raises ValueError: if no file is found
71
+ """
67
72
 
68
73
  if code.startswith("http"):
69
74
  url = code
@@ -76,50 +81,57 @@ def fetch(code: str, *args, **kwargs) -> Any: # type: ignore [no-untyped-def]
76
81
  response = get(url, stream=True)
77
82
  if response.status_code == 200:
78
83
  text = response.content if code.endswith(".mmtf") else response.text
79
- return parse_string(text, code, *args, **kwargs)
80
- raise ValueError("Could not find anything at {}".format(url))
84
+ return parse_string(text, code, file_dict=file_dict, data_dict=data_dict)
81
85
 
86
+ raise ValueError(f"Could not find anything at {url}")
82
87
 
83
- def parse_string(filestring: str, path: str, file_dict: bool = False, data_dict: bool = False) -> Any:
84
- """Takes a filestring and parses it in the appropriate way. You must provide
88
+
89
+ def parse_string(filestring: Any, path: Path | str, file_dict: bool = False, data_dict: bool = False) -> dict[str, Any]:
90
+ """
91
+ Takes a filestring and parses it in the appropriate way. You must provide
85
92
  the string to parse itself, and some other string that ends in either .cif,
86
93
  .mmtf, or .cif - that will determine how the file is parsed.
87
94
 
88
95
  (If this cannot be inferred from the path string, atomium will guess based
89
96
  on the filestring contents.)
90
97
 
91
- :param str filestring: the contents of some file.
92
- :param str path: the filename of the file of origin.
93
- :param bool file_dict: if ``True``, parsing will stop at the file ``dict``.
94
- :param bool data_dict: if ``True``, parsing will stop at the data ``dict``.
95
- :rtype: ``File``"""
98
+ :param filestring: contents of some file
99
+ :param path: filename of the file of origin
100
+ :param file_dict: if ``True``, parsing will stop at the file ``dict``
101
+ :param data_dict: if ``True``, parsing will stop at the data ``dict``
102
+ :return: File
103
+ """
96
104
 
97
105
  file_func, data_func = get_parse_functions(filestring, path)
98
106
  parsed = file_func(filestring)
107
+
99
108
  if not file_dict:
100
109
  parsed = data_func(parsed)
110
+
101
111
  return parsed
102
112
 
103
113
 
104
- def get_parse_functions(filestring: str, path: str) -> Any:
105
- """Works out which parsing functions to use for a given filestring and
106
- returns them.
114
+ def get_parse_functions(filestring: str, path: Path | str) -> tuple[Callable[[str], dict[str, Any]], Callable[[dict[str, Any]], dict[str, Any]]]:
115
+ """
116
+ Determines the parsing functions to use for a given filestring and path.
107
117
 
108
118
  (If this cannot be inferred from the path string, atomium will guess based
109
119
  on the filestring contents.)
110
120
 
111
- :param str filestring: the filestring to inspect.
112
- :param str path: the path to inspect.
113
- :rtype: ``tuple``"""
114
-
115
- if "." in path:
116
- ending = path.split(".")[-1]
117
- if ending in ("mmtf", "cif", "pdb"):
118
- return {
119
- "cif": (mmcif_string_to_mmcif_dict, mmcif_dict_to_data_dict),
120
- "pdb": (pdb_string_to_pdb_dict, pdb_dict_to_data_dict),
121
- }[ending]
121
+ :param filestring: the filestring to inspect
122
+ :param path: the path to inspect
123
+ """
124
+ path = Path(path)
125
+
126
+ funcs = {
127
+ ".mmtf": (mmcif_string_to_mmcif_dict, mmcif_dict_to_data_dict),
128
+ ".cif": (mmcif_string_to_mmcif_dict, mmcif_dict_to_data_dict),
129
+ ".pdb": (pdb_string_to_pdb_dict, pdb_dict_to_data_dict),
130
+ }
131
+
132
+ if path.suffix:
133
+ return funcs.get(path.suffix, (pdb_string_to_pdb_dict, pdb_dict_to_data_dict))
122
134
  elif "_atom_sites" in filestring:
123
135
  return (mmcif_string_to_mmcif_dict, mmcif_dict_to_data_dict)
124
- else:
125
- return (pdb_string_to_pdb_dict, pdb_dict_to_data_dict)
136
+
137
+ return (pdb_string_to_pdb_dict, pdb_dict_to_data_dict)
stjames/molecule.py CHANGED
@@ -1,9 +1,11 @@
1
1
  import re
2
2
  from pathlib import Path
3
- from typing import Annotated, Iterable, Optional, Self
3
+ from typing import Annotated, Iterable, Optional, Self, TypeAlias
4
4
 
5
5
  import pydantic
6
6
  from pydantic import AfterValidator, NonNegativeInt, PositiveInt, ValidationError
7
+ from rdkit import Chem
8
+ from rdkit.Chem import AllChem
7
9
 
8
10
  from .atom import Atom
9
11
  from .base import Base, round_float, round_optional_float
@@ -20,6 +22,8 @@ from .types import (
20
22
  round_vector3d_per_atom,
21
23
  )
22
24
 
25
+ RdkitMol: TypeAlias = Chem.rdchem.Mol | Chem.rdchem.RWMol
26
+
23
27
 
24
28
  class MoleculeReadError(RuntimeError):
25
29
  pass
@@ -263,6 +267,49 @@ class Molecule(Base):
263
267
  except (ValueError, ValidationError) as e:
264
268
  raise MoleculeReadError("Error reading molecule from extxyz") from e
265
269
 
270
+ @classmethod
271
+ def from_rdkit(cls: type[Self], rdkm: RdkitMol, cid: int = 0) -> Self:
272
+ if len(rdkm.GetConformers()) == 0:
273
+ rdkm = _embed_rdkit_mol(rdkm)
274
+
275
+ atoms = []
276
+ atomic_numbers = [atom.GetAtomicNum() for atom in rdkm.GetAtoms()] # type: ignore [no-untyped-call, unused-ignore]
277
+ geom = rdkm.GetConformers()[cid].GetPositions()
278
+
279
+ for i in range(len(atomic_numbers)):
280
+ atoms.append(Atom(atomic_number=atomic_numbers[i], position=geom[i]))
281
+
282
+ charge = Chem.GetFormalCharge(rdkm)
283
+ multiplicity = 1
284
+
285
+ return cls(atoms=atoms, charge=charge, multiplicity=multiplicity)
286
+
287
+ @classmethod
288
+ def from_smiles(cls: type[Self], smiles: str) -> Self:
289
+ rdkm = Chem.MolFromSmiles(smiles)
290
+ assert rdkm is not None
291
+ return cls.from_rdkit(rdkm)
292
+
293
+
294
+ def _embed_rdkit_mol(rdkm: RdkitMol) -> RdkitMol:
295
+ try:
296
+ AllChem.SanitizeMol(rdkm) # type: ignore [attr-defined, unused-ignore]
297
+ except Exception as e:
298
+ raise ValueError("Molecule could not be generated -- invalid chemistry!\n") from e
299
+
300
+ rdkm = AllChem.AddHs(rdkm) # type: ignore [attr-defined, unused-ignore]
301
+ try:
302
+ status1 = AllChem.EmbedMolecule(rdkm, maxAttempts=200) # type: ignore [attr-defined, unused-ignore]
303
+ assert status1 >= 0
304
+ except Exception as e:
305
+ status1 = AllChem.EmbedMolecule(rdkm, maxAttempts=200, useRandomCoords=True) # type: ignore [attr-defined, unused-ignore]
306
+ if status1 < 0:
307
+ raise ValueError(f"Cannot embed molecule! Error: {e}")
308
+
309
+ AllChem.MMFFOptimizeMolecule(rdkm, maxIters=200) # type: ignore [attr-defined, call-arg, unused-ignore]
310
+
311
+ return rdkm
312
+
266
313
 
267
314
  def parse_comment_line(line: str) -> PeriodicCell:
268
315
  """
stjames/pdb.py CHANGED
@@ -214,23 +214,42 @@ def pdb_from_mmcif_filestring(pdb: str) -> PDB:
214
214
  return PDB.model_validate(mmcif_dict_to_data_dict(mmcif_string_to_mmcif_dict(pdb)))
215
215
 
216
216
 
217
- def pdb_object_to_pdb_filestring(pdb: PDB) -> str:
217
+ def pdb_object_to_pdb_filestring(
218
+ pdb: PDB,
219
+ header: bool = False,
220
+ source: bool = False,
221
+ keyword: bool = False,
222
+ seqres: bool = True,
223
+ hetnam: bool = True,
224
+ remark: bool = True,
225
+ crystallography: bool = False,
226
+ ) -> str:
218
227
  pdb_lines: list[str] = []
219
228
  chains: list[str] = []
220
- # Header
221
- pdb_lines.extend(_build_header_section(pdb))
222
- pdb_lines.extend(_build_source_section(pdb))
223
- pdb_lines.extend(_build_keyword_section(pdb))
229
+
230
+ if header:
231
+ pdb_lines.extend(_build_header_section(pdb))
232
+
233
+ if source:
234
+ pdb_lines.extend(_build_source_section(pdb))
235
+
236
+ if keyword:
237
+ pdb_lines.extend(_build_keyword_section(pdb))
224
238
 
225
239
  full_name_dict: dict[str, str] = {}
226
240
  seqres_lines, chains = _build_secondary_structure_and_seqres(pdb, full_name_dict)
227
241
 
228
- pdb_lines.extend(seqres_lines)
229
- pdb_lines.extend(_build_hetname_section(full_name_dict))
242
+ if seqres:
243
+ pdb_lines.extend(seqres_lines)
244
+
245
+ if hetnam:
246
+ pdb_lines.extend(_build_hetname_section(full_name_dict))
230
247
 
231
- pdb_lines.extend(_build_remark_section(pdb, chains))
248
+ if remark:
249
+ pdb_lines.extend(_build_remark_section(pdb, chains))
232
250
 
233
- pdb_lines.extend(_build_crystallography_section(pdb))
251
+ if crystallography:
252
+ pdb_lines.extend(_build_crystallography_section(pdb))
234
253
 
235
254
  for model_index, model in enumerate(pdb.models, start=1):
236
255
  # If more than one model, add MODEL line
@@ -633,11 +652,11 @@ def _build_hetname_section(full_name_dict: dict[str, str]) -> list[str]:
633
652
  def _build_remark_section(pdb: PDB, chains: list[str]) -> list[str]:
634
653
  """Builds REMARK lines (resolution, R factors, biomolecule and missing residues)."""
635
654
  lines = []
636
- lines.append(f"REMARK 2 RESOLUTION. {pdb.quality.resolution:>7} ANGSTROMS.")
655
+ lines.append(f"REMARK 2 RESOLUTION. {pdb.quality.resolution or '':>7} ANGSTROMS.")
637
656
  if pdb.quality.rfree:
638
- lines.append(f"REMARK 3 FREE R VALUE : {pdb.quality.rfree}")
657
+ lines.append(f"REMARK 3 FREE R VALUE : {pdb.quality.rfree or ''}")
639
658
  if pdb.quality.rvalue:
640
- lines.append(f"REMARK 3 R VALUE (WORKING SET) : {pdb.quality.rvalue}")
659
+ lines.append(f"REMARK 3 R VALUE (WORKING SET) : {pdb.quality.rvalue or ''}")
641
660
 
642
661
  # REMARK 350: Biomolecule details
643
662
  lines.append("REMARK 350")
stjames/types.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Callable, TypeAlias
1
+ from typing import Callable, Iterable, TypeAlias
2
2
 
3
3
  UUID: TypeAlias = str
4
4
 
@@ -10,6 +10,15 @@ FloatPerAtom: TypeAlias = list[float]
10
10
  Matrix3x3: TypeAlias = tuple[Vector3D, Vector3D, Vector3D]
11
11
 
12
12
 
13
+ def round_list(round_to: int = 6) -> Callable[[Iterable[float]], list[float]]:
14
+ """Create a validator that rounds each float in a list to a given number of decimal places."""
15
+
16
+ def rounder(values: Iterable[float]) -> list[float]:
17
+ return [round(value, round_to) for value in values]
18
+
19
+ return rounder
20
+
21
+
13
22
  def round_vector3d(round_to: int = 6) -> Callable[[Vector3D], Vector3D]:
14
23
  """Create a validator that rounds each component of a Vector3D to a given number of decimal places."""
15
24
 
@@ -18,9 +18,10 @@ from .multistage_opt import *
18
18
  from .pka import *
19
19
  from .redox_potential import *
20
20
  from .scan import *
21
+ from .solubility import *
21
22
  from .spin_states import *
22
23
  from .tautomer import *
23
- from .workflow import Workflow
24
+ from .workflow import *
24
25
 
25
26
  WORKFLOW_NAME = Literal[
26
27
  "admet",
@@ -39,11 +40,12 @@ WORKFLOW_NAME = Literal[
39
40
  "pka",
40
41
  "redox_potential",
41
42
  "scan",
43
+ "solubility",
42
44
  "spin_states",
43
45
  "tautomers",
44
46
  ]
45
47
 
46
- WORKFLOW_MAPPING: dict[str, Workflow] = {
48
+ WORKFLOW_MAPPING: dict[WORKFLOW_NAME, Workflow] = {
47
49
  "admet": ADMETWorkflow, # type: ignore [dict-item]
48
50
  "basic_calculation": BasicCalculationWorkflow, # type: ignore [dict-item]
49
51
  "bde": BDEWorkflow, # type: ignore [dict-item]
@@ -60,6 +62,7 @@ WORKFLOW_MAPPING: dict[str, Workflow] = {
60
62
  "pka": pKaWorkflow, # type: ignore [dict-item]
61
63
  "redox_potential": RedoxPotentialWorkflow, # type: ignore [dict-item]
62
64
  "scan": ScanWorkflow, # type: ignore [dict-item]
65
+ "solubility": SolubilityWorkflow, # type: ignore [dict-item]
63
66
  "spin_states": SpinStatesWorkflow, # type: ignore [dict-item]
64
67
  "tautomers": TautomerWorkflow, # type: ignore [dict-item]
65
68
  }
@@ -1,7 +1,18 @@
1
- from typing import Optional
1
+ """ADME-Tox property prediction workflow."""
2
2
 
3
- from .workflow import Workflow
3
+ from .workflow import MoleculeWorkflow
4
4
 
5
5
 
6
- class ADMETWorkflow(Workflow):
7
- properties: Optional[dict[str, float | int]] = None
6
+ class ADMETWorkflow(MoleculeWorkflow):
7
+ """
8
+ A workflow for predicting ADME-Tox properties.
9
+
10
+ Inherited:
11
+ :param initial_molecule: Molecule of interest
12
+ :param mode: Mode for workflow (currently unused)
13
+
14
+ New:
15
+ :param properties: predicted properties
16
+ """
17
+
18
+ properties: dict[str, float | int] | None = None
@@ -1,9 +1,24 @@
1
+ """Basic calculation workflow."""
2
+
1
3
  from ..settings import Settings
2
4
  from ..types import UUID
3
- from .workflow import Workflow
5
+ from .workflow import MoleculeWorkflow
6
+
7
+
8
+ class BasicCalculationWorkflow(MoleculeWorkflow):
9
+ """
10
+ Workflow for a basic calculation.
11
+
12
+ Inherited:
13
+ :param initial_molecule: Molecule of interest
14
+ :param mode: Mode for workflow
4
15
 
16
+ New:
17
+ :param settings: Settings for running the calculation
18
+ :param engine: Engine to use
19
+ :param calculation_uuid: UUID of the calculation
20
+ """
5
21
 
6
- class BasicCalculationWorkflow(Workflow):
7
22
  settings: Settings
8
23
  engine: str
9
24
  calculation_uuid: UUID | None = None
stjames/workflows/bde.py CHANGED
@@ -1,4 +1,4 @@
1
- """Bond Dissociation Energy (BDE) workflow."""
1
+ """Bond-dissociation energy (BDE) workflow."""
2
2
 
3
3
  import itertools
4
4
  from typing import Annotated, Any, Iterable, Self, TypeVar
@@ -10,7 +10,7 @@ from ..mode import Mode
10
10
  from ..molecule import Molecule
11
11
  from ..types import UUID
12
12
  from .multistage_opt import MultiStageOptMixin
13
- from .workflow import Workflow
13
+ from .workflow import MoleculeWorkflow
14
14
 
15
15
  # the id of a mutable object may change, thus using object()
16
16
  _sentinel_mso_mode = object()
@@ -19,7 +19,7 @@ _T = TypeVar("_T")
19
19
 
20
20
  class BDE(BaseModel):
21
21
  """
22
- Bond Dissociation Energy (BDE) result.
22
+ Bond-dissociation energy (BDE) result.
23
23
 
24
24
  energy => (E_{fragment1} + E_{fragment2}) - E_{starting molecule}
25
25
 
@@ -49,9 +49,9 @@ class BDE(BaseModel):
49
49
  return f"<{type(self).__name__} {self.fragment_idxs} {energy}>"
50
50
 
51
51
 
52
- class BDEWorkflow(Workflow, MultiStageOptMixin):
52
+ class BDEWorkflow(MoleculeWorkflow, MultiStageOptMixin):
53
53
  """
54
- Bond Dissociation Energy (BDE) workflow.
54
+ Bond-dissociation energy (BDE) workflow.
55
55
 
56
56
  Uses the modes from MultiStageOptSettings to compute BDEs.
57
57
 
@@ -1,3 +1,5 @@
1
+ """Deprecated conformer search workflow, use ConformerSearchWorkflow instead."""
2
+
1
3
  from typing import Annotated, Any, Optional
2
4
 
3
5
  from pydantic import AfterValidator
@@ -7,7 +9,7 @@ from ..constraint import Constraint
7
9
  from ..method import Method
8
10
  from ..mode import Mode
9
11
  from ..solvent import Solvent
10
- from .workflow import Workflow
12
+ from .workflow import MoleculeWorkflow
11
13
 
12
14
 
13
15
  class ConformerSettings(Base):
@@ -42,7 +44,7 @@ class Conformer(Base):
42
44
  uuid: Optional[str] = None
43
45
 
44
46
 
45
- class ConformerWorkflow(Workflow):
47
+ class ConformerWorkflow(MoleculeWorkflow):
46
48
  mode: Mode = Mode.RAPID
47
49
  settings: ConformerSettings = ConformerSettings()
48
50
  conformers: list[Conformer] = []
@@ -1,4 +1,4 @@
1
- """Conformer Search Workflow."""
1
+ """Conformer search workflow."""
2
2
 
3
3
  from abc import ABC
4
4
  from typing import Annotated, Self, Sequence, TypeVar
@@ -11,7 +11,7 @@ from ..method import Method, XTBMethod
11
11
  from ..mode import Mode
12
12
  from ..types import UUID, FloatPerAtom, round_float_per_atom
13
13
  from .multistage_opt import MultiStageOptMixin
14
- from .workflow import Workflow
14
+ from .workflow import MoleculeWorkflow
15
15
 
16
16
  _sentinel = object()
17
17
 
@@ -338,7 +338,7 @@ class ConformerSearchMixin(ConformerGenMixin, MultiStageOptMixin):
338
338
  return self
339
339
 
340
340
 
341
- class ConformerSearchWorkflow(ConformerSearchMixin, Workflow):
341
+ class ConformerSearchWorkflow(ConformerSearchMixin, MoleculeWorkflow):
342
342
  """
343
343
  ConformerSearch Workflow.
344
344
 
@@ -1,11 +1,24 @@
1
+ """Molecular descriptors workflow."""
2
+
1
3
  from ..types import UUID
2
- from .workflow import Workflow
4
+ from .workflow import MoleculeWorkflow
3
5
 
4
6
  Descriptors = dict[str, dict[str, float] | tuple[float | None, ...] | float]
5
7
 
6
8
 
7
- class DescriptorsWorkflow(Workflow):
8
- # UUID of optimization
9
+ class DescriptorsWorkflow(MoleculeWorkflow):
10
+ """
11
+ A workflow for calculating molecular descriptors.
12
+
13
+ Inherited:
14
+ :param initial_molecule: Molecule of interest
15
+ :param mode: Mode for workflow
16
+
17
+ New:
18
+ :param optimization: UUID of optimization
19
+ :param descriptors: calculated descriptors
20
+ """
21
+
9
22
  optimization: UUID | None = None
10
23
 
11
24
  descriptors: Descriptors | None = None
@@ -7,7 +7,7 @@ from pydantic import AfterValidator, ConfigDict, field_validator, model_validato
7
7
  from ..base import Base, round_float
8
8
  from ..pdb import PDB
9
9
  from ..types import UUID, Vector3D
10
- from .workflow import Workflow
10
+ from .workflow import MoleculeWorkflow
11
11
 
12
12
 
13
13
  class Score(Base):
@@ -22,7 +22,7 @@ class Score(Base):
22
22
  score: Annotated[float, AfterValidator(round_float(3))]
23
23
 
24
24
 
25
- class DockingWorkflow(Workflow):
25
+ class DockingWorkflow(MoleculeWorkflow):
26
26
  """
27
27
  Docking workflow.
28
28
 
@@ -39,6 +39,7 @@ class DockingWorkflow(Workflow):
39
39
  :param smiles: SMILES strings of the ligands (optional)
40
40
  :param do_csearch: whether to csearch starting structures
41
41
  :param do_optimization: whether to optimize starting structures
42
+ :param do_pose_refinement: whether to optimize non-rotatable bonds in output poses
42
43
  :param conformers: UUIDs of optimized conformers
43
44
  :param target: PDB of the protein.
44
45
  :param target_uuid: UUID of the protein.
@@ -58,7 +59,7 @@ class DockingWorkflow(Workflow):
58
59
  target_uuid: UUID | None = None
59
60
  pocket: tuple[Vector3D, Vector3D]
60
61
 
61
- do_pose_hydrogen_refinement: bool = True
62
+ do_pose_refinement: bool = True
62
63
  scores: list[Score] = []
63
64
 
64
65
  def __str__(self) -> str:
@@ -84,7 +85,7 @@ class DockingWorkflow(Workflow):
84
85
 
85
86
  @field_validator("pocket", mode="after")
86
87
  def validate_pocket(cls, pocket: tuple[Vector3D, Vector3D]) -> tuple[Vector3D, Vector3D]:
87
- center, size = pocket
88
+ _center, size = pocket
88
89
  if any(q <= 0 for q in size):
89
90
  raise ValueError(f"Pocket size must be positive, got: {size}")
90
91
  return pocket
@@ -1,3 +1,5 @@
1
+ """Electronic properties workflow."""
2
+
1
3
  from typing import Annotated
2
4
 
3
5
  from pydantic import AfterValidator, NonNegativeFloat, NonNegativeInt
@@ -5,7 +7,7 @@ from pydantic import AfterValidator, NonNegativeFloat, NonNegativeInt
5
7
  from ..base import Base, round_float
6
8
  from ..settings import Settings
7
9
  from ..types import UUID, FloatPerAtom, Matrix3x3, Vector3D, round_optional_float_per_atom, round_optional_matrix3x3, round_optional_vector3d
8
- from .workflow import Workflow
10
+ from .workflow import MoleculeWorkflow
9
11
 
10
12
 
11
13
  class PropertyCubePoint(Base):
@@ -34,12 +36,13 @@ class MolecularOrbitalCube(PropertyCube):
34
36
  energy: Annotated[float, AfterValidator(round_float(6))]
35
37
 
36
38
 
37
- class ElectronicPropertiesWorkflow(Workflow):
39
+ class ElectronicPropertiesWorkflow(MoleculeWorkflow):
38
40
  """
39
41
  Workflow for computing electronic properties.
40
42
 
41
43
  Inherited
42
44
  :param initial_molecule: Molecule of interest
45
+ :param mode: Mode for workflow (currently unused)
43
46
 
44
47
  Config settings:
45
48
  :param settings: settings for the calculation
@@ -1,13 +1,30 @@
1
+ """Fukui index workflow."""
2
+
1
3
  from typing import Annotated
2
4
 
3
5
  from pydantic import AfterValidator
4
6
 
5
7
  from ..base import round_optional_float
6
8
  from ..types import UUID, FloatPerAtom, round_optional_float_per_atom
7
- from .workflow import Workflow
9
+ from .workflow import MoleculeWorkflow
10
+
11
+
12
+ class FukuiIndexWorkflow(MoleculeWorkflow):
13
+ """
14
+ Workflow for calculating Fukui indices.
15
+
16
+ Inherited:
17
+ :param initial_molecule: Molecule of interest
18
+ :param mode: Mode for workflow (currently unused)
8
19
 
20
+ Results:
21
+ :param optimization: UUID of optimization
22
+ :param global_electrophilicity_index: global electrophilicity index
23
+ :param fukui_positive: Fukui index for positive charges
24
+ :param fukui_negative: Fukui index for negative charges
25
+ :param fukui_zero: Fukui index for zero charges
26
+ """
9
27
 
10
- class FukuiIndexWorkflow(Workflow):
11
28
  # UUID of optimization
12
29
  optimization: UUID | None = None
13
30