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.
- mdprep/__init__.py +7 -0
- mdprep/_selftest_data/basic_protein.yaml +33 -0
- mdprep/_selftest_data/ligand_sub.mol2 +11 -0
- mdprep/ambertools/__init__.py +2 -0
- mdprep/ambertools/antechamber.py +110 -0
- mdprep/ambertools/commands.py +33 -0
- mdprep/ambertools/mol2.py +415 -0
- mdprep/ambertools/parmchk2.py +89 -0
- mdprep/ambertools/resp.py +570 -0
- mdprep/charges/__init__.py +1 -0
- mdprep/charges/esp_grid.py +190 -0
- mdprep/cli.py +451 -0
- mdprep/config/__init__.py +9 -0
- mdprep/config/loader.py +146 -0
- mdprep/config/models.py +1100 -0
- mdprep/external/__init__.py +14 -0
- mdprep/external/discovery.py +14 -0
- mdprep/external/runner.py +55 -0
- mdprep/leap/__init__.py +2 -0
- mdprep/leap/builder.py +476 -0
- mdprep/leap/forcefields.py +93 -0
- mdprep/leap/ions.py +112 -0
- mdprep/leap/log_parser.py +170 -0
- mdprep/leap/report.py +99 -0
- mdprep/leap/residues.py +653 -0
- mdprep/leap/runner.py +65 -0
- mdprep/ligands/__init__.py +2 -0
- mdprep/ligands/extract.py +355 -0
- mdprep/ligands/identity.py +39 -0
- mdprep/ligands/pyscf_charges.py +242 -0
- mdprep/ligands/report.py +146 -0
- mdprep/ligands/workflow.py +838 -0
- mdprep/md/__init__.py +5 -0
- mdprep/md/roe_brooks.py +650 -0
- mdprep/metals/__init__.py +2 -0
- mdprep/metals/c4.py +157 -0
- mdprep/metals/coordination.py +313 -0
- mdprep/metals/gxtb_hessian.py +467 -0
- mdprep/metals/hydrogenate.py +344 -0
- mdprep/metals/mace_polar1_hessian.py +357 -0
- mdprep/metals/mace_polar1_worker.py +295 -0
- mdprep/metals/mcpb.py +949 -0
- mdprep/metals/mcpb_compat.py +125 -0
- mdprep/metals/nonbonded.py +294 -0
- mdprep/metals/parameter_comparison.py +563 -0
- mdprep/metals/pyscf_mcpb.py +1388 -0
- mdprep/metals/report.py +119 -0
- mdprep/metals/workflow.py +320 -0
- mdprep/protonation/__init__.py +15 -0
- mdprep/protonation/apply.py +968 -0
- mdprep/protonation/disulfide_states.py +141 -0
- mdprep/protonation/histidine_geometry.py +950 -0
- mdprep/protonation/histidine_xtb.py +390 -0
- mdprep/protonation/overrides.py +64 -0
- mdprep/protonation/pka_rules.py +165 -0
- mdprep/protonation/propka.py +174 -0
- mdprep/protonation/propka_parser.py +140 -0
- mdprep/protonation/report.py +187 -0
- mdprep/protonation/states.py +52 -0
- mdprep/protonation/temporary_hydrogenation.py +238 -0
- mdprep/protonation/xtb_parser.py +79 -0
- mdprep/protonation/xtb_runner.py +108 -0
- mdprep/qm/__init__.py +2 -0
- mdprep/qm/point_charges.py +233 -0
- mdprep/qm/pyscf_esp.py +70 -0
- mdprep/qm/pyscf_runner.py +570 -0
- mdprep/refinement/__init__.py +5 -0
- mdprep/refinement/ash.py +541 -0
- mdprep/refinement/coordinates.py +210 -0
- mdprep/refinement/mapping.py +163 -0
- mdprep/refinement/report.py +66 -0
- mdprep/refinement/selection.py +436 -0
- mdprep/refinement/workflow.py +188 -0
- mdprep/reports/__init__.py +8 -0
- mdprep/reports/structure_report.py +92 -0
- mdprep/structure/__init__.py +14 -0
- mdprep/structure/classify.py +92 -0
- mdprep/structure/disulfides.py +51 -0
- mdprep/structure/inspect.py +105 -0
- mdprep/structure/models.py +86 -0
- mdprep/structure/normalize.py +363 -0
- mdprep/structure/pdb.py +511 -0
- mdprep/structure/repair.py +566 -0
- mdprep/structure/selectors.py +204 -0
- mdprep/structure/writer.py +84 -0
- mdprep/validation/__init__.py +2 -0
- mdprep/validation/openmm_check.py +86 -0
- mdprep/validation/parmed_check.py +43 -0
- mdprep/validation/topology.py +239 -0
- mdprep/workflows/__init__.py +6 -0
- mdprep/workflows/init.py +242 -0
- mdprep/workflows/inspect.py +21 -0
- mdprep/workflows/prepare.py +763 -0
- mdprep/workflows/selftest.py +220 -0
- mdprep/workflows/validate.py +30 -0
- mdprep-0.2.0.dist-info/METADATA +264 -0
- mdprep-0.2.0.dist-info/RECORD +100 -0
- mdprep-0.2.0.dist-info/WHEEL +4 -0
- mdprep-0.2.0.dist-info/entry_points.txt +2 -0
- mdprep-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""Isolated MACE-POLAR-1 inference worker invoked through the external runner.
|
|
2
|
+
|
|
3
|
+
This module deliberately has no imports from mdprep. It is executed by the
|
|
4
|
+
Python interpreter configured for MACE-POLAR-1, which may be a dedicated
|
|
5
|
+
environment separate from mdprep's own Python environment.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import hashlib
|
|
12
|
+
import importlib.metadata
|
|
13
|
+
import json
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
import re
|
|
16
|
+
import sys
|
|
17
|
+
from time import perf_counter
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
EV_PER_HARTREE = 27.211386245988
|
|
22
|
+
ANGSTROM_PER_BOHR = 0.529177210903
|
|
23
|
+
MINIMUM_MACE_VERSION = (0, 3, 16)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _version(distribution: str) -> str | None:
|
|
27
|
+
try:
|
|
28
|
+
return importlib.metadata.version(distribution)
|
|
29
|
+
except importlib.metadata.PackageNotFoundError:
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _versions() -> dict[str, str | None]:
|
|
34
|
+
return {
|
|
35
|
+
"python": sys.version.splitlines()[0],
|
|
36
|
+
"mace_torch": _version("mace-torch"),
|
|
37
|
+
"torch": _version("torch"),
|
|
38
|
+
"ase": _version("ase"),
|
|
39
|
+
"graph_longrange": _version("graph-longrange"),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _require_supported_mace_version() -> str:
|
|
44
|
+
version = _version("mace-torch")
|
|
45
|
+
if version is None:
|
|
46
|
+
raise RuntimeError("mace-torch distribution metadata is unavailable")
|
|
47
|
+
match = re.match(r"^(\d+)\.(\d+)\.(\d+)", version)
|
|
48
|
+
parsed = tuple(int(value) for value in match.groups()) if match else None
|
|
49
|
+
if parsed is None or parsed < MINIMUM_MACE_VERSION:
|
|
50
|
+
raise RuntimeError(
|
|
51
|
+
"MACE-POLAR-1 Hessians require mace-torch 0.3.16 or newer; "
|
|
52
|
+
f"found {version}"
|
|
53
|
+
)
|
|
54
|
+
return version
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _sha256(path: Path) -> str:
|
|
58
|
+
digest = hashlib.sha256()
|
|
59
|
+
with path.open("rb") as handle:
|
|
60
|
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
61
|
+
digest.update(chunk)
|
|
62
|
+
return digest.hexdigest()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _cartesian_matrix(raw: object, atom_count: int) -> Any:
|
|
66
|
+
import numpy as np
|
|
67
|
+
|
|
68
|
+
values = np.asarray(raw, dtype=float)
|
|
69
|
+
dimension = atom_count * 3
|
|
70
|
+
if values.shape == (dimension, dimension):
|
|
71
|
+
matrix = values
|
|
72
|
+
elif values.shape == (dimension, atom_count, 3):
|
|
73
|
+
matrix = values.reshape((dimension, dimension))
|
|
74
|
+
elif values.shape == (atom_count, 3, atom_count, 3):
|
|
75
|
+
matrix = values.reshape((dimension, dimension))
|
|
76
|
+
elif values.shape == (atom_count, atom_count, 3, 3):
|
|
77
|
+
matrix = values.transpose(0, 2, 1, 3).reshape((dimension, dimension))
|
|
78
|
+
else:
|
|
79
|
+
raise ValueError(
|
|
80
|
+
f"MACE Hessian shape {values.shape} is incompatible with "
|
|
81
|
+
f"{atom_count} atoms"
|
|
82
|
+
)
|
|
83
|
+
if not np.all(np.isfinite(matrix)):
|
|
84
|
+
raise ValueError("MACE-POLAR-1 Hessian contains non-finite values")
|
|
85
|
+
return matrix
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _deterministic_direction(dimension: int) -> Any:
|
|
89
|
+
import numpy as np
|
|
90
|
+
|
|
91
|
+
indices = np.arange(1, dimension + 1, dtype=float)
|
|
92
|
+
direction = np.sin(indices) + 0.5 * np.cos(indices * 0.37)
|
|
93
|
+
direction /= np.linalg.norm(direction)
|
|
94
|
+
return direction
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _finite_difference_check(
|
|
98
|
+
atoms: Any,
|
|
99
|
+
hessian_ev_per_angstrom2: object,
|
|
100
|
+
*,
|
|
101
|
+
step_angstrom: float,
|
|
102
|
+
) -> dict[str, float]:
|
|
103
|
+
import numpy as np
|
|
104
|
+
|
|
105
|
+
atom_count = len(atoms)
|
|
106
|
+
direction = _deterministic_direction(atom_count * 3)
|
|
107
|
+
original = np.asarray(atoms.get_positions(), dtype=float)
|
|
108
|
+
displaced = direction.reshape((atom_count, 3)) * step_angstrom
|
|
109
|
+
try:
|
|
110
|
+
atoms.set_positions(original + displaced)
|
|
111
|
+
force_plus = np.asarray(atoms.get_forces(), dtype=float).reshape(-1)
|
|
112
|
+
atoms.set_positions(original - displaced)
|
|
113
|
+
force_minus = np.asarray(atoms.get_forces(), dtype=float).reshape(-1)
|
|
114
|
+
finally:
|
|
115
|
+
atoms.set_positions(original)
|
|
116
|
+
finite_difference = -(force_plus - force_minus) / (2.0 * step_angstrom)
|
|
117
|
+
analytic = np.asarray(hessian_ev_per_angstrom2, dtype=float) @ direction
|
|
118
|
+
difference = analytic - finite_difference
|
|
119
|
+
denominator = max(
|
|
120
|
+
float(np.linalg.norm(analytic)),
|
|
121
|
+
float(np.linalg.norm(finite_difference)),
|
|
122
|
+
1.0e-12,
|
|
123
|
+
)
|
|
124
|
+
return {
|
|
125
|
+
"step_angstrom": step_angstrom,
|
|
126
|
+
"analytic_hessian_vector_norm_ev_per_angstrom2": float(
|
|
127
|
+
np.linalg.norm(analytic)
|
|
128
|
+
),
|
|
129
|
+
"finite_difference_hessian_vector_norm_ev_per_angstrom2": float(
|
|
130
|
+
np.linalg.norm(finite_difference)
|
|
131
|
+
),
|
|
132
|
+
"difference_norm_ev_per_angstrom2": float(np.linalg.norm(difference)),
|
|
133
|
+
"relative_error": float(np.linalg.norm(difference) / denominator),
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _run(args: argparse.Namespace) -> None:
|
|
138
|
+
import numpy as np
|
|
139
|
+
from ase.io import read
|
|
140
|
+
from mace.calculators import mace_polar
|
|
141
|
+
from mace.calculators.foundations_models import download_mace_polar_checkpoint
|
|
142
|
+
|
|
143
|
+
_require_supported_mace_version()
|
|
144
|
+
|
|
145
|
+
input_path = Path(args.xyz).resolve()
|
|
146
|
+
output_hessian = Path(args.output_hessian).resolve()
|
|
147
|
+
output_report = Path(args.output_report).resolve()
|
|
148
|
+
checkpoint = Path(download_mace_polar_checkpoint(args.model)).resolve()
|
|
149
|
+
model_sha256 = _sha256(checkpoint)
|
|
150
|
+
if args.expected_model_sha256 is not None and (
|
|
151
|
+
model_sha256.lower() != args.expected_model_sha256.lower()
|
|
152
|
+
):
|
|
153
|
+
raise ValueError(
|
|
154
|
+
"MACE-POLAR-1 checkpoint checksum mismatch: expected "
|
|
155
|
+
f"{args.expected_model_sha256}, found {model_sha256}"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
atoms = read(input_path, format="xyz")
|
|
159
|
+
if len(atoms) != args.atom_count:
|
|
160
|
+
raise ValueError(
|
|
161
|
+
f"XYZ contains {len(atoms)} atoms; expected {args.atom_count}"
|
|
162
|
+
)
|
|
163
|
+
atoms.info["charge"] = args.charge
|
|
164
|
+
# MACE-POLAR-1 defines spin as the spin multiplicity (unpaired electrons + 1).
|
|
165
|
+
atoms.info["spin"] = args.multiplicity
|
|
166
|
+
atoms.info["external_field"] = [0.0, 0.0, 0.0]
|
|
167
|
+
|
|
168
|
+
started = perf_counter()
|
|
169
|
+
calculator = mace_polar(
|
|
170
|
+
model=str(checkpoint),
|
|
171
|
+
device=args.device,
|
|
172
|
+
default_dtype="float64",
|
|
173
|
+
)
|
|
174
|
+
supported_atomic_numbers = sorted(
|
|
175
|
+
int(value) for value in calculator.z_table.zs
|
|
176
|
+
)
|
|
177
|
+
unsupported = sorted(
|
|
178
|
+
set(int(value) for value in atoms.numbers) - set(supported_atomic_numbers)
|
|
179
|
+
)
|
|
180
|
+
if unsupported:
|
|
181
|
+
raise ValueError(
|
|
182
|
+
f"MACE-POLAR-1 checkpoint does not support atomic numbers {unsupported}"
|
|
183
|
+
)
|
|
184
|
+
atoms.calc = calculator
|
|
185
|
+
energy_ev = float(atoms.get_potential_energy())
|
|
186
|
+
forces_ev_per_angstrom = np.asarray(atoms.get_forces(), dtype=float)
|
|
187
|
+
raw_hessian = calculator.get_hessian(atoms=atoms)
|
|
188
|
+
hessian_ev_per_angstrom2 = _cartesian_matrix(raw_hessian, len(atoms))
|
|
189
|
+
finite_difference = _finite_difference_check(
|
|
190
|
+
atoms,
|
|
191
|
+
hessian_ev_per_angstrom2,
|
|
192
|
+
step_angstrom=args.finite_difference_step_angstrom,
|
|
193
|
+
)
|
|
194
|
+
if finite_difference["relative_error"] > args.max_hessian_vector_relative_error:
|
|
195
|
+
raise ValueError(
|
|
196
|
+
"MACE-POLAR-1 analytic Hessian failed its deterministic central-force "
|
|
197
|
+
"difference check: relative error "
|
|
198
|
+
f"{finite_difference['relative_error']:.6g} exceeds "
|
|
199
|
+
f"{args.max_hessian_vector_relative_error:.6g}"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
hessian_hartree_per_bohr2 = (
|
|
203
|
+
hessian_ev_per_angstrom2 * ANGSTROM_PER_BOHR**2 / EV_PER_HARTREE
|
|
204
|
+
)
|
|
205
|
+
antisymmetry = float(
|
|
206
|
+
np.max(np.abs(hessian_hartree_per_bohr2 - hessian_hartree_per_bohr2.T))
|
|
207
|
+
)
|
|
208
|
+
np.save(output_hessian, hessian_hartree_per_bohr2)
|
|
209
|
+
report = {
|
|
210
|
+
"status": "complete",
|
|
211
|
+
"backend": "MACE-POLAR-1",
|
|
212
|
+
"model": args.model,
|
|
213
|
+
"model_path": str(checkpoint),
|
|
214
|
+
"model_sha256": model_sha256,
|
|
215
|
+
"versions": _versions(),
|
|
216
|
+
"device": args.device,
|
|
217
|
+
"default_dtype": "float64",
|
|
218
|
+
"atom_count": len(atoms),
|
|
219
|
+
"atomic_numbers": [int(value) for value in atoms.numbers],
|
|
220
|
+
"supported_atomic_numbers": supported_atomic_numbers,
|
|
221
|
+
"molecular_charge": args.charge,
|
|
222
|
+
"multiplicity": args.multiplicity,
|
|
223
|
+
"spin_input_semantics": "spin multiplicity (unpaired electrons + 1)",
|
|
224
|
+
"external_field": [0.0, 0.0, 0.0],
|
|
225
|
+
"energy_ev": energy_ev,
|
|
226
|
+
"energy_hartree": energy_ev / EV_PER_HARTREE,
|
|
227
|
+
"force_norm_ev_per_angstrom": float(np.linalg.norm(forces_ev_per_angstrom)),
|
|
228
|
+
"gradient_norm_hartree_per_bohr": float(
|
|
229
|
+
np.linalg.norm(forces_ev_per_angstrom)
|
|
230
|
+
* ANGSTROM_PER_BOHR
|
|
231
|
+
/ EV_PER_HARTREE
|
|
232
|
+
),
|
|
233
|
+
"raw_hessian_shape": list(np.asarray(raw_hessian).shape),
|
|
234
|
+
"hessian_shape": list(hessian_hartree_per_bohr2.shape),
|
|
235
|
+
"source_hessian_units": "eV/angstrom^2",
|
|
236
|
+
"output_hessian_units": "hartree/bohr^2",
|
|
237
|
+
"hessian_antisymmetry_max_hartree_per_bohr2": antisymmetry,
|
|
238
|
+
"hessian_sha256": _sha256(output_hessian),
|
|
239
|
+
"finite_difference_validation": finite_difference,
|
|
240
|
+
"runtime_seconds": perf_counter() - started,
|
|
241
|
+
"geometry_optimization_performed": False,
|
|
242
|
+
}
|
|
243
|
+
output_report.write_text(
|
|
244
|
+
json.dumps(report, indent=2, sort_keys=True) + "\n",
|
|
245
|
+
encoding="utf-8",
|
|
246
|
+
)
|
|
247
|
+
print(json.dumps(report, sort_keys=True))
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def main() -> None:
|
|
251
|
+
parser = argparse.ArgumentParser()
|
|
252
|
+
parser.add_argument("--probe", action="store_true")
|
|
253
|
+
parser.add_argument("--xyz")
|
|
254
|
+
parser.add_argument("--atom-count", type=int)
|
|
255
|
+
parser.add_argument("--charge", type=int)
|
|
256
|
+
parser.add_argument("--multiplicity", type=int)
|
|
257
|
+
parser.add_argument("--model")
|
|
258
|
+
parser.add_argument("--device")
|
|
259
|
+
parser.add_argument("--expected-model-sha256")
|
|
260
|
+
parser.add_argument("--finite-difference-step-angstrom", type=float)
|
|
261
|
+
parser.add_argument("--max-hessian-vector-relative-error", type=float)
|
|
262
|
+
parser.add_argument("--output-hessian")
|
|
263
|
+
parser.add_argument("--output-report")
|
|
264
|
+
args = parser.parse_args()
|
|
265
|
+
if args.probe:
|
|
266
|
+
from mace.calculators import mace_polar # noqa: F401
|
|
267
|
+
|
|
268
|
+
_require_supported_mace_version()
|
|
269
|
+
print(
|
|
270
|
+
json.dumps(
|
|
271
|
+
{"status": "available", "versions": _versions()},
|
|
272
|
+
sort_keys=True,
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
return
|
|
276
|
+
required = (
|
|
277
|
+
"xyz",
|
|
278
|
+
"atom_count",
|
|
279
|
+
"charge",
|
|
280
|
+
"multiplicity",
|
|
281
|
+
"model",
|
|
282
|
+
"device",
|
|
283
|
+
"finite_difference_step_angstrom",
|
|
284
|
+
"max_hessian_vector_relative_error",
|
|
285
|
+
"output_hessian",
|
|
286
|
+
"output_report",
|
|
287
|
+
)
|
|
288
|
+
missing = [name for name in required if getattr(args, name) is None]
|
|
289
|
+
if missing:
|
|
290
|
+
parser.error(f"missing required arguments: {', '.join(missing)}")
|
|
291
|
+
_run(args)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
if __name__ == "__main__":
|
|
295
|
+
main()
|