mdprep 0.2.0__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.
Files changed (100) hide show
  1. mdprep/__init__.py +7 -0
  2. mdprep/_selftest_data/basic_protein.yaml +33 -0
  3. mdprep/_selftest_data/ligand_sub.mol2 +11 -0
  4. mdprep/ambertools/__init__.py +2 -0
  5. mdprep/ambertools/antechamber.py +110 -0
  6. mdprep/ambertools/commands.py +33 -0
  7. mdprep/ambertools/mol2.py +415 -0
  8. mdprep/ambertools/parmchk2.py +89 -0
  9. mdprep/ambertools/resp.py +570 -0
  10. mdprep/charges/__init__.py +1 -0
  11. mdprep/charges/esp_grid.py +190 -0
  12. mdprep/cli.py +451 -0
  13. mdprep/config/__init__.py +9 -0
  14. mdprep/config/loader.py +146 -0
  15. mdprep/config/models.py +1100 -0
  16. mdprep/external/__init__.py +14 -0
  17. mdprep/external/discovery.py +14 -0
  18. mdprep/external/runner.py +55 -0
  19. mdprep/leap/__init__.py +2 -0
  20. mdprep/leap/builder.py +476 -0
  21. mdprep/leap/forcefields.py +93 -0
  22. mdprep/leap/ions.py +112 -0
  23. mdprep/leap/log_parser.py +170 -0
  24. mdprep/leap/report.py +99 -0
  25. mdprep/leap/residues.py +653 -0
  26. mdprep/leap/runner.py +65 -0
  27. mdprep/ligands/__init__.py +2 -0
  28. mdprep/ligands/extract.py +355 -0
  29. mdprep/ligands/identity.py +39 -0
  30. mdprep/ligands/pyscf_charges.py +242 -0
  31. mdprep/ligands/report.py +146 -0
  32. mdprep/ligands/workflow.py +838 -0
  33. mdprep/md/__init__.py +5 -0
  34. mdprep/md/roe_brooks.py +650 -0
  35. mdprep/metals/__init__.py +2 -0
  36. mdprep/metals/c4.py +157 -0
  37. mdprep/metals/coordination.py +313 -0
  38. mdprep/metals/gxtb_hessian.py +467 -0
  39. mdprep/metals/hydrogenate.py +344 -0
  40. mdprep/metals/mace_polar1_hessian.py +357 -0
  41. mdprep/metals/mace_polar1_worker.py +295 -0
  42. mdprep/metals/mcpb.py +949 -0
  43. mdprep/metals/mcpb_compat.py +125 -0
  44. mdprep/metals/nonbonded.py +294 -0
  45. mdprep/metals/parameter_comparison.py +563 -0
  46. mdprep/metals/pyscf_mcpb.py +1388 -0
  47. mdprep/metals/report.py +119 -0
  48. mdprep/metals/workflow.py +320 -0
  49. mdprep/protonation/__init__.py +15 -0
  50. mdprep/protonation/apply.py +968 -0
  51. mdprep/protonation/disulfide_states.py +141 -0
  52. mdprep/protonation/histidine_geometry.py +950 -0
  53. mdprep/protonation/histidine_xtb.py +390 -0
  54. mdprep/protonation/overrides.py +64 -0
  55. mdprep/protonation/pka_rules.py +165 -0
  56. mdprep/protonation/propka.py +174 -0
  57. mdprep/protonation/propka_parser.py +140 -0
  58. mdprep/protonation/report.py +187 -0
  59. mdprep/protonation/states.py +52 -0
  60. mdprep/protonation/temporary_hydrogenation.py +238 -0
  61. mdprep/protonation/xtb_parser.py +79 -0
  62. mdprep/protonation/xtb_runner.py +108 -0
  63. mdprep/qm/__init__.py +2 -0
  64. mdprep/qm/point_charges.py +233 -0
  65. mdprep/qm/pyscf_esp.py +70 -0
  66. mdprep/qm/pyscf_runner.py +570 -0
  67. mdprep/refinement/__init__.py +5 -0
  68. mdprep/refinement/ash.py +541 -0
  69. mdprep/refinement/coordinates.py +210 -0
  70. mdprep/refinement/mapping.py +163 -0
  71. mdprep/refinement/report.py +66 -0
  72. mdprep/refinement/selection.py +436 -0
  73. mdprep/refinement/workflow.py +188 -0
  74. mdprep/reports/__init__.py +8 -0
  75. mdprep/reports/structure_report.py +92 -0
  76. mdprep/structure/__init__.py +14 -0
  77. mdprep/structure/classify.py +92 -0
  78. mdprep/structure/disulfides.py +51 -0
  79. mdprep/structure/inspect.py +105 -0
  80. mdprep/structure/models.py +86 -0
  81. mdprep/structure/normalize.py +363 -0
  82. mdprep/structure/pdb.py +511 -0
  83. mdprep/structure/repair.py +566 -0
  84. mdprep/structure/selectors.py +204 -0
  85. mdprep/structure/writer.py +84 -0
  86. mdprep/validation/__init__.py +2 -0
  87. mdprep/validation/openmm_check.py +86 -0
  88. mdprep/validation/parmed_check.py +43 -0
  89. mdprep/validation/topology.py +239 -0
  90. mdprep/workflows/__init__.py +6 -0
  91. mdprep/workflows/init.py +242 -0
  92. mdprep/workflows/inspect.py +21 -0
  93. mdprep/workflows/prepare.py +763 -0
  94. mdprep/workflows/selftest.py +220 -0
  95. mdprep/workflows/validate.py +30 -0
  96. mdprep-0.2.0.dist-info/METADATA +264 -0
  97. mdprep-0.2.0.dist-info/RECORD +100 -0
  98. mdprep-0.2.0.dist-info/WHEEL +4 -0
  99. mdprep-0.2.0.dist-info/entry_points.txt +2 -0
  100. mdprep-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,141 @@
