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 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
@@ -0,0 +1,3 @@
1
+ """Version of the pmeff distribution (kept in sync with the plugin)."""
2
+
3
+ __version__ = "1.0.0"