physikmdb 1.0.0__py3-none-win_amd64.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.
- physikmdb-1.0.0.data/purelib/physikmdb/__init__.py +66 -0
- physikmdb-1.0.0.data/purelib/physikmdb/_library.py +257 -0
- physikmdb-1.0.0.data/purelib/physikmdb/_vendor/kernels/basis.h +199 -0
- physikmdb-1.0.0.data/purelib/physikmdb/_vendor/kernels/harmonics.h +103 -0
- physikmdb-1.0.0.data/purelib/physikmdb/_vendor/kernels/kernels.h +334 -0
- physikmdb-1.0.0.data/purelib/physikmdb/_vendor/kernels/map_image.c +346 -0
- physikmdb-1.0.0.data/purelib/physikmdb/_vendor/kernels/momentum_map.c +477 -0
- physikmdb-1.0.0.data/purelib/physikmdb/_vendor/kernels/momentumspace.c +354 -0
- physikmdb-1.0.0.data/purelib/physikmdb/_vendor/kernels/realspace.c +318 -0
- physikmdb-1.0.0.data/purelib/physikmdb/_vendor/prebuilt/libkernels.dll +0 -0
- physikmdb-1.0.0.data/purelib/physikmdb/binary.py +286 -0
- physikmdb-1.0.0.data/purelib/physikmdb/database.py +976 -0
- physikmdb-1.0.0.data/purelib/physikmdb/kernels.py +380 -0
- physikmdb-1.0.0.data/purelib/physikmdb/plot.py +188 -0
- physikmdb-1.0.0.data/purelib/physikmdb/units.py +86 -0
- physikmdb-1.0.0.dist-info/METADATA +229 -0
- physikmdb-1.0.0.dist-info/RECORD +20 -0
- physikmdb-1.0.0.dist-info/WHEEL +5 -0
- physikmdb-1.0.0.dist-info/licenses/LICENSE +190 -0
- physikmdb-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Photoemission momentum maps and molecular orbitals from PhysikMDB.
|
|
2
|
+
|
|
3
|
+
The same C physics kernels the PhysikMDB website runs, plus a client for the
|
|
4
|
+
database behind it.
|
|
5
|
+
|
|
6
|
+
import physikmdb
|
|
7
|
+
|
|
8
|
+
db = physikmdb.Database()
|
|
9
|
+
calc = db.calculation(21)
|
|
10
|
+
image = calc.momentum_map("HOMO", photon_energy=21.2, k_max=3.0)
|
|
11
|
+
|
|
12
|
+
`Calculation` downloads the basis and the coefficient rows it needs on first
|
|
13
|
+
use and keeps them, so a loop over orbitals costs one small request each:
|
|
14
|
+
|
|
15
|
+
for name in ("HOMO", "HOMO-1", "HOMO-2"):
|
|
16
|
+
image = calc.momentum_map(name, photon_energy=21.2, k_max=3.0)
|
|
17
|
+
|
|
18
|
+
Work offline by saving a calculation once and reopening it:
|
|
19
|
+
|
|
20
|
+
calc.save("naphthalene/")
|
|
21
|
+
calc = physikmdb.load("naphthalene/")
|
|
22
|
+
|
|
23
|
+
Units are eV and Angstrom, matching the website's own controls. Everything
|
|
24
|
+
below is Hartree and Bohr, as the kernels take them; `physikmdb.units` is the
|
|
25
|
+
only place that converts. To skip the conversion entirely, call
|
|
26
|
+
`physikmdb.kernels` with your own basis and coefficients.
|
|
27
|
+
|
|
28
|
+
No compiler needed: a prebuilt kernel library ships for Linux, macOS and
|
|
29
|
+
Windows, and is built from source only on a platform without one.
|
|
30
|
+
|
|
31
|
+
Submodules:
|
|
32
|
+
|
|
33
|
+
- `physikmdb.kernels` the kernels as numpy functions, in atomic units
|
|
34
|
+
- `physikmdb.binary` read/write basis.bin and coefficients.bin
|
|
35
|
+
- `physikmdb.units` eV/Angstrom <-> Hartree/Bohr
|
|
36
|
+
- `physikmdb.plot` quick matplotlib views (needs `physikmdb[plot]`)
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from physikmdb import binary, kernels, units
|
|
40
|
+
from physikmdb.binary import Basis
|
|
41
|
+
from physikmdb.database import (
|
|
42
|
+
Calculation,
|
|
43
|
+
Database,
|
|
44
|
+
Orbital,
|
|
45
|
+
PhysikMDBError,
|
|
46
|
+
System,
|
|
47
|
+
load,
|
|
48
|
+
)
|
|
49
|
+
from physikmdb.units import ANGSTROM_TO_BOHR, HARTREE_TO_EV
|
|
50
|
+
|
|
51
|
+
__version__ = "1.0.0"
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"ANGSTROM_TO_BOHR",
|
|
55
|
+
"HARTREE_TO_EV",
|
|
56
|
+
"Basis",
|
|
57
|
+
"Calculation",
|
|
58
|
+
"Database",
|
|
59
|
+
"Orbital",
|
|
60
|
+
"PhysikMDBError",
|
|
61
|
+
"System",
|
|
62
|
+
"binary",
|
|
63
|
+
"kernels",
|
|
64
|
+
"load",
|
|
65
|
+
"units",
|
|
66
|
+
]
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Loading the compiled physics kernels. Internal - use `physikmdb.kernels`.
|
|
2
|
+
|
|
3
|
+
`load()` returns the shared library, trying in order:
|
|
4
|
+
|
|
5
|
+
1. The prebuilt binary bundled in this platform's wheel
|
|
6
|
+
(`_vendor/prebuilt/libkernels.*`). Nearly every install takes this path and
|
|
7
|
+
never needs a compiler.
|
|
8
|
+
2. Compiling the C sources in `_vendor/kernels/` with whatever C compiler is
|
|
9
|
+
on PATH - only when no prebuilt binary shipped for this platform, or the
|
|
10
|
+
bundled one will not load.
|
|
11
|
+
|
|
12
|
+
The result is cached, so a script calling several kernels pays the cost once.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import ctypes
|
|
16
|
+
import platform
|
|
17
|
+
import shutil
|
|
18
|
+
import subprocess
|
|
19
|
+
import tempfile
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
_VENDOR_DIR = Path(__file__).resolve().parent / "_vendor"
|
|
23
|
+
KERNEL_SOURCE_DIR = _VENDOR_DIR / "kernels"
|
|
24
|
+
|
|
25
|
+
# map_image.c is separate from momentum_map.c but part of the same library
|
|
26
|
+
KERNEL_SOURCES = ("momentum_map.c", "map_image.c", "realspace.c", "momentumspace.c")
|
|
27
|
+
|
|
28
|
+
_PREBUILT_EXTENSION = {"Linux": "so", "Darwin": "dylib", "Windows": "dll"}.get(
|
|
29
|
+
platform.system(), "so"
|
|
30
|
+
)
|
|
31
|
+
PREBUILT_PATH = _VENDOR_DIR / "prebuilt" / f"libkernels.{_PREBUILT_EXTENSION}"
|
|
32
|
+
|
|
33
|
+
FLOAT_PTR = ctypes.POINTER(ctypes.c_float)
|
|
34
|
+
INT_PTR = ctypes.POINTER(ctypes.c_int)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class KernelBuildError(RuntimeError):
|
|
38
|
+
"""Neither a bundled prebuilt library nor a from-source build was usable."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _declare(library: ctypes.CDLL) -> ctypes.CDLL:
|
|
42
|
+
"""Declare every entry point's argument types on a freshly loaded library.
|
|
43
|
+
|
|
44
|
+
ctypes does not check arity against the C signature, so a wrong list here
|
|
45
|
+
shifts every later argument and produces plausible nonsense rather than an
|
|
46
|
+
error. Keep these next to kernels.h.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
library: the loaded shared library.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The same library, with restype/argtypes set.
|
|
53
|
+
"""
|
|
54
|
+
# The shell-major basis every kernel takes, in `physikmdb.binary`'s layout:
|
|
55
|
+
# Natoms, shells per atom, positions, then per shell l and primitive count,
|
|
56
|
+
# then the primitives themselves
|
|
57
|
+
basis_args = [
|
|
58
|
+
ctypes.c_int, # Natoms
|
|
59
|
+
INT_PTR, # Nshells per atom
|
|
60
|
+
FLOAT_PTR,
|
|
61
|
+
FLOAT_PTR,
|
|
62
|
+
FLOAT_PTR, # Rx, Ry, Rz
|
|
63
|
+
INT_PTR,
|
|
64
|
+
INT_PTR, # shell_l, shell_Nprim
|
|
65
|
+
FLOAT_PTR,
|
|
66
|
+
FLOAT_PTR, # exponents, contractions
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
signatures = {
|
|
70
|
+
"normalisation": [ctypes.c_int, FLOAT_PTR],
|
|
71
|
+
"symmetrisation": [ctypes.c_int, ctypes.c_int, FLOAT_PTR],
|
|
72
|
+
"polarisation_factor": [
|
|
73
|
+
ctypes.c_float, # E_kin
|
|
74
|
+
ctypes.c_float, # k0
|
|
75
|
+
ctypes.c_float, # dk
|
|
76
|
+
ctypes.c_int, # Nk
|
|
77
|
+
ctypes.c_float, # polar
|
|
78
|
+
ctypes.c_float, # azimuth
|
|
79
|
+
ctypes.c_int, # polarisation
|
|
80
|
+
ctypes.c_float, # s_share
|
|
81
|
+
ctypes.c_int, # handedness
|
|
82
|
+
ctypes.c_float, # gamma
|
|
83
|
+
FLOAT_PTR,
|
|
84
|
+
],
|
|
85
|
+
"momentum_map": [
|
|
86
|
+
ctypes.c_float,
|
|
87
|
+
ctypes.c_float,
|
|
88
|
+
ctypes.c_float,
|
|
89
|
+
ctypes.c_int, # E_kin, k0, dk, Nk
|
|
90
|
+
ctypes.c_float,
|
|
91
|
+
ctypes.c_float,
|
|
92
|
+
ctypes.c_float, # phi, theta, psi
|
|
93
|
+
*basis_args,
|
|
94
|
+
FLOAT_PTR,
|
|
95
|
+
FLOAT_PTR, # coefficients, out
|
|
96
|
+
],
|
|
97
|
+
"momentum_map_sum": [
|
|
98
|
+
ctypes.c_int,
|
|
99
|
+
FLOAT_PTR,
|
|
100
|
+
FLOAT_PTR,
|
|
101
|
+
FLOAT_PTR, # Norbitals, E_kin, weights, rows
|
|
102
|
+
ctypes.c_float,
|
|
103
|
+
ctypes.c_float,
|
|
104
|
+
ctypes.c_int, # k0, dk, Nk
|
|
105
|
+
ctypes.c_float,
|
|
106
|
+
ctypes.c_float,
|
|
107
|
+
ctypes.c_float, # phi, theta, psi
|
|
108
|
+
ctypes.c_int,
|
|
109
|
+
ctypes.c_int, # substrate, apply_polarisation
|
|
110
|
+
ctypes.c_float,
|
|
111
|
+
ctypes.c_float, # polar, azimuth
|
|
112
|
+
ctypes.c_int,
|
|
113
|
+
ctypes.c_float,
|
|
114
|
+
ctypes.c_int, # polarisation, s_share, handedness
|
|
115
|
+
FLOAT_PTR, # gamma (per orbital)
|
|
116
|
+
*basis_args,
|
|
117
|
+
FLOAT_PTR, # out
|
|
118
|
+
],
|
|
119
|
+
"scalar_field": [
|
|
120
|
+
ctypes.c_int, # N
|
|
121
|
+
ctypes.c_float,
|
|
122
|
+
ctypes.c_float,
|
|
123
|
+
ctypes.c_float, # x0, y0, z0
|
|
124
|
+
ctypes.c_float, # spacing
|
|
125
|
+
*basis_args,
|
|
126
|
+
FLOAT_PTR,
|
|
127
|
+
FLOAT_PTR, # coefficients, field
|
|
128
|
+
],
|
|
129
|
+
"charge_density_sum": [
|
|
130
|
+
ctypes.c_int, # N
|
|
131
|
+
ctypes.c_float,
|
|
132
|
+
ctypes.c_float,
|
|
133
|
+
ctypes.c_float, # x0, y0, z0
|
|
134
|
+
ctypes.c_float, # spacing
|
|
135
|
+
ctypes.c_int,
|
|
136
|
+
FLOAT_PTR,
|
|
137
|
+
FLOAT_PTR, # Norbitals, weights, rows
|
|
138
|
+
*basis_args,
|
|
139
|
+
FLOAT_PTR, # out
|
|
140
|
+
],
|
|
141
|
+
"momentum_density": [
|
|
142
|
+
ctypes.c_int,
|
|
143
|
+
ctypes.c_float,
|
|
144
|
+
ctypes.c_float, # N, k0, dk
|
|
145
|
+
*basis_args,
|
|
146
|
+
FLOAT_PTR,
|
|
147
|
+
FLOAT_PTR, # coefficients, field
|
|
148
|
+
],
|
|
149
|
+
"momentum_density_sum": [
|
|
150
|
+
ctypes.c_int,
|
|
151
|
+
ctypes.c_float,
|
|
152
|
+
ctypes.c_float, # N, k0, dk
|
|
153
|
+
ctypes.c_int,
|
|
154
|
+
FLOAT_PTR,
|
|
155
|
+
FLOAT_PTR, # Norbitals, weights, rows
|
|
156
|
+
*basis_args,
|
|
157
|
+
FLOAT_PTR, # out
|
|
158
|
+
],
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
for name, argtypes in signatures.items():
|
|
162
|
+
entry = getattr(library, name)
|
|
163
|
+
entry.restype = None
|
|
164
|
+
entry.argtypes = argtypes
|
|
165
|
+
|
|
166
|
+
return library
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def build(
|
|
170
|
+
source_dir: Path | None = None,
|
|
171
|
+
library_path: Path | None = None,
|
|
172
|
+
extra_flags=(),
|
|
173
|
+
) -> ctypes.CDLL:
|
|
174
|
+
"""Compile the kernels into a shared library and load it.
|
|
175
|
+
|
|
176
|
+
This is the fallback path - see the module docstring. Most installs never
|
|
177
|
+
call this; it only runs when no prebuilt binary shipped for the current
|
|
178
|
+
platform, or the bundled one failed to load.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
source_dir: directory holding the .c and .h files; defaults to the
|
|
182
|
+
vendored copy in this package.
|
|
183
|
+
library_path: where to write the library; defaults to a temporary
|
|
184
|
+
file that lives as long as the process.
|
|
185
|
+
extra_flags: extra compiler flags, e.g. `["-fopenmp"]`.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
The loaded library, with every entry point declared.
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
KernelBuildError: if no C compiler is on PATH, or the build fails.
|
|
192
|
+
"""
|
|
193
|
+
source_dir = Path(source_dir) if source_dir is not None else KERNEL_SOURCE_DIR
|
|
194
|
+
if library_path is None:
|
|
195
|
+
library_path = Path(tempfile.mkdtemp(prefix="physikmdb-kernels-")) / "libkernels.so"
|
|
196
|
+
|
|
197
|
+
compiler = shutil.which("cc") or shutil.which("clang") or shutil.which("gcc")
|
|
198
|
+
if compiler is None:
|
|
199
|
+
raise KernelBuildError(
|
|
200
|
+
"physikmdb has no prebuilt kernel library for this platform, and no C "
|
|
201
|
+
"compiler (cc, clang, gcc) is on PATH to build one from source. Install a C "
|
|
202
|
+
"compiler (e.g. Xcode Command Line Tools on macOS, build-essential on Linux, "
|
|
203
|
+
"or MSVC/MinGW on Windows) and try again."
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
result = subprocess.run(
|
|
207
|
+
[
|
|
208
|
+
compiler,
|
|
209
|
+
"-shared",
|
|
210
|
+
"-fPIC",
|
|
211
|
+
"-O2",
|
|
212
|
+
"-std=c99",
|
|
213
|
+
*extra_flags,
|
|
214
|
+
*[str(source_dir / name) for name in KERNEL_SOURCES],
|
|
215
|
+
"-lm",
|
|
216
|
+
"-o",
|
|
217
|
+
str(library_path),
|
|
218
|
+
],
|
|
219
|
+
capture_output=True,
|
|
220
|
+
text=True,
|
|
221
|
+
)
|
|
222
|
+
if result.returncode != 0:
|
|
223
|
+
flags = " ".join(extra_flags)
|
|
224
|
+
raise KernelBuildError(
|
|
225
|
+
f"Failed to build the kernels{f' with {flags}' if flags else ''}:\n{result.stderr}"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return _declare(ctypes.CDLL(str(library_path)))
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
_cached: ctypes.CDLL | None = None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def load() -> ctypes.CDLL:
|
|
235
|
+
"""The kernels, built once per process: prebuilt if this wheel bundled
|
|
236
|
+
one for the current platform, compiled from the vendored source otherwise.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
The loaded library.
|
|
240
|
+
|
|
241
|
+
Raises:
|
|
242
|
+
KernelBuildError: if there is no usable prebuilt binary and the
|
|
243
|
+
fallback build also fails.
|
|
244
|
+
"""
|
|
245
|
+
global _cached
|
|
246
|
+
if _cached is not None:
|
|
247
|
+
return _cached
|
|
248
|
+
|
|
249
|
+
if PREBUILT_PATH.exists():
|
|
250
|
+
try:
|
|
251
|
+
_cached = _declare(ctypes.CDLL(str(PREBUILT_PATH)))
|
|
252
|
+
return _cached
|
|
253
|
+
except OSError:
|
|
254
|
+
pass # bundled binary present but unusable (e.g. glibc too old) - fall through
|
|
255
|
+
|
|
256
|
+
_cached = build()
|
|
257
|
+
return _cached
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/* The LCAO-GTO basis set, and how one shell is evaluated.
|
|
2
|
+
*
|
|
3
|
+
* All three kernels take the same nine flat arrays straight out of basis.bin
|
|
4
|
+
* (see docs/binary-format.md) and walked them with their own ishell/iprim/
|
|
5
|
+
* icoeff running counters - three copies of the same bookkeeping, and the
|
|
6
|
+
* largest index-bug surface in the kernels. `Basis` bundles the arrays;
|
|
7
|
+
* `radial_*` are the two per-shell radial amplitudes, which were written out
|
|
8
|
+
* twice each.
|
|
9
|
+
*
|
|
10
|
+
* The layout is shell-major: atoms in order, each atom's shells in order, and
|
|
11
|
+
* per shell one angular momentum l, one list of shell_Nprim primitives, and
|
|
12
|
+
* 2l+1 consecutive MO coefficients (m in ORCA's order). So walking it means
|
|
13
|
+
* carrying three offsets at once - the shell, its first primitive, and its
|
|
14
|
+
* first coefficient.
|
|
15
|
+
*
|
|
16
|
+
* Everything here is Hartree atomic units: Bohr, Bohr^-1, Bohr^-2.
|
|
17
|
+
*/
|
|
18
|
+
#ifndef PHYSIKMDB_BASIS_H
|
|
19
|
+
#define PHYSIKMDB_BASIS_H
|
|
20
|
+
|
|
21
|
+
#include <math.h>
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* One orbital's basis set. Borrows the caller's arrays - it copies nothing and
|
|
25
|
+
* owns nothing, so they must outlive it.
|
|
26
|
+
*/
|
|
27
|
+
typedef struct
|
|
28
|
+
{
|
|
29
|
+
int Natoms;
|
|
30
|
+
int Nshells; // total over all atoms, i.e. sum(Nshells_per_atom)
|
|
31
|
+
int Nbasis; // sum of 2l+1 over all shells; the length of one coefficient row
|
|
32
|
+
|
|
33
|
+
const int *Nshells_per_atom; // length Natoms
|
|
34
|
+
const float *Rx; // atom x-positions [Bohr]; length Natoms
|
|
35
|
+
const float *Ry; // atom y-positions [Bohr]; length Natoms
|
|
36
|
+
const float *Rz; // atom z-positions [Bohr]; length Natoms
|
|
37
|
+
const int *shell_l; // angular momentum per shell; length Nshells
|
|
38
|
+
const int *shell_Nprim; // primitives per shell; length Nshells
|
|
39
|
+
const float *exponents; // alpha_p [Bohr^-2]; length sum(shell_Nprim)
|
|
40
|
+
const float *contractions; // c_p per primitive; length sum(shell_Nprim)
|
|
41
|
+
} Basis;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Bundle the flat arrays, counting the shell and basis-function totals the
|
|
45
|
+
* kernels each used to count for themselves.
|
|
46
|
+
*
|
|
47
|
+
* @param Natoms number of atoms
|
|
48
|
+
* @param Nshells_per_atom shells per atom; length Natoms
|
|
49
|
+
* @param Rx x-positions [Bohr]; length Natoms
|
|
50
|
+
* @param Ry y-positions [Bohr]; length Natoms
|
|
51
|
+
* @param Rz z-positions [Bohr]; length Natoms
|
|
52
|
+
* @param shell_l angular momentum per shell
|
|
53
|
+
* @param shell_Nprim primitives per shell
|
|
54
|
+
* @param exponents alpha_p [Bohr^-2]
|
|
55
|
+
* @param contractions c_p per primitive
|
|
56
|
+
* @return the bundle, with Nshells and Nbasis filled in
|
|
57
|
+
*/
|
|
58
|
+
static inline Basis basis_from_arrays(
|
|
59
|
+
int Natoms,
|
|
60
|
+
const int *Nshells_per_atom,
|
|
61
|
+
const float *Rx, const float *Ry, const float *Rz,
|
|
62
|
+
const int *shell_l, const int *shell_Nprim,
|
|
63
|
+
const float *exponents, const float *contractions)
|
|
64
|
+
{
|
|
65
|
+
Basis basis;
|
|
66
|
+
basis.Natoms = Natoms;
|
|
67
|
+
basis.Nshells_per_atom = Nshells_per_atom;
|
|
68
|
+
basis.Rx = Rx;
|
|
69
|
+
basis.Ry = Ry;
|
|
70
|
+
basis.Rz = Rz;
|
|
71
|
+
basis.shell_l = shell_l;
|
|
72
|
+
basis.shell_Nprim = shell_Nprim;
|
|
73
|
+
basis.exponents = exponents;
|
|
74
|
+
basis.contractions = contractions;
|
|
75
|
+
|
|
76
|
+
basis.Nshells = 0;
|
|
77
|
+
for (int a = 0; a < Natoms; a++)
|
|
78
|
+
{
|
|
79
|
+
basis.Nshells += Nshells_per_atom[a];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
basis.Nbasis = 0;
|
|
83
|
+
for (int s = 0; s < basis.Nshells; s++)
|
|
84
|
+
{
|
|
85
|
+
basis.Nbasis += 2 * shell_l[s] + 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return basis;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* The two approximations, shared by all three kernels.
|
|
92
|
+
*
|
|
93
|
+
* Both are RELATIVE AMPLITUDES: a contribution below this fraction of what it
|
|
94
|
+
* would otherwise be is dropped. So smaller is always closer to exact, and
|
|
95
|
+
* always slower - which is the only reason they exist, since most of a large
|
|
96
|
+
* basis set contributes nothing to any single orbital.
|
|
97
|
+
*
|
|
98
|
+
* They used to differ per kernel, and the difference was not a considered
|
|
99
|
+
* accuracy budget so much as drift: the momentum map applied no cutoff at all,
|
|
100
|
+
* momentumspace.c cut primitives at exp(-30) = 9e-14, and realspace.c at
|
|
101
|
+
* exp(-7) = 9e-4 - which, because it turns that into a cutoff radius
|
|
102
|
+
* sqrt(eps/alpha), discarded up to 5% of a g shell's peak amplitude. Being
|
|
103
|
+
* consistent and being right both point the same way here.
|
|
104
|
+
*
|
|
105
|
+
* The primitive cutoff was written as the exponent argument rather than the
|
|
106
|
+
* amplitude, which made it read backwards: larger meant more exact. Stating
|
|
107
|
+
* the amplitude and deriving the exponent is the same arithmetic under a name
|
|
108
|
+
* that means what it says. */
|
|
109
|
+
#define PRIMITIVE_EPSILON 1e-13f // exp(-x) below this: primitive dropped
|
|
110
|
+
#define COEFFICIENT_EPSILON 1e-6f // |c_mu| below this: whole shell dropped
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The exponent argument at which a primitive reaches PRIMITIVE_EPSILON.
|
|
114
|
+
*
|
|
115
|
+
* `alpha_p * r^2` in real space, `k^2 / (4 alpha_p)` in momentum space; both
|
|
116
|
+
* enter as exp(-x). Derived rather than written down twice, so the two cannot
|
|
117
|
+
* drift apart.
|
|
118
|
+
*
|
|
119
|
+
* @return the cutoff in x, i.e. -log(PRIMITIVE_EPSILON)
|
|
120
|
+
*/
|
|
121
|
+
static inline float primitive_exponent_cutoff(void)
|
|
122
|
+
{
|
|
123
|
+
return -logf(PRIMITIVE_EPSILON);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The radial amplitude of one shell in real space: sum_p c_p exp(-alpha_p r^2).
|
|
128
|
+
*
|
|
129
|
+
* The 2l+1 members of a shell share it, so it is evaluated once per shell and
|
|
130
|
+
* per point, not once per basis function.
|
|
131
|
+
*
|
|
132
|
+
* @param basis the basis set
|
|
133
|
+
* @param shell shell index, 0 .. Nshells-1
|
|
134
|
+
* @param first_primitive index of the shell's first primitive in `exponents`
|
|
135
|
+
* @param r2 squared distance from the shell's atom [Bohr^2]
|
|
136
|
+
* @param cutoff skip primitives whose alpha_p * r^2 reaches this; normally
|
|
137
|
+
* primitive_exponent_cutoff()
|
|
138
|
+
* @return the radial amplitude
|
|
139
|
+
*/
|
|
140
|
+
static inline float radial_realspace(const Basis *basis, int shell,
|
|
141
|
+
int first_primitive, float r2, float cutoff)
|
|
142
|
+
{
|
|
143
|
+
float radial = 0.0f;
|
|
144
|
+
|
|
145
|
+
for (int p = 0; p < basis->shell_Nprim[shell]; p++)
|
|
146
|
+
{
|
|
147
|
+
float exponent = basis->exponents[first_primitive + p] * r2;
|
|
148
|
+
|
|
149
|
+
// Skip primitives that have underflowed to zero anyway
|
|
150
|
+
if (exponent < cutoff)
|
|
151
|
+
{
|
|
152
|
+
radial += basis->contractions[first_primitive + p] * expf(-exponent);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return radial;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The radial amplitude of one shell in momentum space:
|
|
161
|
+
* (k/2)^l * sum_p c_p alpha_p^(-l-3/2) exp(-k^2 / 4 alpha_p).
|
|
162
|
+
*
|
|
163
|
+
* This is the closed-form radial Fourier integral of a contracted Gaussian
|
|
164
|
+
* (see theory/main.tex); the momentum map evaluates it once per shell at the
|
|
165
|
+
* hemisphere's fixed |k|, the momentum density tabulates it over k.
|
|
166
|
+
*
|
|
167
|
+
* @param basis the basis set
|
|
168
|
+
* @param shell shell index, 0 .. Nshells-1
|
|
169
|
+
* @param first_primitive index of the shell's first primitive in `exponents`
|
|
170
|
+
* @param k momentum magnitude [Bohr^-1]
|
|
171
|
+
* @param cutoff skip primitives whose k^2 / (4 alpha_p) reaches this;
|
|
172
|
+
* normally primitive_exponent_cutoff()
|
|
173
|
+
* @return the radial amplitude
|
|
174
|
+
*/
|
|
175
|
+
static inline float radial_momentum(const Basis *basis, int shell,
|
|
176
|
+
int first_primitive, float k, float cutoff)
|
|
177
|
+
{
|
|
178
|
+
int l = basis->shell_l[shell];
|
|
179
|
+
float k2 = k * k;
|
|
180
|
+
float radial = 0.0f;
|
|
181
|
+
|
|
182
|
+
for (int p = 0; p < basis->shell_Nprim[shell]; p++)
|
|
183
|
+
{
|
|
184
|
+
float exponent = k2 / (4.0f * basis->exponents[first_primitive + p]);
|
|
185
|
+
|
|
186
|
+
if (exponent < cutoff)
|
|
187
|
+
{
|
|
188
|
+
radial += basis->contractions[first_primitive + p] *
|
|
189
|
+
powf(basis->exponents[first_primitive + p], -l - 1.5f) *
|
|
190
|
+
expf(-exponent);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Factor independent of alpha_p and c_p. Dividing by 2^l is exact, so it
|
|
195
|
+
// does not matter that the two callers used to bracket this differently.
|
|
196
|
+
return radial * (powf(k, l) / powf(2.0f, l));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#endif
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/* Real cubic harmonics, shared by all three kernels.
|
|
2
|
+
*
|
|
3
|
+
* The table was copy-pasted three times before this header. Two copies (the
|
|
4
|
+
* momentum map and the momentum density) wrote the unit-vector form, valid
|
|
5
|
+
* only on |k| = 1; the real-space copy wrote the general solid-harmonic form
|
|
6
|
+
* with an explicit r^2. They are the same polynomials - the general form
|
|
7
|
+
* evaluated at r^2 = 1 is the unit-vector form - so one helper covers all
|
|
8
|
+
* three, with the k-space callers passing the literal 1.0f.
|
|
9
|
+
*
|
|
10
|
+
* That literal matters. Passing kx^2 + ky^2 + kz^2 instead would be "the same"
|
|
11
|
+
* only in exact arithmetic; in float it differs from 1 in the last bits and
|
|
12
|
+
* shifts every d, f and g harmonic. tests/test_wasm_physics.py has a
|
|
13
|
+
* regression test for exactly that mistake.
|
|
14
|
+
*
|
|
15
|
+
* Normalisations follow ORCA's real solid harmonic convention:
|
|
16
|
+
* https://www.faccts.de/docs/orca/6.1/manual/contents/utilitiesvisualization/orca_2json.html#definition-of-the-real-solid-harmonic-orbitals
|
|
17
|
+
*/
|
|
18
|
+
#ifndef PHYSIKMDB_HARMONICS_H
|
|
19
|
+
#define PHYSIKMDB_HARMONICS_H
|
|
20
|
+
|
|
21
|
+
#define C_S 0.28209479177387814 // 1/2 * sqrt(1 / pi)
|
|
22
|
+
#define C_P 0.4886025119029199 // 1/2 * sqrt(3 / pi)
|
|
23
|
+
#define C_D 1.0925484305920792 // 1/2 * sqrt(15 / pi)
|
|
24
|
+
#define C_F 2.890611442640554 // 1/2 * sqrt(105 / pi)
|
|
25
|
+
#define C_G 5.006685883593409 // 3/2 * sqrt(35 / pi)
|
|
26
|
+
|
|
27
|
+
// First cubic-harmonic index of each shell (s, p, d, f, g); a shell of angular
|
|
28
|
+
// momentum l occupies SHELL_START[l] .. SHELL_START[l] + 2l
|
|
29
|
+
static const int SHELL_START[5] = {0, 1, 4, 9, 16};
|
|
30
|
+
|
|
31
|
+
// The highest angular momentum the table covers (g)
|
|
32
|
+
#define MAX_ANGULAR_MOMENTUM 4
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Evaluate the real cubic harmonics at one point, up to a given shell.
|
|
36
|
+
*
|
|
37
|
+
* Filled cumulatively: only entries belonging to shells the caller actually
|
|
38
|
+
* needs are written, since for one orbital most of a basis set contributes
|
|
39
|
+
* nothing. `max_l` is a parameter rather than something derived here because
|
|
40
|
+
* the three kernels each decide it differently - over every shell, over the
|
|
41
|
+
* significant shells only, or per atom.
|
|
42
|
+
*
|
|
43
|
+
* @param x [Bohr or Bohr^-1] first Cartesian component
|
|
44
|
+
* @param y [Bohr or Bohr^-1] second Cartesian component
|
|
45
|
+
* @param z [Bohr or Bohr^-1] third Cartesian component
|
|
46
|
+
* @param r2 [x^2 + y^2 + z^2] in the same units squared; pass the literal
|
|
47
|
+
* 1.0f when x/y/z are already a unit vector
|
|
48
|
+
* @param max_l highest angular momentum to fill (0 = s only, 4 = through g)
|
|
49
|
+
* @param H out; entries SHELL_START[0] .. SHELL_START[max_l] + 2*max_l written
|
|
50
|
+
*/
|
|
51
|
+
static inline void cubic_harmonics(float x, float y, float z, float r2,
|
|
52
|
+
int max_l, float H[25])
|
|
53
|
+
{
|
|
54
|
+
H[0] = C_S; // s
|
|
55
|
+
|
|
56
|
+
if (max_l >= 1)
|
|
57
|
+
{
|
|
58
|
+
H[1] = C_P * z; // pz
|
|
59
|
+
H[2] = C_P * x; // px
|
|
60
|
+
H[3] = C_P * y; // py
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (max_l >= 2)
|
|
64
|
+
{
|
|
65
|
+
float x2 = x * x;
|
|
66
|
+
float y2 = y * y;
|
|
67
|
+
float z2 = z * z;
|
|
68
|
+
|
|
69
|
+
H[4] = C_D * 0.28867513459481287f * (3.0f * z2 - r2); // dz2
|
|
70
|
+
H[5] = C_D * x * z; // dxz
|
|
71
|
+
H[6] = C_D * y * z; // dyz
|
|
72
|
+
H[7] = C_D * 0.5f * (x2 - y2); // dx2-y2
|
|
73
|
+
H[8] = C_D * x * y; // dxy
|
|
74
|
+
|
|
75
|
+
if (max_l >= 3)
|
|
76
|
+
{
|
|
77
|
+
H[9] = C_F * 0.12909944487358058f * z * (5.0f * z2 - 3.0f * r2); // fz3
|
|
78
|
+
H[10] = C_F * 0.15811388300841897f * x * (5.0f * z2 - r2); // fxz2
|
|
79
|
+
H[11] = C_F * 0.15811388300841897f * y * (5.0f * z2 - r2); // fyz2
|
|
80
|
+
H[12] = C_F * 0.5f * z * (x2 - y2); // fz(x2-y2)
|
|
81
|
+
H[13] = C_F * x * y * z; // fxyz
|
|
82
|
+
H[14] = -C_F * 0.20412414523193148f * x * (x2 - 3.0f * y2); // fx(x2-3y2)
|
|
83
|
+
H[15] = -C_F * 0.20412414523193148f * y * (3.0f * x2 - y2); // fy(3x2-y2)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (max_l >= 4)
|
|
87
|
+
{
|
|
88
|
+
float r4 = r2 * r2;
|
|
89
|
+
|
|
90
|
+
H[16] = C_G * 0.021128856368212916f * (35.0f * z2 * z2 - 30.0f * z2 * r2 + 3.0f * r4); // gz4
|
|
91
|
+
H[17] = C_G * 0.1336306209562122f * x * z * (7.0f * z2 - 3.0f * r2); // gxz3
|
|
92
|
+
H[18] = C_G * 0.1336306209562122f * y * z * (7.0f * z2 - 3.0f * r2); // gyz3
|
|
93
|
+
H[19] = C_G * 0.09449111825230681f * (x2 - y2) * (7.0f * z2 - r2); // gx2-y2
|
|
94
|
+
H[20] = C_G * 0.18898223650461363f * x * y * (7.0f * z2 - r2); // gxy
|
|
95
|
+
H[21] = -C_G * 0.3535533905932738f * x * z * (x2 - 3.0f * y2); // gx(x2-3y2)
|
|
96
|
+
H[22] = -C_G * 0.3535533905932738f * y * z * (3.0f * x2 - y2); // gy(3x2-y2)
|
|
97
|
+
H[23] = -C_G * 0.125f * (x2 * x2 - 6.0f * x2 * y2 + y2 * y2); // gx4-6x2y2+y4
|
|
98
|
+
H[24] = -C_G * 0.5f * x * y * (x2 - y2); // gxy(x2-y2)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#endif
|