1
+ """CYX assignment from configured and detected disulfides."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from mdprep.config.models import ManifestConfig
8
+ from mdprep.structure.disulfides import detect_possible_disulfides
9
+ from mdprep.structure.models import PdbStructure, ResidueRecord
10
+ from mdprep.structure.selectors import SelectorError, resolve_residue_selector
11
+
12
+
13
+ class DisulfideAssignmentError(ValueError):
14
+ """Raised when disulfide residue assignment is incompatible with manual states."""
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class DisulfideResidueAssignment:
19
+ residue: ResidueRecord
20
+ source: str
21
+ reason: str
22
+ partner: ResidueRecord
23
+ distance_angstrom: float | None = None
24
+
25
+
26
+ def resolve_disulfide_assignments(
27
+ structure: PdbStructure,
28
+ manifest: ManifestConfig,
29
+ ) -> list[DisulfideResidueAssignment]:
30
+ assignments: list[DisulfideResidueAssignment] = []
31
+ forced_pairs = _resolve_configured_pairs(structure, manifest.disulfides.force, "force")
32
+ forbidden_pairs = {
33
+ _pair_key(a, b)
34
+ for a, b in _resolve_configured_pairs(structure, manifest.disulfides.forbid, "forbid")
35
+ }
36
+ forced_keys = {_pair_key(a, b) for a, b in forced_pairs}
37
+
38
+ for residue_a, residue_b in forced_pairs:
39
+ assignments.extend(
40
+ _pair_assignments(
41
+ residue_a,
42
+ residue_b,
43
+ source="forced_disulfide",
44
+ reason="Configured disulfide force entry",
45
+ distance_angstrom=None,
46
+ )
47
+ )
48
+
49
+ if manifest.disulfides.auto_detect:
50
+ candidates = detect_possible_disulfides(
51
+ structure.residues,
52
+ cutoff_angstrom=manifest.disulfides.detection_cutoff_angstrom,
53
+ )
54
+ residue_lookup = {_residue_identity(residue): residue for residue in structure.residues}
55
+ for candidate in candidates:
56
+ residue_a = residue_lookup[_residue_identity_from_dict(candidate.a.to_dict())]
57
+ residue_b = residue_lookup[_residue_identity_from_dict(candidate.b.to_dict())]
58
+ key = _pair_key(residue_a, residue_b)
59
+ if key in forbidden_pairs or key in forced_keys:
60
+ continue
61
+ assignments.extend(
62
+ _pair_assignments(
63
+ residue_a,
64
+ residue_b,
65
+ source="auto_disulfide",
66
+ reason="SG-SG distance within configured cutoff",
67
+ distance_angstrom=candidate.distance_angstrom,
68
+ )
69
+ )
70
+ return _dedupe_assignments(assignments)
71
+
72
+
73
+ def _resolve_configured_pairs(
74
+ structure: PdbStructure,
75
+ pairs: list[object],
76
+ label: str,
77
+ ) -> list[tuple[ResidueRecord, ResidueRecord]]:
78
+ resolved: list[tuple[ResidueRecord, ResidueRecord]] = []
79
+ for index, pair in enumerate(pairs, start=1):
80
+ try:
81
+ residue_a = resolve_residue_selector(structure, pair.a.model_dump()) # type: ignore[attr-defined]
82
+ residue_b = resolve_residue_selector(structure, pair.b.model_dump()) # type: ignore[attr-defined]
83
+ except SelectorError as exc:
84
+ raise DisulfideAssignmentError(
85
+ f"Disulfide {label}[{index}] selector did not resolve exactly one residue: {exc}"
86
+ ) from exc
87
+ resolved.append((residue_a, residue_b))
88
+ return resolved
89
+
90
+
91
+ def _pair_assignments(
92
+ residue_a: ResidueRecord,
93
+ residue_b: ResidueRecord,
94
+ *,
95
+ source: str,
96
+ reason: str,
97
+ distance_angstrom: float | None,
98
+ ) -> list[DisulfideResidueAssignment]:
99
+ return [
100
+ DisulfideResidueAssignment(
101
+ residue=residue_a,
102
+ source=source,
103
+ reason=reason,
104
+ partner=residue_b,
105
+ distance_angstrom=distance_angstrom,
106
+ ),
107
+ DisulfideResidueAssignment(
108
+ residue=residue_b,
109
+ source=source,
110
+ reason=reason,
111
+ partner=residue_a,
112
+ distance_angstrom=distance_angstrom,
113
+ ),
114
+ ]
115
+
116
+
117
+ def _dedupe_assignments(
118
+ assignments: list[DisulfideResidueAssignment],
119
+ ) -> list[DisulfideResidueAssignment]:
120
+ seen: set[tuple[tuple[str, int, str | None], str]] = set()
121
+ deduped: list[DisulfideResidueAssignment] = []
122
+ for assignment in assignments:
123
+ key = (_residue_identity(assignment.residue), assignment.source)
124
+ if key in seen:
125
+ continue
126
+ seen.add(key)
127
+ deduped.append(assignment)
128
+ return deduped
129
+
130
+
131
+ def _pair_key(a: ResidueRecord, b: ResidueRecord) -> frozenset[tuple[str, int, str | None]]:
132
+ return frozenset({_residue_identity(a), _residue_identity(b)})
133
+
134
+
135
+ def _residue_identity(residue: ResidueRecord) -> tuple[str, int, str | None]:
136
+ return (residue.id.chain_id, residue.id.resid, residue.id.icode)
137
+
138
+
139
+ def _residue_identity_from_dict(data: dict[str, object]) -> tuple[str, int, str | None]:
140
+ return (str(data["chain_id"]), int(data["resid"]), data["icode"]) # type: ignore[arg-type]
141
+