pmeff 1.0.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.
- pmeff/__init__.py +150 -0
- pmeff/_version.py +3 -0
- pmeff/forcefield.py +2011 -0
- pmeff/py.typed +0 -0
- pmeff-1.0.0.dist-info/METADATA +859 -0
- pmeff-1.0.0.dist-info/RECORD +9 -0
- pmeff-1.0.0.dist-info/WHEEL +5 -0
- pmeff-1.0.0.dist-info/licenses/LICENSE +674 -0
- pmeff-1.0.0.dist-info/top_level.txt +1 -0
pmeff/__init__.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""PMEFF — a self-contained universal force field (Z = 1..118).
|
|
2
|
+
|
|
3
|
+
``pmeff`` is the standalone, pip-installable distribution of the PMEFF engine
|
|
4
|
+
that ships inside the MoleditPy *PMEFF Plugin*. It is a small, NumPy-only
|
|
5
|
+
molecular force field whose every parameter is derived from a single
|
|
6
|
+
per-element property (the Pyykko covalent radius), so no element is ever
|
|
7
|
+
missing. It provides bonded (bond/angle/torsion/out-of-plane), van der Waals,
|
|
8
|
+
electrostatic (QEq), hydrogen-bond and dispersion terms, all with analytical
|
|
9
|
+
gradients, plus a dependency-free FIRE + L-BFGS optimizer.
|
|
10
|
+
|
|
11
|
+
Two ways to use it:
|
|
12
|
+
|
|
13
|
+
* **With RDKit** (``pip install "pmeff[rdkit]"``) — the convenient path. Hand it
|
|
14
|
+
an RDKit ``Mol`` that has a 3D conformer; get the same ``Mol`` back with the
|
|
15
|
+
conformer relaxed (bonds, charges and properties preserved):
|
|
16
|
+
|
|
17
|
+
from pmeff import optimize_mol
|
|
18
|
+
mol, result = optimize_mol(mol) # relaxed in place, and returned
|
|
19
|
+
|
|
20
|
+
* **Without RDKit** (``pip install pmeff``) — the pure-NumPy path. Pass atomic
|
|
21
|
+
numbers, a bond list and coordinates; get optimized coordinates back:
|
|
22
|
+
|
|
23
|
+
from pmeff import optimize_coords
|
|
24
|
+
coords, result = optimize_coords(atomic_numbers, bonds, coords)
|
|
25
|
+
|
|
26
|
+
The lower-level engine (``Topology``, ``build_topology``,
|
|
27
|
+
``energy_and_gradient``, ``optimize``, ``vibrational_analysis``, ...) is
|
|
28
|
+
re-exported for advanced use.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from typing import Any, List, Optional, Sequence, Tuple
|
|
34
|
+
|
|
35
|
+
import numpy as np
|
|
36
|
+
|
|
37
|
+
from ._version import __version__
|
|
38
|
+
from .forcefield import (
|
|
39
|
+
OptimizeResult,
|
|
40
|
+
Topology,
|
|
41
|
+
bond_rest_length,
|
|
42
|
+
build_topology,
|
|
43
|
+
energy_and_gradient,
|
|
44
|
+
energy_components,
|
|
45
|
+
optimize,
|
|
46
|
+
qeq_charges,
|
|
47
|
+
vibrational_analysis,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
"__version__",
|
|
52
|
+
"optimize_mol",
|
|
53
|
+
"optimize_coords",
|
|
54
|
+
"OptimizeResult",
|
|
55
|
+
"Topology",
|
|
56
|
+
"build_topology",
|
|
57
|
+
"energy_and_gradient",
|
|
58
|
+
"energy_components",
|
|
59
|
+
"optimize",
|
|
60
|
+
"qeq_charges",
|
|
61
|
+
"vibrational_analysis",
|
|
62
|
+
"bond_rest_length",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def optimize_mol(
|
|
67
|
+
mol: Any,
|
|
68
|
+
max_iter: int = 1000,
|
|
69
|
+
f_tol: float = 1e-3,
|
|
70
|
+
electronic_effects: bool = True,
|
|
71
|
+
use_morse: bool = True,
|
|
72
|
+
use_hbond: bool = True,
|
|
73
|
+
use_dispersion: bool = False,
|
|
74
|
+
use_polar_contraction: bool = True,
|
|
75
|
+
) -> Tuple[Any, Optional[OptimizeResult]]:
|
|
76
|
+
"""Relax an RDKit ``Mol``'s 3D conformer in place with PMEFF.
|
|
77
|
+
|
|
78
|
+
Returns ``(mol, result)`` — the *same* molecule object (its conformer
|
|
79
|
+
updated), so connectivity, bond orders, formal charges and properties are
|
|
80
|
+
all preserved, plus an :class:`OptimizeResult` (``None`` for a single-atom
|
|
81
|
+
molecule with nothing to do). Requires RDKit and a molecule that already
|
|
82
|
+
carries a conformer; raises ``ImportError`` if RDKit is not installed and
|
|
83
|
+
``ValueError`` if the molecule has no 3D coordinates.
|
|
84
|
+
|
|
85
|
+
The defaults mirror the shipped plugin (electronic effects, Morse bonds,
|
|
86
|
+
H-bonds and the polar-bond contraction on; dispersion off).
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
from .forcefield import optimize_rdkit_mol
|
|
90
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
91
|
+
raise ImportError("pmeff.optimize_mol requires RDKit") from exc
|
|
92
|
+
|
|
93
|
+
success, result = optimize_rdkit_mol(
|
|
94
|
+
mol,
|
|
95
|
+
max_iter=max_iter,
|
|
96
|
+
f_tol=f_tol,
|
|
97
|
+
electronic_effects=electronic_effects,
|
|
98
|
+
use_morse=use_morse,
|
|
99
|
+
use_hbond=use_hbond,
|
|
100
|
+
use_dispersion=use_dispersion,
|
|
101
|
+
use_polar_contraction=use_polar_contraction,
|
|
102
|
+
)
|
|
103
|
+
if not success:
|
|
104
|
+
raise ValueError("PMEFF: molecule has no 3D conformer to optimize")
|
|
105
|
+
return mol, result
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def optimize_coords(
|
|
109
|
+
atomic_numbers: Sequence[int],
|
|
110
|
+
bonds: Sequence[Tuple[int, int]],
|
|
111
|
+
coords: "np.ndarray",
|
|
112
|
+
hybridizations: Optional[Sequence[Optional[str]]] = None,
|
|
113
|
+
bond_orders: Optional[Sequence[float]] = None,
|
|
114
|
+
charges: Optional[Sequence[float]] = None,
|
|
115
|
+
max_iter: int = 1000,
|
|
116
|
+
f_tol: float = 1e-3,
|
|
117
|
+
use_morse: bool = True,
|
|
118
|
+
use_hbond: bool = False,
|
|
119
|
+
use_dispersion: bool = False,
|
|
120
|
+
use_polar_contraction: bool = True,
|
|
121
|
+
) -> Tuple["np.ndarray", OptimizeResult]:
|
|
122
|
+
"""Relax a molecule described by plain arrays — no RDKit required.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
atomic_numbers: Atomic number of every atom (length N).
|
|
126
|
+
bonds: ``(i, j)`` index pairs describing the covalent bonds.
|
|
127
|
+
coords: Initial coordinates, shape ``(N, 3)`` (not modified in place).
|
|
128
|
+
hybridizations: Optional per-atom labels ("SP", "SP2", "SP3", ...) that
|
|
129
|
+
improve angle/torsion assignment; omit for a coordination-based
|
|
130
|
+
fallback.
|
|
131
|
+
bond_orders: Optional per-bond orders aligned with *bonds*.
|
|
132
|
+
charges: Optional per-atom partial charges enabling the Coulomb term;
|
|
133
|
+
omit for none, or use :func:`qeq_charges` to derive them.
|
|
134
|
+
|
|
135
|
+
Returns ``(optimized_coords, result)``.
|
|
136
|
+
"""
|
|
137
|
+
coords = np.asarray(coords, dtype=float)
|
|
138
|
+
topo = build_topology(
|
|
139
|
+
atomic_numbers,
|
|
140
|
+
bonds,
|
|
141
|
+
hybridizations=hybridizations,
|
|
142
|
+
bond_orders=bond_orders,
|
|
143
|
+
charges=charges,
|
|
144
|
+
coords=coords,
|
|
145
|
+
use_morse=use_morse,
|
|
146
|
+
use_hbond=use_hbond,
|
|
147
|
+
use_dispersion=use_dispersion,
|
|
148
|
+
use_polar_contraction=use_polar_contraction,
|
|
149
|
+
)
|
|
150
|
+
return optimize(coords, topo, max_iter=max_iter, f_tol=f_tol)
|
pmeff/_version.py
ADDED