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.
Files changed (165) hide show
  1. dashboard_core/__init__.py +115 -0
  2. dashboard_core/_gate_tables.py +30 -0
  3. dashboard_core/band_structure.py +71 -0
  4. dashboard_core/circuit_builder_component.py +232 -0
  5. dashboard_core/circuit_diagram.py +216 -0
  6. dashboard_core/crypto_protocols.py +77 -0
  7. dashboard_core/engine.py +326 -0
  8. dashboard_core/graphical_builder.py +114 -0
  9. dashboard_core/hamiltonians.py +593 -0
  10. dashboard_core/mass_decomposition_tool.py +47 -0
  11. dashboard_core/mitigation.py +343 -0
  12. dashboard_core/native_hf_diagnostics.py +62 -0
  13. dashboard_core/noise_tools.py +125 -0
  14. dashboard_core/qasm_library.py +233 -0
  15. dashboard_core/qmmm.py +16 -0
  16. dashboard_core/rag_tool.py +45 -0
  17. dashboard_core/state_visuals.py +288 -0
  18. dashboard_core/system_limits.py +60 -0
  19. dashboard_core/vector_healing.py +102 -0
  20. dashboard_core/visuals.py +158 -0
  21. dashboard_core/vqe.py +533 -0
  22. dashboard_core/wormhole.py +580 -0
  23. dense_evolution/__init__.py +114 -0
  24. dense_evolution/autodiff.py +10 -0
  25. dense_evolution/backends/__init__.py +5 -0
  26. dense_evolution/backends/chunk/__init__.py +37 -0
  27. dense_evolution/backends/chunk/_engine_imports.py +57 -0
  28. dense_evolution/backends/chunk/circuit_chunker.py +55 -0
  29. dense_evolution/backends/chunk/core.py +432 -0
  30. dense_evolution/backends/chunk/disk_overflow.py +232 -0
  31. dense_evolution/backends/chunk/geometry.py +95 -0
  32. dense_evolution/backends/chunk/guard.py +190 -0
  33. dense_evolution/backends/chunk/kernels.py +531 -0
  34. dense_evolution/backends/mps.py +1569 -0
  35. dense_evolution/backends/statevector.py +616 -0
  36. dense_evolution/chunk.py +25 -0
  37. dense_evolution/circuits/__init__.py +20 -0
  38. dense_evolution/circuits/compiler.py +488 -0
  39. dense_evolution/circuits/diagram.py +94 -0
  40. dense_evolution/circuits/gates.py +91 -0
  41. dense_evolution/circuits/parser.py +632 -0
  42. dense_evolution/circuits/qft.py +66 -0
  43. dense_evolution/circuits/random_circuit.py +85 -0
  44. dense_evolution/circuits/registry.py +74 -0
  45. dense_evolution/circuits/topology.py +79 -0
  46. dense_evolution/circuits/trotter.py +265 -0
  47. dense_evolution/circuits/uccsd.py +275 -0
  48. dense_evolution/cli.py +199 -0
  49. dense_evolution/compiler.py +9 -0
  50. dense_evolution/config.py +49 -0
  51. dense_evolution/drawing.py +10 -0
  52. dense_evolution/entropy.py +9 -0
  53. dense_evolution/fermions.py +9 -0
  54. dense_evolution/gates.py +9 -0
  55. dense_evolution/harrison_tb.py +16 -0
  56. dense_evolution/healing.py +18 -0
  57. dense_evolution/interop/__init__.py +18 -0
  58. dense_evolution/interop/qiskit_pennylane.py +406 -0
  59. dense_evolution/measurement.py +10 -0
  60. dense_evolution/mitigation/__init__.py +54 -0
  61. dense_evolution/mitigation/healing.py +215 -0
  62. dense_evolution/mitigation/kl_divergence.py +93 -0
  63. dense_evolution/mitigation/magic_entropy.py +163 -0
  64. dense_evolution/mitigation/magic_entropy_shadows.py +262 -0
  65. dense_evolution/mitigation/renyi.py +168 -0
  66. dense_evolution/mitigation/stabilizer_renyi_entropy.py +103 -0
  67. dense_evolution/mitigation/zne.py +990 -0
  68. dense_evolution/mps.py +9 -0
  69. dense_evolution/native_hf/__init__.py +26 -0
  70. dense_evolution/native_hf/_libcint/LICENSE-libcint +10 -0
  71. dense_evolution/native_hf/_libcint/libdecint.dll +0 -0
  72. dense_evolution/native_hf/assembly.py +304 -0
  73. dense_evolution/native_hf/basis.py +117 -0
  74. dense_evolution/native_hf/boys.py +35 -0
  75. dense_evolution/native_hf/bridge.py +112 -0
  76. dense_evolution/native_hf/cartesian.py +64 -0
  77. dense_evolution/native_hf/coulomb.py +196 -0
  78. dense_evolution/native_hf/differentiable.py +53 -0
  79. dense_evolution/native_hf/gaussians.py +79 -0
  80. dense_evolution/native_hf/kinetic.py +52 -0
  81. dense_evolution/native_hf/libcint_bridge.py +167 -0
  82. dense_evolution/native_hf/overlap.py +91 -0
  83. dense_evolution/native_hf/scf.py +404 -0
  84. dense_evolution/noise/__init__.py +79 -0
  85. dense_evolution/noise/coherent_attack.py +264 -0
  86. dense_evolution/noise/cosmic_ray.py +61 -0
  87. dense_evolution/noise/density_matrix_channels.py +78 -0
  88. dense_evolution/noise/differentiable.py +66 -0
  89. dense_evolution/noise/kraus/__init__.py +6 -0
  90. dense_evolution/noise/kraus/amplitude_damping.py +47 -0
  91. dense_evolution/noise/kraus/bitflip.py +22 -0
  92. dense_evolution/noise/kraus/combined.py +16 -0
  93. dense_evolution/noise/kraus/depolarizing.py +47 -0
  94. dense_evolution/noise/kraus/ideal.py +10 -0
  95. dense_evolution/noise/kraus/phaseflip.py +21 -0
  96. dense_evolution/noise/kraus_channels.py +285 -0
  97. dense_evolution/noise/oscillating.py +32 -0
  98. dense_evolution/noise/pink.py +80 -0
  99. dense_evolution/observables.py +11 -0
  100. dense_evolution/parser.py +9 -0
  101. dense_evolution/physics/__init__.py +27 -0
  102. dense_evolution/physics/entropy.py +161 -0
  103. dense_evolution/physics/fermions.py +322 -0
  104. dense_evolution/physics/observables.py +523 -0
  105. dense_evolution/physics/qec.py +1113 -0
  106. dense_evolution/physics/spectral.py +143 -0
  107. dense_evolution/physics/states.py +43 -0
  108. dense_evolution/protocols/__init__.py +27 -0
  109. dense_evolution/protocols/bb84.py +133 -0
  110. dense_evolution/protocols/di_qkd_ghz.py +199 -0
  111. dense_evolution/protocols/dicka_protocol2.py +124 -0
  112. dense_evolution/qec.py +20 -0
  113. dense_evolution/qft.py +9 -0
  114. dense_evolution/qmmm/__init__.py +13 -0
  115. dense_evolution/qmmm/ase_bridge.py +97 -0
  116. dense_evolution/qmmm/forces.py +388 -0
  117. dense_evolution/qmmm/propagation.py +80 -0
  118. dense_evolution/qmmm/region.py +137 -0
  119. dense_evolution/random_circuit.py +15 -0
  120. dense_evolution/registry.py +9 -0
  121. dense_evolution/simulator.py +10 -0
  122. dense_evolution/solvers/__init__.py +19 -0
  123. dense_evolution/solvers/autodiff.py +169 -0
  124. dense_evolution/solvers/harrison_tb.py +189 -0
  125. dense_evolution/solvers/vhd_tb.py +187 -0
  126. dense_evolution/states.py +9 -0
  127. dense_evolution/topology.py +9 -0
  128. dense_evolution/trotter.py +9 -0
  129. dense_evolution/utils/__init__.py +13 -0
  130. dense_evolution/utils/drawing.py +101 -0
  131. dense_evolution/utils/mass_decomposition.py +246 -0
  132. dense_evolution/utils/measurement.py +94 -0
  133. dense_evolution/vhd_tb.py +16 -0
  134. dense_evolution-8.3.0.dist-info/METADATA +366 -0
  135. dense_evolution-8.3.0.dist-info/RECORD +165 -0
  136. dense_evolution-8.3.0.dist-info/WHEEL +5 -0
  137. dense_evolution-8.3.0.dist-info/entry_points.txt +2 -0
  138. dense_evolution-8.3.0.dist-info/licenses/license.md +58 -0
  139. dense_evolution-8.3.0.dist-info/top_level.txt +5 -0
  140. ia_utils/__init__.py +0 -0
  141. ia_utils/adversarial_vector_attack.py +196 -0
  142. ia_utils/rag.py +288 -0
  143. ia_utils/vector_healing.py +399 -0
  144. local_site/__init__.py +0 -0
  145. local_site/app/__init__.py +0 -0
  146. local_site/app/server.py +1009 -0
  147. mcp_server/__init__.py +0 -0
  148. mcp_server/client.py +324 -0
  149. mcp_server/config.py +32 -0
  150. mcp_server/models.py +347 -0
  151. mcp_server/molecules.py +71 -0
  152. mcp_server/server.py +119 -0
  153. mcp_server/tools/__init__.py +0 -0
  154. mcp_server/tools/chemistry_tools.py +225 -0
  155. mcp_server/tools/circuit_tools.py +83 -0
  156. mcp_server/tools/crypto_tools.py +66 -0
  157. mcp_server/tools/mitigation_tools.py +81 -0
  158. mcp_server/tools/noise_tools.py +60 -0
  159. mcp_server/tools/retrieval_tools.py +44 -0
  160. mcp_server/tools/system_tools.py +149 -0
  161. mcp_server/tools/wormhole_tools.py +142 -0
  162. mcp_server/utils/__init__.py +0 -0
  163. mcp_server/utils/cache.py +55 -0
  164. mcp_server/utils/images.py +67 -0
  165. 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())