dense-evolution 8.3.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.
- dashboard_core/__init__.py +115 -0
- dashboard_core/_gate_tables.py +30 -0
- dashboard_core/band_structure.py +71 -0
- dashboard_core/circuit_builder_component.py +232 -0
- dashboard_core/circuit_diagram.py +216 -0
- dashboard_core/crypto_protocols.py +77 -0
- dashboard_core/engine.py +326 -0
- dashboard_core/graphical_builder.py +114 -0
- dashboard_core/hamiltonians.py +593 -0
- dashboard_core/mass_decomposition_tool.py +47 -0
- dashboard_core/mitigation.py +343 -0
- dashboard_core/native_hf_diagnostics.py +62 -0
- dashboard_core/noise_tools.py +125 -0
- dashboard_core/qasm_library.py +233 -0
- dashboard_core/qmmm.py +16 -0
- dashboard_core/rag_tool.py +45 -0
- dashboard_core/state_visuals.py +288 -0
- dashboard_core/system_limits.py +60 -0
- dashboard_core/vector_healing.py +102 -0
- dashboard_core/visuals.py +158 -0
- dashboard_core/vqe.py +533 -0
- dashboard_core/wormhole.py +580 -0
- dense_evolution/__init__.py +114 -0
- dense_evolution/autodiff.py +10 -0
- dense_evolution/backends/__init__.py +5 -0
- dense_evolution/backends/chunk/__init__.py +37 -0
- dense_evolution/backends/chunk/_engine_imports.py +57 -0
- dense_evolution/backends/chunk/circuit_chunker.py +55 -0
- dense_evolution/backends/chunk/core.py +432 -0
- dense_evolution/backends/chunk/disk_overflow.py +232 -0
- dense_evolution/backends/chunk/geometry.py +95 -0
- dense_evolution/backends/chunk/guard.py +190 -0
- dense_evolution/backends/chunk/kernels.py +531 -0
- dense_evolution/backends/mps.py +1569 -0
- dense_evolution/backends/statevector.py +616 -0
- dense_evolution/chunk.py +25 -0
- dense_evolution/circuits/__init__.py +20 -0
- dense_evolution/circuits/compiler.py +488 -0
- dense_evolution/circuits/diagram.py +94 -0
- dense_evolution/circuits/gates.py +91 -0
- dense_evolution/circuits/parser.py +632 -0
- dense_evolution/circuits/qft.py +66 -0
- dense_evolution/circuits/random_circuit.py +85 -0
- dense_evolution/circuits/registry.py +74 -0
- dense_evolution/circuits/topology.py +79 -0
- dense_evolution/circuits/trotter.py +265 -0
- dense_evolution/circuits/uccsd.py +275 -0
- dense_evolution/cli.py +199 -0
- dense_evolution/compiler.py +9 -0
- dense_evolution/config.py +49 -0
- dense_evolution/drawing.py +10 -0
- dense_evolution/entropy.py +9 -0
- dense_evolution/fermions.py +9 -0
- dense_evolution/gates.py +9 -0
- dense_evolution/harrison_tb.py +16 -0
- dense_evolution/healing.py +18 -0
- dense_evolution/interop/__init__.py +18 -0
- dense_evolution/interop/qiskit_pennylane.py +406 -0
- dense_evolution/measurement.py +10 -0
- dense_evolution/mitigation/__init__.py +54 -0
- dense_evolution/mitigation/healing.py +215 -0
- dense_evolution/mitigation/kl_divergence.py +93 -0
- dense_evolution/mitigation/magic_entropy.py +163 -0
- dense_evolution/mitigation/magic_entropy_shadows.py +262 -0
- dense_evolution/mitigation/renyi.py +168 -0
- dense_evolution/mitigation/stabilizer_renyi_entropy.py +103 -0
- dense_evolution/mitigation/zne.py +990 -0
- dense_evolution/mps.py +9 -0
- dense_evolution/native_hf/__init__.py +26 -0
- dense_evolution/native_hf/_libcint/LICENSE-libcint +10 -0
- dense_evolution/native_hf/_libcint/libdecint.dll +0 -0
- dense_evolution/native_hf/assembly.py +304 -0
- dense_evolution/native_hf/basis.py +117 -0
- dense_evolution/native_hf/boys.py +35 -0
- dense_evolution/native_hf/bridge.py +112 -0
- dense_evolution/native_hf/cartesian.py +64 -0
- dense_evolution/native_hf/coulomb.py +196 -0
- dense_evolution/native_hf/differentiable.py +53 -0
- dense_evolution/native_hf/gaussians.py +79 -0
- dense_evolution/native_hf/kinetic.py +52 -0
- dense_evolution/native_hf/libcint_bridge.py +167 -0
- dense_evolution/native_hf/overlap.py +91 -0
- dense_evolution/native_hf/scf.py +404 -0
- dense_evolution/noise/__init__.py +79 -0
- dense_evolution/noise/coherent_attack.py +264 -0
- dense_evolution/noise/cosmic_ray.py +61 -0
- dense_evolution/noise/density_matrix_channels.py +78 -0
- dense_evolution/noise/differentiable.py +66 -0
- dense_evolution/noise/kraus/__init__.py +6 -0
- dense_evolution/noise/kraus/amplitude_damping.py +47 -0
- dense_evolution/noise/kraus/bitflip.py +22 -0
- dense_evolution/noise/kraus/combined.py +16 -0
- dense_evolution/noise/kraus/depolarizing.py +47 -0
- dense_evolution/noise/kraus/ideal.py +10 -0
- dense_evolution/noise/kraus/phaseflip.py +21 -0
- dense_evolution/noise/kraus_channels.py +285 -0
- dense_evolution/noise/oscillating.py +32 -0
- dense_evolution/noise/pink.py +80 -0
- dense_evolution/observables.py +11 -0
- dense_evolution/parser.py +9 -0
- dense_evolution/physics/__init__.py +27 -0
- dense_evolution/physics/entropy.py +161 -0
- dense_evolution/physics/fermions.py +322 -0
- dense_evolution/physics/observables.py +523 -0
- dense_evolution/physics/qec.py +1113 -0
- dense_evolution/physics/spectral.py +143 -0
- dense_evolution/physics/states.py +43 -0
- dense_evolution/protocols/__init__.py +27 -0
- dense_evolution/protocols/bb84.py +133 -0
- dense_evolution/protocols/di_qkd_ghz.py +199 -0
- dense_evolution/protocols/dicka_protocol2.py +124 -0
- dense_evolution/qec.py +20 -0
- dense_evolution/qft.py +9 -0
- dense_evolution/qmmm/__init__.py +13 -0
- dense_evolution/qmmm/ase_bridge.py +97 -0
- dense_evolution/qmmm/forces.py +388 -0
- dense_evolution/qmmm/propagation.py +80 -0
- dense_evolution/qmmm/region.py +137 -0
- dense_evolution/random_circuit.py +15 -0
- dense_evolution/registry.py +9 -0
- dense_evolution/simulator.py +10 -0
- dense_evolution/solvers/__init__.py +19 -0
- dense_evolution/solvers/autodiff.py +169 -0
- dense_evolution/solvers/harrison_tb.py +189 -0
- dense_evolution/solvers/vhd_tb.py +187 -0
- dense_evolution/states.py +9 -0
- dense_evolution/topology.py +9 -0
- dense_evolution/trotter.py +9 -0
- dense_evolution/utils/__init__.py +13 -0
- dense_evolution/utils/drawing.py +101 -0
- dense_evolution/utils/mass_decomposition.py +246 -0
- dense_evolution/utils/measurement.py +94 -0
- dense_evolution/vhd_tb.py +16 -0
- dense_evolution-8.3.0.dist-info/METADATA +366 -0
- dense_evolution-8.3.0.dist-info/RECORD +165 -0
- dense_evolution-8.3.0.dist-info/WHEEL +5 -0
- dense_evolution-8.3.0.dist-info/entry_points.txt +2 -0
- dense_evolution-8.3.0.dist-info/licenses/license.md +58 -0
- dense_evolution-8.3.0.dist-info/top_level.txt +5 -0
- ia_utils/__init__.py +0 -0
- ia_utils/adversarial_vector_attack.py +196 -0
- ia_utils/rag.py +288 -0
- ia_utils/vector_healing.py +399 -0
- local_site/__init__.py +0 -0
- local_site/app/__init__.py +0 -0
- local_site/app/server.py +1009 -0
- mcp_server/__init__.py +0 -0
- mcp_server/client.py +324 -0
- mcp_server/config.py +32 -0
- mcp_server/models.py +347 -0
- mcp_server/molecules.py +71 -0
- mcp_server/server.py +119 -0
- mcp_server/tools/__init__.py +0 -0
- mcp_server/tools/chemistry_tools.py +225 -0
- mcp_server/tools/circuit_tools.py +83 -0
- mcp_server/tools/crypto_tools.py +66 -0
- mcp_server/tools/mitigation_tools.py +81 -0
- mcp_server/tools/noise_tools.py +60 -0
- mcp_server/tools/retrieval_tools.py +44 -0
- mcp_server/tools/system_tools.py +149 -0
- mcp_server/tools/wormhole_tools.py +142 -0
- mcp_server/utils/__init__.py +0 -0
- mcp_server/utils/cache.py +55 -0
- mcp_server/utils/images.py +67 -0
- mcp_server/utils/truncation.py +38 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Enumerating the physical (lx,ly,lz) Cartesian components of a shell.
|
|
2
|
+
|
|
3
|
+
Our integral tensors are shaped (degree+1, degree+1, degree+1, ...) for
|
|
4
|
+
convenience, but only the (lx,ly,lz) triples with lx+ly+lz == degree
|
|
5
|
+
are physical basis functions -- e.g. for a p shell (degree=1) that's
|
|
6
|
+
(1,0,0), (0,1,0), (0,0,1), not e.g. (1,1,0) which the tensor shape
|
|
7
|
+
happens to also have room for.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def cartesian_powers(degree: int) -> np.ndarray:
|
|
14
|
+
"""Returns an (M, 3) array of (lx,ly,lz) triples with lx+ly+lz == degree,
|
|
15
|
+
in a fixed canonical order determined by the generator below -- not the
|
|
16
|
+
common lexicographic convention some other codes use. For p (degree=1)
|
|
17
|
+
that's px, pz, py, not px, py, pz (confirmed by calling this function
|
|
18
|
+
directly, not assumed from the shape of the loop); for d (degree=2),
|
|
19
|
+
xx, xz, xy, zz, yz, yy, not the lexicographic xx, xy, xz, yy, yz, zz.
|
|
20
|
+
Internally consistent (cartesian_normalization_ratios and every caller
|
|
21
|
+
in assembly.py use this exact order), but this matters for anything
|
|
22
|
+
that needs to match a DIFFERENT code's AO ordering (e.g. libcint's) --
|
|
23
|
+
see native_hf/libcint_bridge.py's per-degree permutation."""
|
|
24
|
+
return np.array(
|
|
25
|
+
[(lx, degree - lx - lz, lz) for lx in range(degree, -1, -1) for lz in range(degree - lx, -1, -1)],
|
|
26
|
+
dtype=np.int32,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _double_factorial_odd(n: int) -> float:
|
|
31
|
+
"""(2n-1)!! for n >= 0, with the (2*0-1)!! = (-1)!! = 1 convention."""
|
|
32
|
+
result = 1.0
|
|
33
|
+
k = 2 * n - 1
|
|
34
|
+
while k > 1:
|
|
35
|
+
result *= k
|
|
36
|
+
k -= 2
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def cartesian_normalization_ratios(degree: int) -> np.ndarray:
|
|
41
|
+
"""Relative normalization of each Cartesian component of a shell of
|
|
42
|
+
the given degree, relative to the (degree,0,0) component -- 1.0 for
|
|
43
|
+
every component when degree<=1 (px, py, pz are equivalent by
|
|
44
|
+
symmetry), but genuinely different starting at degree=2 (e.g. dxy
|
|
45
|
+
needs a larger normalization constant than dxx, since <dxx|dxx> =
|
|
46
|
+
3*<dxy|dxy> for the same exponent).
|
|
47
|
+
|
|
48
|
+
Standard result for an unnormalized Cartesian Gaussian primitive
|
|
49
|
+
(x-Rx)^lx (y-Ry)^ly (z-Rz)^lz exp(-a|r-R|^2): its normalization
|
|
50
|
+
constant is proportional to 1/sqrt((2lx-1)!!(2ly-1)!!(2lz-1)!!), so
|
|
51
|
+
this ratio -- independent of the exponent, verified numerically
|
|
52
|
+
against overlap_3d's own self-overlap at several exponents -- is
|
|
53
|
+
sqrt((2*degree-1)!! / ((2lx-1)!!(2ly-1)!!(2lz-1)!!)).
|
|
54
|
+
|
|
55
|
+
Order matches cartesian_powers(degree) exactly, so callers can zip
|
|
56
|
+
or elementwise-multiply the two directly."""
|
|
57
|
+
powers = cartesian_powers(degree)
|
|
58
|
+
reference = _double_factorial_odd(degree)
|
|
59
|
+
return np.array(
|
|
60
|
+
[
|
|
61
|
+
np.sqrt(reference / (_double_factorial_odd(lx) * _double_factorial_odd(ly) * _double_factorial_odd(lz)))
|
|
62
|
+
for lx, ly, lz in powers
|
|
63
|
+
]
|
|
64
|
+
)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Nuclear attraction and electron repulsion integrals.
|
|
2
|
+
|
|
3
|
+
Both integral types reduce to the same building block: the n-th order
|
|
4
|
+
Hermite Coulomb integral
|
|
5
|
+
|
|
6
|
+
V_n(P, C) = K * F_n(p |P-C|^2)
|
|
7
|
+
|
|
8
|
+
where F_n is the Boys function, P the center of a Gaussian product and
|
|
9
|
+
C either a nuclear position (one-electron case) or the center of a
|
|
10
|
+
second electron pair's product Gaussian (two-electron case). Angular
|
|
11
|
+
momentum on each of up to four centers is then built up from V_n by
|
|
12
|
+
three kinds of linear recursion (Obara-Saika):
|
|
13
|
+
|
|
14
|
+
* vertical transfer -- raises angular momentum on the "bra" center
|
|
15
|
+
while also shifting the Boys-function order n. This is the only
|
|
16
|
+
recursion that touches the Boys function directly.
|
|
17
|
+
* horizontal transfer -- shifts angular momentum from one center to
|
|
18
|
+
its partner on the same electron (exact via Gaussian product
|
|
19
|
+
translation, same recursion used for overlap integrals).
|
|
20
|
+
* electron transfer -- shifts angular momentum from electron 1's
|
|
21
|
+
pair to electron 2's pair (only needed for the two-electron
|
|
22
|
+
repulsion integral).
|
|
23
|
+
|
|
24
|
+
Each recursion has a 2-term memory in its recursion index, so each maps
|
|
25
|
+
onto jax.lax.scan.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import functools
|
|
29
|
+
|
|
30
|
+
import jax
|
|
31
|
+
import jax.numpy as jnp
|
|
32
|
+
|
|
33
|
+
from dense_evolution.native_hf.boys import boys
|
|
34
|
+
from dense_evolution.native_hf.gaussians import GaussianShell3D, product_center, product_prefactor
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _hermite_base(order: int, g1: GaussianShell3D, g2: GaussianShell3D, scale: jax.Array, C: jax.Array) -> jax.Array:
|
|
38
|
+
"""V[n,0,0,0] for n = 0..order-1, i.e. the (0,0,0) angular-momentum
|
|
39
|
+
slice of the Hermite Coulomb integral at every Boys order we'll need."""
|
|
40
|
+
K = product_prefactor(g1, g2)
|
|
41
|
+
P = product_center(g1, g2)
|
|
42
|
+
dist_sq = jnp.sum(jnp.square(P - C))
|
|
43
|
+
orders = jnp.arange(order)
|
|
44
|
+
return K * boys(orders, scale * dist_sq)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _vertical_step(scale, p, PA, PC, carry, i):
|
|
48
|
+
v_prev, v_prev2 = carry
|
|
49
|
+
v_prev_shift = jnp.roll(v_prev, shift=-1, axis=0)
|
|
50
|
+
v_prev2_shift = jnp.roll(v_prev2, shift=-1, axis=0)
|
|
51
|
+
|
|
52
|
+
v_next = (
|
|
53
|
+
PA * v_prev
|
|
54
|
+
- (scale / p) * PC * v_prev_shift
|
|
55
|
+
+ ((i - 1) / (2.0 * p)) * v_prev2
|
|
56
|
+
- (((i - 1) * scale) / (2.0 * p * p)) * v_prev2_shift
|
|
57
|
+
)
|
|
58
|
+
return (v_next, v_prev), v_next
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _raise_bra_degree(V0: jax.Array, degree: int, scale: jax.Array, p: jax.Array, A: jax.Array, C: jax.Array, P: jax.Array) -> jax.Array:
|
|
62
|
+
"""Appends a new trailing axis of size `degree` to V0 built via the
|
|
63
|
+
vertical transfer recursion (one Cartesian direction at a time --
|
|
64
|
+
call this three times, once per x/y/z, to build a full 3D shell)."""
|
|
65
|
+
step = functools.partial(_vertical_step, scale, p, P - A, P - C)
|
|
66
|
+
init = (V0, jnp.zeros_like(V0))
|
|
67
|
+
_, rest = jax.lax.scan(step, init, jnp.arange(1, degree), unroll=True)
|
|
68
|
+
return jnp.concatenate((V0[..., None], jnp.moveaxis(rest, 0, -1)), axis=-1)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _hermite_coulomb_tensor(g1: GaussianShell3D, g2: GaussianShell3D, scale: jax.Array, C: jax.Array) -> jax.Array:
|
|
72
|
+
"""The (degree+1)^3 tensor V[ix,iy,iz] = V_0-order Hermite integral
|
|
73
|
+
with all angular momentum on the bra (g1) side, at Boys order 0."""
|
|
74
|
+
a, A = jnp.asarray(g1.exponent), jnp.asarray(g1.center)
|
|
75
|
+
b, B = jnp.asarray(g2.exponent), jnp.asarray(g2.center)
|
|
76
|
+
p = a + b
|
|
77
|
+
P = (a * A + b * B) / p
|
|
78
|
+
|
|
79
|
+
if g1.degree == 0:
|
|
80
|
+
return _hermite_base(1, g1, g2, scale, C)[0, None, None, None]
|
|
81
|
+
|
|
82
|
+
n = g1.degree + 1
|
|
83
|
+
V = _hermite_base(3 * n, g1, g2, scale, C)
|
|
84
|
+
for axis in range(3):
|
|
85
|
+
V = _raise_bra_degree(V, n, scale, p, A[axis], C[axis], P[axis])
|
|
86
|
+
if axis < 2:
|
|
87
|
+
V = V[:-n, ...]
|
|
88
|
+
return V[0, ...]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _horizontal_step(diff, column, _):
|
|
92
|
+
shifted = jnp.roll(column, shift=-1)
|
|
93
|
+
new_column = diff * column + shifted
|
|
94
|
+
return new_column, new_column
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _shift_degree(tensor: jax.Array, axis: int, new_size: int, center_from: jax.Array, center_to: jax.Array) -> jax.Array:
|
|
98
|
+
"""Adds a trailing axis of size new_size to `tensor`, transferring
|
|
99
|
+
angular momentum from `axis` to it via the (exact) product-Gaussian
|
|
100
|
+
translation recursion. Used both for same-electron transfers
|
|
101
|
+
(nuclear attraction, and g1->g2 / g3->g4 in repulsion) and can be
|
|
102
|
+
reused unmodified for the cross-electron transfer below because the
|
|
103
|
+
recursion only differs in its coefficients, supplied by the caller."""
|
|
104
|
+
if new_size <= 1:
|
|
105
|
+
return tensor[..., None]
|
|
106
|
+
moved = jnp.moveaxis(tensor, axis, -1)
|
|
107
|
+
step = functools.partial(_horizontal_step, center_from - center_to)
|
|
108
|
+
_, rest = jax.lax.scan(step, moved, jnp.arange(1, new_size), unroll=True)
|
|
109
|
+
combined = jnp.concatenate((moved[..., None], jnp.moveaxis(rest, 0, -1)), axis=-1)
|
|
110
|
+
return jnp.moveaxis(combined, -2, axis)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@jax.jit
|
|
114
|
+
def nuclear_attraction(g1: GaussianShell3D, g2: GaussianShell3D, nucleus: jax.Array) -> jax.Array:
|
|
115
|
+
"""<g1| 1/|r - nucleus| |g2>, shape (L1+1,)*3 + (L2+1,)*3."""
|
|
116
|
+
a, A = jnp.asarray(g1.exponent), jnp.asarray(g1.center)
|
|
117
|
+
b, B = jnp.asarray(g2.exponent), jnp.asarray(g2.center)
|
|
118
|
+
p = a + b
|
|
119
|
+
|
|
120
|
+
padded_g1 = GaussianShell3D(degree=g1.degree + g2.degree, exponent=a, center=A)
|
|
121
|
+
I = (2.0 * jnp.pi / p) * _hermite_coulomb_tensor(padded_g1, g2, p, nucleus)
|
|
122
|
+
|
|
123
|
+
for axis in range(3):
|
|
124
|
+
I = _shift_degree(I, axis, g2.degree + 1, A[axis], B[axis])
|
|
125
|
+
I = I[(slice(0, g1.degree + 1),) * (axis + 1) + (Ellipsis,)]
|
|
126
|
+
return I
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _electron_transfer_step(p, q, alpha, carry, j):
|
|
130
|
+
I_prev, I_prev2 = carry
|
|
131
|
+
I_prev_up = jnp.roll(I_prev, shift=-1, axis=-1)
|
|
132
|
+
I_prev_down = jnp.pad(I_prev[..., :-1], ((0, 0),) * (I_prev.ndim - 1) + ((1, 0),))
|
|
133
|
+
idx = jnp.arange(I_prev.shape[-1]).reshape((1,) * (I_prev.ndim - 1) + (-1,))
|
|
134
|
+
|
|
135
|
+
I_next = (
|
|
136
|
+
alpha * I_prev
|
|
137
|
+
+ (idx / (2.0 * q)) * I_prev_down
|
|
138
|
+
+ ((j - 1) / (2.0 * q)) * I_prev2
|
|
139
|
+
- (p / q) * I_prev_up
|
|
140
|
+
)
|
|
141
|
+
return (I_next, I_prev), I_next
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _transfer_to_second_electron(tensor: jax.Array, axis: int, new_size: int, exponents, centers) -> jax.Array:
|
|
145
|
+
if new_size <= 1:
|
|
146
|
+
return tensor[..., None]
|
|
147
|
+
a, b, c, d = exponents
|
|
148
|
+
A, B, C, D = centers
|
|
149
|
+
p, q = a + b, c + d
|
|
150
|
+
alpha = -(1.0 / q) * (b * (A - B) + d * (C - D))
|
|
151
|
+
|
|
152
|
+
moved = jnp.moveaxis(tensor, axis, -1)
|
|
153
|
+
step = functools.partial(_electron_transfer_step, p, q, alpha)
|
|
154
|
+
init = (moved, jnp.zeros_like(moved))
|
|
155
|
+
_, rest = jax.lax.scan(step, init, jnp.arange(1, new_size), unroll=True)
|
|
156
|
+
combined = jnp.concatenate((moved[..., None], jnp.moveaxis(rest, 0, -1)), axis=-1)
|
|
157
|
+
return jnp.moveaxis(combined, -2, axis)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@jax.jit
|
|
161
|
+
def electron_repulsion(g1: GaussianShell3D, g2: GaussianShell3D, g3: GaussianShell3D, g4: GaussianShell3D) -> jax.Array:
|
|
162
|
+
"""<g1 g2| 1/r12 |g3 g4>, shape (L1+1,)^3 + (L2+1,)^3 + (L3+1,)^3 + (L4+1,)^3."""
|
|
163
|
+
a, A = jnp.asarray(g1.exponent), jnp.asarray(g1.center)
|
|
164
|
+
b, B = jnp.asarray(g2.exponent), jnp.asarray(g2.center)
|
|
165
|
+
c, C = jnp.asarray(g3.exponent), jnp.asarray(g3.center)
|
|
166
|
+
d, D = jnp.asarray(g4.exponent), jnp.asarray(g4.center)
|
|
167
|
+
|
|
168
|
+
padded_g3 = GaussianShell3D(degree=g3.degree + g4.degree, exponent=c, center=C)
|
|
169
|
+
padded_g1 = GaussianShell3D(degree=g1.degree + g2.degree + padded_g3.degree, exponent=a, center=A)
|
|
170
|
+
|
|
171
|
+
p, q = a + b, c + d
|
|
172
|
+
scale = (p * q) / (p + q)
|
|
173
|
+
Q = (c * C + d * D) / q
|
|
174
|
+
K34 = jnp.exp(-((c * d) / q) * jnp.sum(jnp.square(C - D)))
|
|
175
|
+
prefactor = 2.0 * jnp.pi ** 2.5 / (p * q * jnp.sqrt(p + q)) * K34
|
|
176
|
+
|
|
177
|
+
I = prefactor * _hermite_coulomb_tensor(padded_g1, g2, scale, Q)
|
|
178
|
+
|
|
179
|
+
exponents = jnp.array([a, b, c, d])
|
|
180
|
+
for axis in range(3):
|
|
181
|
+
I = _transfer_to_second_electron(
|
|
182
|
+
I, axis, padded_g3.degree + 1, exponents,
|
|
183
|
+
jnp.array([A[axis], B[axis], C[axis], D[axis]]),
|
|
184
|
+
)
|
|
185
|
+
I = I[(slice(0, g1.degree + g2.degree + 1),) * (axis + 1) + (Ellipsis,)]
|
|
186
|
+
|
|
187
|
+
for axis in range(3):
|
|
188
|
+
I = _shift_degree(I, axis, g2.degree + 1, A[axis], B[axis])
|
|
189
|
+
I = I[(slice(0, g1.degree + 1),) * (axis + 1) + (Ellipsis,)]
|
|
190
|
+
|
|
191
|
+
for axis in range(3):
|
|
192
|
+
I = _shift_degree(I, axis + 3, g4.degree + 1, C[axis], D[axis])
|
|
193
|
+
I = I[(slice(0, g1.degree + 1),) * 3 + (slice(0, g3.degree + 1),) * (axis + 1) + (Ellipsis,)]
|
|
194
|
+
|
|
195
|
+
# axes are currently (g1, g3, g2, g4); reorder to (g1, g2, g3, g4)
|
|
196
|
+
return jnp.moveaxis(I, [3, 4, 5], [6, 7, 8])
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""A differentiable-w.r.t.-nuclear-positions RHF total energy, composed
|
|
2
|
+
from basis.py + assembly.py + scf.py.
|
|
3
|
+
|
|
4
|
+
Two separate, deliberate pieces make this possible, neither of which is
|
|
5
|
+
a property of any one of those modules alone:
|
|
6
|
+
|
|
7
|
+
1. Schwarz screening (assembly.py's quartet_screening_indices) is a
|
|
8
|
+
discrete, structural decision -- which shell quartets exist in the
|
|
9
|
+
ERI sum at all -- and can't be part of a jax.grad trace (Python
|
|
10
|
+
control flow can't run on a traced value). It's decided ONCE here,
|
|
11
|
+
from the reference geometry passed to build_energy_fn, and reused as
|
|
12
|
+
a fixed structure for every geometry the returned function is later
|
|
13
|
+
called at.
|
|
14
|
+
|
|
15
|
+
2. scf.py's iterative convergence search (jax.lax.while_loop) can't be
|
|
16
|
+
differentiated in reverse mode either -- scf_electronic_energy
|
|
17
|
+
sidesteps that with the analytic Hartree-Fock gradient (Pople,
|
|
18
|
+
Krishnan, Schlegel & Binkley, 1979) instead of backpropagating
|
|
19
|
+
through the loop.
|
|
20
|
+
|
|
21
|
+
The returned function is only valid near the reference geometry: if the
|
|
22
|
+
nuclei move far enough that a previously-negligible shell-pair
|
|
23
|
+
interaction becomes non-negligible (or vice versa), the frozen
|
|
24
|
+
screening structure is stale and build_energy_fn should be called again
|
|
25
|
+
at the new geometry.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import numpy as np
|
|
29
|
+
|
|
30
|
+
from dense_evolution.native_hf.basis import build_molecule_shells
|
|
31
|
+
from dense_evolution.native_hf.assembly import (
|
|
32
|
+
build_overlap_matrix, build_core_hamiltonian, build_repulsion_tensor, quartet_screening_indices,
|
|
33
|
+
)
|
|
34
|
+
from dense_evolution.native_hf.scf import scf_electronic_energy, nuclear_repulsion_energy
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def build_energy_fn(
|
|
38
|
+
atomic_numbers: list[int], nuclear_charges: list[float], n_electrons: int,
|
|
39
|
+
basis_name: str, reference_geometry_bohr: np.ndarray, screening_tol: float = 1e-12,
|
|
40
|
+
):
|
|
41
|
+
reference_shells = build_molecule_shells(atomic_numbers, np.asarray(reference_geometry_bohr), basis_name)
|
|
42
|
+
quartet_indices = quartet_screening_indices(reference_shells, screening_tol)
|
|
43
|
+
|
|
44
|
+
def energy_fn(geometry_bohr):
|
|
45
|
+
shells = build_molecule_shells(atomic_numbers, geometry_bohr, basis_name)
|
|
46
|
+
S = build_overlap_matrix(shells)
|
|
47
|
+
H_core = build_core_hamiltonian(shells, nuclear_charges, geometry_bohr)
|
|
48
|
+
repulsion = build_repulsion_tensor(shells, quartet_indices=quartet_indices)
|
|
49
|
+
electronic_energy = scf_electronic_energy(S, H_core, repulsion, n_electrons)
|
|
50
|
+
e_nuc = nuclear_repulsion_energy(nuclear_charges, geometry_bohr)
|
|
51
|
+
return electronic_energy + e_nuc
|
|
52
|
+
|
|
53
|
+
return energy_fn
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Primitive Gaussian shells and the Gaussian product theorem.
|
|
2
|
+
|
|
3
|
+
A primitive Gaussian of angular momentum degree L centered at R with
|
|
4
|
+
exponent a is, in 3D:
|
|
5
|
+
|
|
6
|
+
G(r) = (x-Rx)^lx (y-Ry)^ly (z-Rz)^lz * exp(-a |r-R|^2)
|
|
7
|
+
|
|
8
|
+
We only ever need the *maximum* degree L for a shell (s: L=0, p: L=1,
|
|
9
|
+
...) because the recursions below build every (lx,ly,lz) with
|
|
10
|
+
lx+ly+lz <= L in one shot, so a shell is fully described by (L, a, R).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import dataclasses
|
|
14
|
+
|
|
15
|
+
import jax
|
|
16
|
+
import jax.numpy as jnp
|
|
17
|
+
from jax.tree_util import register_pytree_node_class
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@register_pytree_node_class
|
|
21
|
+
@dataclasses.dataclass
|
|
22
|
+
class GaussianShell1D:
|
|
23
|
+
"""A single Cartesian component (x, y, or z) of a Gaussian shell."""
|
|
24
|
+
|
|
25
|
+
degree: int
|
|
26
|
+
exponent: jax.Array # shape ()
|
|
27
|
+
center: jax.Array # shape ()
|
|
28
|
+
|
|
29
|
+
def tree_flatten(self):
|
|
30
|
+
return (self.exponent, self.center), self.degree
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def tree_unflatten(cls, degree, children):
|
|
34
|
+
exponent, center = children
|
|
35
|
+
return cls(degree=degree, exponent=exponent, center=center)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@register_pytree_node_class
|
|
39
|
+
@dataclasses.dataclass
|
|
40
|
+
class GaussianShell3D:
|
|
41
|
+
"""A 3D Gaussian shell: one exponent/center, all (lx,ly,lz) with
|
|
42
|
+
lx+ly+lz <= degree implicitly represented."""
|
|
43
|
+
|
|
44
|
+
degree: int
|
|
45
|
+
exponent: jax.Array # shape ()
|
|
46
|
+
center: jax.Array # shape (3,)
|
|
47
|
+
|
|
48
|
+
def tree_flatten(self):
|
|
49
|
+
return (self.exponent, self.center), self.degree
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def tree_unflatten(cls, degree, children):
|
|
53
|
+
exponent, center = children
|
|
54
|
+
return cls(degree=degree, exponent=exponent, center=center)
|
|
55
|
+
|
|
56
|
+
def component(self, axis: int) -> GaussianShell1D:
|
|
57
|
+
return GaussianShell1D(
|
|
58
|
+
degree=self.degree,
|
|
59
|
+
exponent=self.exponent,
|
|
60
|
+
center=jnp.asarray(self.center)[axis],
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def product_center(g1: GaussianShell3D, g2: GaussianShell3D) -> jax.Array:
|
|
65
|
+
"""The center P of the Gaussian obtained by multiplying two Gaussians
|
|
66
|
+
(Gaussian product theorem): P = (a*A + b*B) / (a+b)."""
|
|
67
|
+
a, b = jnp.asarray(g1.exponent), jnp.asarray(g2.exponent)
|
|
68
|
+
A, B = jnp.asarray(g1.center), jnp.asarray(g2.center)
|
|
69
|
+
return (a * A + b * B) / (a + b)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def product_prefactor(g1: GaussianShell3D, g2: GaussianShell3D) -> jax.Array:
|
|
73
|
+
"""The scalar prefactor K = exp(-mu |A-B|^2), mu = a*b/(a+b), that the
|
|
74
|
+
product of two Gaussians picks up (everything else about the product
|
|
75
|
+
is folded into the recursions below)."""
|
|
76
|
+
a, b = jnp.asarray(g1.exponent), jnp.asarray(g2.exponent)
|
|
77
|
+
diff = jnp.asarray(g1.center) - jnp.asarray(g2.center)
|
|
78
|
+
mu = (a * b) / (a + b)
|
|
79
|
+
return jnp.exp(-mu * jnp.dot(diff, diff))
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Kinetic energy integrals, obtained from overlap integrals for free.
|
|
2
|
+
|
|
3
|
+
The second derivative of a Gaussian (x-B)^j exp(-b(x-B)^2) with respect
|
|
4
|
+
to its own center reduces to a combination of Gaussians of degree j-2,
|
|
5
|
+
j and j+2, which gives kinetic integrals as a fixed linear combination
|
|
6
|
+
of overlap integrals evaluated at a boosted degree:
|
|
7
|
+
|
|
8
|
+
T[i,j] = j(j-1) S[i,j-2] - 2b(2j+1) S[i,j] + 4b^2 S[i,j+2]
|
|
9
|
+
|
|
10
|
+
T here is <i| d^2/dx^2 |j>; the physical kinetic energy operator is
|
|
11
|
+
-1/2 (d^2/dx^2 + d^2/dy^2 + d^2/dz^2), so callers must negate and halve
|
|
12
|
+
the sum of the three Cartesian terms.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import jax
|
|
16
|
+
import jax.numpy as jnp
|
|
17
|
+
|
|
18
|
+
from dense_evolution.native_hf.gaussians import GaussianShell3D
|
|
19
|
+
from dense_evolution.native_hf.overlap import overlap_1d
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _kinetic_1d_from_overlap(S: jax.Array, b: jax.Array) -> jax.Array:
|
|
23
|
+
"""S has shape (n_i, n_j) with n_j >= 3 (degree-boosted). Returns the
|
|
24
|
+
kinetic matrix of shape (n_i, n_j - 2)."""
|
|
25
|
+
n_j_out = S.shape[1] - 2
|
|
26
|
+
j = jnp.arange(n_j_out)[None, :]
|
|
27
|
+
|
|
28
|
+
term_down = jnp.pad(j[:, 2:] * (j[:, 2:] - 1) * S[:, :-4], ((0, 0), (2, 0))) if n_j_out > 2 else jnp.zeros((S.shape[0], n_j_out))
|
|
29
|
+
term_same = -2.0 * b * (2.0 * j + 1.0) * S[:, :-2]
|
|
30
|
+
term_up = 4.0 * b * b * S[:, 2:]
|
|
31
|
+
|
|
32
|
+
return term_down + term_same + term_up
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@jax.jit
|
|
36
|
+
def kinetic_3d(g1: GaussianShell3D, g2: GaussianShell3D) -> jax.Array:
|
|
37
|
+
"""<g1| d^2/dx^2 + d^2/dy^2 + d^2/dz^2 |g2>, shape matching overlap_3d.
|
|
38
|
+
|
|
39
|
+
Multiply by -0.5 to get the physical kinetic-energy matrix elements.
|
|
40
|
+
"""
|
|
41
|
+
b = jnp.asarray(g2.exponent)
|
|
42
|
+
g2_boosted = GaussianShell3D(degree=g2.degree + 2, exponent=g2.exponent, center=g2.center)
|
|
43
|
+
|
|
44
|
+
S = [overlap_1d(g1.component(d), g2_boosted.component(d)) for d in range(3)]
|
|
45
|
+
T = [_kinetic_1d_from_overlap(S[d], b) for d in range(3)]
|
|
46
|
+
S_trim = [s[:, :-2] for s in S]
|
|
47
|
+
|
|
48
|
+
term_x = jnp.einsum("ad,be,cf->abcdef", T[0], S_trim[1], S_trim[2])
|
|
49
|
+
term_y = jnp.einsum("ad,be,cf->abcdef", S_trim[0], T[1], S_trim[2])
|
|
50
|
+
term_z = jnp.einsum("ad,be,cf->abcdef", S_trim[0], S_trim[1], T[2])
|
|
51
|
+
|
|
52
|
+
return term_x + term_y + term_z
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""libcint bridge for native_hf's one- and two-electron integrals.
|
|
2
|
+
|
|
3
|
+
native_hf's own build_repulsion_tensor (assembly.py) computes the ERI
|
|
4
|
+
tensor via JAX-jitted Obara-Saika recursions -- correct, differentiable,
|
|
5
|
+
but pays a JIT-compilation tax on mixed-angular-momentum bases (~532s on
|
|
6
|
+
Ne/6-31G* even after the primitive-count-padding fix), and its one-electron
|
|
7
|
+
assembly ran out of memory during XLA compilation for a 4-heavy-atom
|
|
8
|
+
molecule at 6-31G* on a Kaggle CPU kernel. libcint (Sun, J. Comput. Chem.
|
|
9
|
+
36, 1664 (2015), BSD-2) computes the identical integrals with C kernels
|
|
10
|
+
compiled once, ahead of time.
|
|
11
|
+
|
|
12
|
+
libcint is linked statically, together with csrc/cint_driver.c, into one
|
|
13
|
+
shared library shipped inside the platform wheels of dense-evolution
|
|
14
|
+
(dense_evolution/native_hf/_libcint/, built by
|
|
15
|
+
.github/scripts/build-libcint.sh in .github/workflows/libcint.yml) and
|
|
16
|
+
called through ctypes, with the atm/bas/env arrays built here from
|
|
17
|
+
native_hf's own ContractedShell list. The driver loops over shell blocks
|
|
18
|
+
in C (8-fold ERI symmetry, int2e_optimizer) and writes every integral
|
|
19
|
+
already rescaled and in native_hf's AO order. A source install has no
|
|
20
|
+
bundled library: run build-libcint.sh and point DENSE_EVOLUTION_LIBCINT at
|
|
21
|
+
the resulting file.
|
|
22
|
+
|
|
23
|
+
Shells are passed to libcint in native_hf's own order, so AO blocks line
|
|
24
|
+
up shell by shell. Two convention differences remain inside each shell,
|
|
25
|
+
confirmed empirically on Ne/6-31G*:
|
|
26
|
+
- Cartesian component order: libcint orders px,py,pz and
|
|
27
|
+
xx,xy,xz,yy,yz,zz; native_hf's cartesian_powers orders px,pz,py and
|
|
28
|
+
xx,xz,xy,zz,yz,yy.
|
|
29
|
+
- Normalization: libcint's raw Cartesian d components are not unit
|
|
30
|
+
self-overlap (xx/yy/zz vs. xy/xz/yz differ by exactly a factor of 3).
|
|
31
|
+
native_hf's primitive-normalized coefficients differ from libcint's
|
|
32
|
+
radial convention by a per-shell constant too. Both are removed by a
|
|
33
|
+
per-AO rescale computed from libcint's own overlap diagonal at call
|
|
34
|
+
time, exact for whatever exponent/element/degree is in play.
|
|
35
|
+
Degrees above 2 raise NotImplementedError (native_hf itself caps at 2).
|
|
36
|
+
"""
|
|
37
|
+
import ctypes
|
|
38
|
+
import os
|
|
39
|
+
import sys
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
import numpy as np
|
|
43
|
+
|
|
44
|
+
from dense_evolution.native_hf.basis import build_molecule_shells
|
|
45
|
+
from dense_evolution.native_hf.cartesian import cartesian_powers
|
|
46
|
+
|
|
47
|
+
_LIBCINT_CARTESIAN_ORDER = {
|
|
48
|
+
0: [(0, 0, 0)],
|
|
49
|
+
1: [(1, 0, 0), (0, 1, 0), (0, 0, 1)],
|
|
50
|
+
2: [(2, 0, 0), (1, 1, 0), (1, 0, 1), (0, 2, 0), (0, 1, 1), (0, 0, 2)],
|
|
51
|
+
}
|
|
52
|
+
_LIB_NAMES = {"win32": "libdecint.dll", "darwin": "libdecint.dylib"}
|
|
53
|
+
_PTR_ENV_START = 20
|
|
54
|
+
_POINT_NUC = 1
|
|
55
|
+
_KINDS = {"ovlp": 0, "kin": 1, "nuc": 2}
|
|
56
|
+
_lib = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_libcint() -> ctypes.CDLL:
|
|
60
|
+
"""The bundled libcint + driver shared library (or DENSE_EVOLUTION_LIBCINT's),
|
|
61
|
+
loaded once; ImportError if neither exists."""
|
|
62
|
+
global _lib
|
|
63
|
+
if _lib is not None:
|
|
64
|
+
return _lib
|
|
65
|
+
path = os.environ.get("DENSE_EVOLUTION_LIBCINT") or str(
|
|
66
|
+
Path(__file__).with_name("_libcint") / _LIB_NAMES.get(sys.platform, "libdecint.so")
|
|
67
|
+
)
|
|
68
|
+
if not os.path.isfile(path):
|
|
69
|
+
raise ImportError(
|
|
70
|
+
f"native_hf.libcint_bridge needs the bundled libcint library, expected at {path}. "
|
|
71
|
+
"It ships inside the dense-evolution wheels for Windows, macOS and Linux "
|
|
72
|
+
"(pip install dense-evolution); for a source install, run "
|
|
73
|
+
".github/scripts/build-libcint.sh and set DENSE_EVOLUTION_LIBCINT to the library file."
|
|
74
|
+
)
|
|
75
|
+
lib = ctypes.CDLL(path)
|
|
76
|
+
tail = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
|
|
77
|
+
lib.de_int1e.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int] + [ctypes.c_void_p] * 3 + tail
|
|
78
|
+
lib.de_int1e.restype = None
|
|
79
|
+
lib.de_int2e.argtypes = [ctypes.c_void_p, ctypes.c_int] + [ctypes.c_void_p] * 3 + tail
|
|
80
|
+
lib.de_int2e.restype = None
|
|
81
|
+
_lib = lib
|
|
82
|
+
return lib
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _permutation_libcint_to_native_hf(degree: int) -> np.ndarray:
|
|
86
|
+
"""perm such that native_hf_ordered[i] == libcint_ordered[perm[i]]."""
|
|
87
|
+
if degree not in _LIBCINT_CARTESIAN_ORDER:
|
|
88
|
+
raise NotImplementedError(
|
|
89
|
+
f"libcint_bridge only has a verified AO-order mapping for degree <= 2; "
|
|
90
|
+
f"degree={degree} would need the same empirical check first, not an "
|
|
91
|
+
f"assumed extension of the pattern."
|
|
92
|
+
)
|
|
93
|
+
native_order = [tuple(int(x) for x in p) for p in cartesian_powers(degree)]
|
|
94
|
+
libcint_order = _LIBCINT_CARTESIAN_ORDER[degree]
|
|
95
|
+
return np.array([libcint_order.index(p) for p in native_order])
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class _Cint:
|
|
99
|
+
"""atm/bas/env arrays for one molecule plus per-shell AO offsets, the
|
|
100
|
+
native_hf AO permutation, evaluated by the C driver."""
|
|
101
|
+
|
|
102
|
+
def __init__(self, atomic_numbers: list, geometry_bohr: np.ndarray, basis_name: str):
|
|
103
|
+
self.lib = load_libcint()
|
|
104
|
+
shells = build_molecule_shells(atomic_numbers, geometry_bohr, basis_name)
|
|
105
|
+
env = [0.0] * _PTR_ENV_START
|
|
106
|
+
atm, bas = [], []
|
|
107
|
+
for z, r in zip(atomic_numbers, np.asarray(geometry_bohr, dtype=float)):
|
|
108
|
+
atm.append([int(z), len(env), _POINT_NUC, len(env) + 3, 0, 0])
|
|
109
|
+
env.extend([r[0], r[1], r[2], 0.0])
|
|
110
|
+
for s in shells:
|
|
111
|
+
e = np.asarray(s.exponents, dtype=float)
|
|
112
|
+
c = np.asarray(s.coefficients, dtype=float)
|
|
113
|
+
bas.append([s.atom_index, s.degree, e.shape[0], 1, 0, len(env), len(env) + e.shape[0], 0])
|
|
114
|
+
env.extend(e)
|
|
115
|
+
env.extend(c)
|
|
116
|
+
self.atm = np.ascontiguousarray(atm, dtype=np.int32)
|
|
117
|
+
self.bas = np.ascontiguousarray(bas, dtype=np.int32)
|
|
118
|
+
self.env = np.ascontiguousarray(env, dtype=np.float64)
|
|
119
|
+
self.sizes = [len(cartesian_powers(s.degree)) for s in shells]
|
|
120
|
+
self.offsets = np.concatenate([[0], np.cumsum(self.sizes)]).astype(int)
|
|
121
|
+
self.n = int(self.offsets[-1])
|
|
122
|
+
self.perm = np.concatenate(
|
|
123
|
+
[o + _permutation_libcint_to_native_hf(s.degree) for o, s in zip(self.offsets, shells)]
|
|
124
|
+
)
|
|
125
|
+
self.ao_loc = np.ascontiguousarray(self.offsets, dtype=np.int32)
|
|
126
|
+
self.pos = np.ascontiguousarray(np.argsort(self.perm), dtype=np.int32)
|
|
127
|
+
|
|
128
|
+
def _tail(self):
|
|
129
|
+
return (self.atm.ctypes.data, self.atm.shape[0], self.bas.ctypes.data, self.bas.shape[0], self.env.ctypes.data)
|
|
130
|
+
|
|
131
|
+
def one(self, kind: str, r: np.ndarray, pos: np.ndarray) -> np.ndarray:
|
|
132
|
+
out = np.empty((self.n, self.n))
|
|
133
|
+
r = np.ascontiguousarray(r, dtype=np.float64)
|
|
134
|
+
self.lib.de_int1e(_KINDS[kind], out.ctypes.data, self.n, self.ao_loc.ctypes.data, r.ctypes.data, pos.ctypes.data, *self._tail())
|
|
135
|
+
return out
|
|
136
|
+
|
|
137
|
+
def rescale(self) -> np.ndarray:
|
|
138
|
+
S = self.one("ovlp", np.ones(self.n), np.arange(self.n, dtype=np.int32))
|
|
139
|
+
return 1.0 / np.sqrt(np.diag(S))
|
|
140
|
+
|
|
141
|
+
def two(self, r: np.ndarray) -> np.ndarray:
|
|
142
|
+
out = np.empty((self.n,) * 4)
|
|
143
|
+
self.lib.de_int2e(out.ctypes.data, self.n, self.ao_loc.ctypes.data, r.ctypes.data, self.pos.ctypes.data, *self._tail())
|
|
144
|
+
return out
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def build_overlap_and_core_hamiltonian_libcint(
|
|
148
|
+
atomic_numbers: list, geometry_bohr: np.ndarray, basis_name: str
|
|
149
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
150
|
+
"""Same contract as assembly.build_overlap_matrix + build_core_hamiltonian
|
|
151
|
+
combined, computed via libcint. Returns (S, H_core) in native_hf's own AO
|
|
152
|
+
ordering/normalization, ready for scf.run_scf alongside
|
|
153
|
+
build_repulsion_tensor_libcint's output."""
|
|
154
|
+
c = _Cint(atomic_numbers, geometry_bohr, basis_name)
|
|
155
|
+
r = c.rescale()
|
|
156
|
+
return c.one("ovlp", r, c.pos), c.one("kin", r, c.pos) + c.one("nuc", r, c.pos)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def build_repulsion_tensor_libcint(atomic_numbers: list, geometry_bohr: np.ndarray, basis_name: str) -> np.ndarray:
|
|
160
|
+
"""Same contract as assembly.build_repulsion_tensor, computed via libcint
|
|
161
|
+
with 8-fold permutational symmetry; takes (atomic_numbers, geometry_bohr,
|
|
162
|
+
basis_name) rather than a shells list.
|
|
163
|
+
|
|
164
|
+
geometry_bohr: shape (n_atoms, 3), atomic units, same convention as
|
|
165
|
+
build_molecule_shells."""
|
|
166
|
+
c = _Cint(atomic_numbers, geometry_bohr, basis_name)
|
|
167
|
+
return c.two(c.rescale())
|