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,242 @@
1
+ """Starter manifest generation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from mdprep.structure.inspect import InspectionSummary, inspect_pdb_structure
8
+
9
+
10
+ def generate_starter_manifest(
11
+ input_structure: str | Path,
12
+ *,
13
+ output_path: str | Path,
14
+ overwrite: bool = False,
15
+ forcefield: str = "ff14SB",
16
+ water_model: str = "TIP3P",
17
+ ph: float = 7.0,
18
+ output_dir: str | None = None,
19
+ protonation_method: str = "manual_only",
20
+ include_ligand_placeholders: bool = False,
21
+ ) -> Path:
22
+ input_path = Path(input_structure)
23
+ target = Path(output_path)
24
+ if target.exists() and not overwrite:
25
+ raise FileExistsError(f"Output manifest already exists: {target}. Use --overwrite to replace it.")
26
+
27
+ summary = inspect_pdb_structure(input_path)
28
+ project_name = input_path.stem
29
+ resolved_output_dir = output_dir or f"prepared/{project_name}"
30
+ manifest_text = _render_manifest(
31
+ summary,
32
+ project_name=project_name,
33
+ input_structure=str(input_path),
34
+ output_dir=resolved_output_dir,
35
+ forcefield=forcefield,
36
+ water_model=water_model,
37
+ ph=ph,
38
+ protonation_method=protonation_method,
39
+ include_ligand_placeholders=include_ligand_placeholders,
40
+ )
41
+ target.parent.mkdir(parents=True, exist_ok=True)
42
+ target.write_text(manifest_text, encoding="utf-8")
43
+ return target
44
+
45
+
46
+ def _render_manifest(
47
+ summary: InspectionSummary,
48
+ *,
49
+ project_name: str,
50
+ input_structure: str,
51
+ output_dir: str,
52
+ forcefield: str,
53
+ water_model: str,
54
+ ph: float,
55
+ protonation_method: str,
56
+ include_ligand_placeholders: bool,
57
+ ) -> str:
58
+ lines: list[str] = [
59
+ "# mdprep starter manifest",
60
+ "# Review all chemistry-sensitive settings before running later preparation stages.",
61
+ ]
62
+ lines.extend(_comment_residues("Likely ligands/cofactors found", summary.likely_ligands))
63
+ lines.extend(_comment_residues("Histidines found", summary.histidines))
64
+ lines.extend(_comment_residues("Titratable residues found", summary.titratable_residues))
65
+ if summary.possible_disulfides:
66
+ lines.append("# Possible disulfides found:")
67
+ for candidate in summary.possible_disulfides:
68
+ lines.append(
69
+ f"# - {candidate.a.display()} -- {candidate.b.display()} "
70
+ f"({candidate.distance_angstrom:.3f} A)"
71
+ )
72
+ else:
73
+ lines.append("# Possible disulfides found: none")
74
+ lines.extend(
75
+ [
76
+ "",
77
+ "project:",
78
+ f" name: {project_name}",
79
+ f" input_structure: {input_structure}",
80
+ f" output_dir: {output_dir}",
81
+ "",
82
+ "structure:",
83
+ " keep_crystal_waters: true",
84
+ " altloc_policy: highest_occupancy",
85
+ " remove_unknown_heterogens: false",
86
+ " preserve_chain_ids: true",
87
+ " remove_input_hydrogens: true",
88
+ "",
89
+ "protein:",
90
+ f" forcefield: {forcefield}",
91
+ f" water_model: {water_model}",
92
+ "",
93
+ "protonation:",
94
+ f" ph: {ph}",
95
+ f" method: {protonation_method}",
96
+ " propka:",
97
+ " executable: null",
98
+ " fallback_executables:",
99
+ " - propka3",
100
+ " - propka",
101
+ " extra_args: []",
102
+ " require_success: true",
103
+ " overrides: []",
104
+ " histidine:",
105
+ " neutral_tautomer_method: xtb",
106
+ " xtb:",
107
+ " executable: xtb",
108
+ " model: gfn2",
109
+ " mode: opt",
110
+ " opt_level: loose",
111
+ " solvent: water",
112
+ " cutoff_angstrom: 5.0",
113
+ " extra_args: []",
114
+ " energy_close_call_kcal_mol: 0.5",
115
+ " add_missing_water_hydrogens: true",
116
+ " water_oh_distance_angstrom: 0.9572",
117
+ " water_hoh_angle_degrees: 104.52",
118
+ " scf_iterations: 500",
119
+ " electronic_temperature_kelvin: 1000.0",
120
+ "",
121
+ "disulfides:",
122
+ " auto_detect: true",
123
+ " detection_cutoff_angstrom: 2.2",
124
+ " force: []",
125
+ " forbid: []",
126
+ "",
127
+ "ligands:",
128
+ ]
129
+ )
130
+ if summary.likely_ligands and include_ligand_placeholders:
131
+ lines.append(" # Placeholder ligand entries were generated from likely heterogens.")
132
+ lines.append(" # Check every net_charge before running ligand parameterization.")
133
+ for residue in summary.likely_ligands:
134
+ ligand_id = f"{residue.id.resname.lower()}_{residue.id.resid}"
135
+ lines.extend(
136
+ [
137
+ f" - id: {ligand_id}",
138
+ " selector:",
139
+ f" chain: {_yaml_string(residue.id.chain_id)}",
140
+ f" resname: {residue.id.resname}",
141
+ f" resid: {residue.id.resid}",
142
+ f" icode: {_yaml_null_or_string(residue.id.icode)}",
143
+ " net_charge: 0",
144
+ " multiplicity: 1",
145
+ " atom_types: gaff2",
146
+ " charge_method: am1bcc",
147
+ " user_mol2: null",
148
+ " user_frcmod: null",
149
+ " preserve_atom_names: true",
150
+ " preserve_coordinates: true",
151
+ " allow_atom_renaming: false",
152
+ " allow_coordinate_changes: false",
153
+ " qmmesp: null",
154
+ ]
155
+ )
156
+ else:
157
+ lines.append(" []")
158
+ lines.extend(_comment_ligand_placeholders(summary.likely_ligands))
159
+ lines.extend(
160
+ [
161
+ "",
162
+ "metals: []",
163
+ "",
164
+ "solvation:",
165
+ " enabled: true",
166
+ " box: truncated_octahedron",
167
+ " buffer_angstrom: 10.0",
168
+ " neutralize: true",
169
+ " salt_concentration_molar: 0.15",
170
+ " positive_ion: Na+",
171
+ " negative_ion: Cl-",
172
+ "",
173
+ "validation:",
174
+ " run_openmm_energy_check: true",
175
+ " fail_on_warnings: false",
176
+ " fail_on_missing_parameters: true",
177
+ " fail_on_noninteger_ligand_charge: true",
178
+ "",
179
+ "molecular_dynamics:",
180
+ " enabled: false",
181
+ "",
182
+ ]
183
+ )
184
+ return "\n".join(lines)
185
+
186
+
187
+ def _comment_residues(title: str, residues: list[object]) -> list[str]:
188
+ if not residues:
189
+ return [f"# {title}: none"]
190
+ lines = [f"# {title}:"]
191
+ for residue in residues:
192
+ residue_id = residue.id # type: ignore[attr-defined]
193
+ lines.append(f"# - {residue_id.display()}")
194
+ return lines
195
+
196
+
197
+ def _comment_ligand_placeholders(residues: list[object]) -> list[str]:
198
+ if not residues:
199
+ return [
200
+ "# Example ligand block:",
201
+ "# - id: sub_501",
202
+ "# selector: {chain: B, resname: SUB, resid: 501, icode: null}",
203
+ "# net_charge: 0 # CHECK THIS VALUE",
204
+ "# multiplicity: 1",
205
+ "# atom_types: gaff2",
206
+ "# charge_method: am1bcc",
207
+ "# user_mol2: null",
208
+ "# user_frcmod: null",
209
+ ]
210
+ lines = [
211
+ "# Detected ligand placeholder suggestions are commented out by default.",
212
+ "# Re-run with --include-ligand-placeholders to make them active.",
213
+ "# Check every net_charge before ligand parameterization.",
214
+ ]
215
+ for residue in residues:
216
+ ligand_id = f"{residue.id.resname.lower()}_{residue.id.resid}" # type: ignore[attr-defined]
217
+ residue_id = residue.id # type: ignore[attr-defined]
218
+ lines.extend(
219
+ [
220
+ f"# - id: {ligand_id}",
221
+ f"# selector: {{chain: {_yaml_string(residue_id.chain_id)}, resname: {residue_id.resname}, resid: {residue_id.resid}, icode: {_yaml_null_or_string(residue_id.icode)}}}",
222
+ "# net_charge: 0 # CHECK THIS VALUE",
223
+ "# multiplicity: 1",
224
+ "# atom_types: gaff2",
225
+ "# charge_method: am1bcc",
226
+ "# user_mol2: null",
227
+ "# user_frcmod: null",
228
+ ]
229
+ )
230
+ return lines
231
+
232
+
233
+ def _yaml_string(value: str) -> str:
234
+ if value == "":
235
+ return '""'
236
+ if any(char in value for char in [":", "#", "{", "}", "[", "]", ",", " "]):
237
+ return f'"{value}"'
238
+ return value
239
+
240
+
241
+ def _yaml_null_or_string(value: str | None) -> str:
242
+ return "null" if value is None else _yaml_string(value)
@@ -0,0 +1,21 @@
1
+ """Structure inspection workflow."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from mdprep.structure.inspect import InspectionSummary, inspect_pdb_structure
8
+ from mdprep.structure.pdb import AltlocPolicy
9
+
10
+
11
+ def inspect_structure(
12
+ input_structure: str | Path,
13
+ *,
14
+ altloc_policy: AltlocPolicy = "highest_occupancy",
15
+ disulfide_cutoff_angstrom: float = 2.2,
16
+ ) -> InspectionSummary:
17
+ return inspect_pdb_structure(
18
+ input_structure,
19
+ altloc_policy=altloc_policy,
20
+ disulfide_cutoff_angstrom=disulfide_cutoff_angstrom,
21
+ )