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,91 @@
1
+ """Overlap integrals between Gaussian shells via the Obara-Saika recursion.
2
+
3
+ For two 1D Gaussians centered at A, B with exponents a, b, let
4
+ p = a+b and P = (aA+bB)/p be the center of their product (Gaussian
5
+ product theorem). The overlap S[i,j] = <(x-A)^i exp(-a(x-A)^2) |
6
+ (x-B)^j exp(-b(x-B)^2)> obeys two recursions:
7
+
8
+ Vertical (build up the first index from the base case S[0,0]):
9
+ S[i,0] = (P-A) S[i-1,0] + (i-1)/(2p) S[i-2,0]
10
+
11
+ Horizontal (build up the second index by shifting angular momentum
12
+ from center A to center B, exact for any two centers -- this is why
13
+ it needs no exponent-dependent term):
14
+ S[i,j] = (A-B) S[i,j-1] + S[i+1,j-1]
15
+
16
+ Both are linear recursions in one index with a 2-term memory, so each
17
+ maps directly onto jax.lax.scan: the whole angular-momentum ladder for
18
+ a shell pair compiles to one XLA loop instead of a Python for-loop.
19
+ """
20
+
21
+ import functools
22
+
23
+ import jax
24
+ import jax.numpy as jnp
25
+
26
+ from dense_evolution.native_hf.gaussians import (
27
+ GaussianShell1D,
28
+ GaussianShell3D,
29
+ product_center,
30
+ product_prefactor,
31
+ )
32
+
33
+
34
+ def _base_overlap_1d(g1: GaussianShell1D, g2: GaussianShell1D) -> jax.Array:
35
+ p = g1.exponent + g2.exponent
36
+ mu = (g1.exponent * g2.exponent) / p
37
+ K = jnp.exp(-mu * jnp.square(g1.center - g2.center))
38
+ return jnp.sqrt(jnp.pi / p) * K
39
+
40
+
41
+ def _vertical_step(p_minus_a, inv_two_p, carry, i):
42
+ s_prev, s_prev2 = carry
43
+ s_next = p_minus_a * s_prev + (i - 1) * inv_two_p * s_prev2
44
+ return (s_next, s_prev), s_next
45
+
46
+
47
+ def _build_first_index(s00, g1: GaussianShell1D, g2: GaussianShell1D, n_extra: int) -> jax.Array:
48
+ """Returns S[i, 0] for i = 0..n_extra."""
49
+ if n_extra == 0:
50
+ return s00[None]
51
+ p = g1.exponent + g2.exponent
52
+ P = (g1.exponent * g1.center + g2.exponent * g2.center) / p
53
+ step = functools.partial(_vertical_step, P - g1.center, 1.0 / (2.0 * p))
54
+ _, rest = jax.lax.scan(step, (s00, jnp.zeros_like(s00)), jnp.arange(1, n_extra + 1))
55
+ return jnp.concatenate([s00[None], rest])
56
+
57
+
58
+ def _horizontal_step(a_minus_b, column, _):
59
+ shifted = jnp.roll(column, shift=-1)
60
+ new_column = a_minus_b * column + shifted
61
+ return new_column, new_column
62
+
63
+
64
+ def _build_second_index(first_col: jax.Array, g1: GaussianShell1D, g2: GaussianShell1D) -> jax.Array:
65
+ """first_col holds S[:,0]. Returns S[:, 0..g2.degree] (rows beyond
66
+ validity are garbage and get sliced away by the caller)."""
67
+ if g2.degree == 0:
68
+ return first_col[:, None]
69
+ step = functools.partial(_horizontal_step, g1.center - g2.center)
70
+ _, rest = jax.lax.scan(step, first_col, jnp.arange(1, g2.degree + 1))
71
+ return jnp.concatenate([first_col[:, None], jnp.moveaxis(rest, 0, -1)], axis=-1)
72
+
73
+
74
+ def overlap_1d(g1: GaussianShell1D, g2: GaussianShell1D) -> jax.Array:
75
+ """S[i,j] for 0<=i<=g1.degree, 0<=j<=g2.degree. Shape (g1.degree+1, g2.degree+1)."""
76
+ s00 = _base_overlap_1d(g1, g2)
77
+ first_col = _build_first_index(s00, g1, g2, g1.degree + g2.degree)
78
+ full = _build_second_index(first_col, g1, g2)
79
+ return full[: g1.degree + 1, :]
80
+
81
+
82
+ @jax.jit
83
+ def overlap_3d(g1: GaussianShell3D, g2: GaussianShell3D) -> jax.Array:
84
+ """S[ix,iy,iz,jx,jy,jz] for the full Cartesian shell pair.
85
+
86
+ Shape: (L1+1,L1+1,L1+1, L2+1,L2+1,L2+1) where L1, L2 are the shell
87
+ degrees (unphysical (i,j,k) combinations with i+j+k > degree are
88
+ simply never read by the caller).
89
+ """
90
+ axes = [overlap_1d(g1.component(d), g2.component(d)) for d in range(3)]
91
+ return jnp.einsum("ad,be,cf->abcdef", *axes)
@@ -0,0 +1,404 @@
1
+ """Restricted Hartree-Fock self-consistent field loop (Roothaan-Hall).
2
+
3
+ Standard textbook algorithm (e.g. Szabo & Ostlund, "Modern Quantum
4
+ Chemistry", ch. 3): orthogonalize the AO basis via S^(-1/2), build the
5
+ Fock matrix F = H_core + 2J - K from the current density, diagonalize
6
+ in the orthogonal basis, form a new density, repeat to convergence.
7
+ This part is genuinely simple compared to the integral evaluation and
8
+ doesn't need vectorizing -- a closed-shell molecule's SCF loop is a few
9
+ dozen matrix multiplies on an N x N matrix where N is a few tens at
10
+ most for STO-3G, nowhere near where PennyLane's implementation loses
11
+ its time (which is entirely in building H_core/repulsion tensor, done
12
+ once in assembly.py, not in this loop).
13
+
14
+ BUG FOUND (Si2, minimal 4-electron/4-orbital active space, R=2.184 A):
15
+ plain (undamped) density substitution never converged for this system
16
+ -- 100/100 iterations, still oscillating -- because two pairs of
17
+ orbitals near the active-space boundary are numerically degenerate
18
+ (HOMO-1/HOMO and LUMO/LUMO+1 each split by <1e-9 Ha), so each iteration
19
+ flips which member of a near-tied pair gets occupied, and the density
20
+ never settles. Confirmed this is a real oscillation, not just slow
21
+ convergence: three separate machines/runs of the undamped loop each
22
+ hit the iteration cap at a DIFFERENT total energy (-571.63, -570.69,
23
+ -571.02 Ha), all physically meaningless artifacts of whatever step the
24
+ loop happened to be on. First fixed with plain linear density damping
25
+ (P_next = alpha*P_new + (1-alpha)*P_old) -- textbook remedy for exactly
26
+ this oscillation failure mode (Szabo & Ostlund ch. 3.4.9), verified to
27
+ converge to the SAME energy (-570.874032094871 Ha, agreeing to 10
28
+ significant figures) across alpha in {0.1, 0.2, 0.3, 0.5, 0.7).
29
+
30
+ UPGRADED to DIIS (Pulay, "Convergence acceleration of iterative
31
+ sequences: the case of SCF iteration", Chem. Phys. Lett. 73, 393
32
+ (1980); "Improved SCF convergence acceleration", J. Comput. Chem. 3,
33
+ 556 (1982)) -- the standard production-grade SCF accelerator plain
34
+ linear damping is a simplified special case of. Pulay's own error
35
+ vector, `e = X.T @ (F@P@S - S@P@F) @ X` (the Fock/density commutator in
36
+ the orthonormal basis, exactly zero at true self-consistency), is kept
37
+ across the last `diis_dim` iterations alongside the Fock matrices that
38
+ produced them; each step extrapolates a new Fock matrix as the
39
+ minimum-norm linear combination of that history (constrained to sum to
40
+ 1) instead of diagonalizing the latest F directly. Verified on the
41
+ same Si2 near-degenerate case that motivated damping in the first
42
+ place: DIIS converges in 11 iterations to -570.8740320948958 Ha, versus
43
+ 53 iterations for plain damped substitution alone (diis_dim=0,
44
+ alpha=0.5) to reach the same energy, -570.8740320948963 Ha -- agreeing
45
+ to 12 significant figures, the same true self-consistent solution
46
+ reached ~4.8x faster, not a different answer.
47
+
48
+ If the DIIS linear system is ever singular (a degenerate/duplicated
49
+ error-vector history), that step falls back to the plain, unextrapolated
50
+ Fock matrix rather than raising -- a transient fallback, not silent
51
+ wrong physics, since the next iteration's fresh error vector rebuilds a
52
+ usable history.
53
+
54
+ Convergence now requires BOTH the density AND the energy to stop
55
+ changing (`|P_new - P| < convergence_tol` AND `|E_new - E_old| <
56
+ energy_tol`) rather than density alone -- density convergence can
57
+ occasionally plateau one step before energy does (or vice versa) for
58
+ a system with several nearly-degenerate iterations near the end of the
59
+ run; requiring both is strictly more conservative than either alone
60
+ and costs at most a couple of extra iterations on every system tested
61
+ here.
62
+
63
+ Rewritten from NumPy to jax.numpy (the integral-building side in
64
+ assembly.py already was JAX; this loop and its np.array() outputs were
65
+ the only remaining barrier to an end-to-end JAX-traced pipeline). The
66
+ iteration itself uses jax.lax.while_loop for its data-dependent
67
+ convergence check, same as the original Python for/break -- note that
68
+ reverse-mode autodiff (jax.grad) does not work through while_loop, by
69
+ JAX's own design (the number of iterations isn't known ahead of time,
70
+ which reverse-mode differentiation needs). Making this loop's OUTPUT
71
+ differentiable is a separate, deliberately deferred step: the correct
72
+ approach is implicit differentiation at the fixed point (the custom
73
+ gradient rule only needs the converged F/P, not a replay of every
74
+ iteration), not unrolling this loop and backpropagating through it.
75
+
76
+ DIIS history is now a fixed-size (diis_dim, n, n) ring buffer instead
77
+ of a growing/shrinking Python list -- jax.lax.while_loop requires its
78
+ carried state to have constant shape across iterations. Each step
79
+ rolls the buffer and writes the newest Fock/error matrix into the last
80
+ slot; a boolean mask (derived from how many iterations have actually
81
+ run) excludes the not-yet-filled slots from the DIIS linear system
82
+ instead of the original's list-length check. The original's
83
+ try/except LinAlgError fallback becomes a jnp.isfinite check on the
84
+ solved coefficients (a singular solve produces NaN/Inf under JAX
85
+ rather than raising), selecting the single latest Fock matrix exactly
86
+ as the original's except-branch did.
87
+ """
88
+
89
+ import dataclasses
90
+ import functools
91
+
92
+ import jax
93
+ import numpy as np
94
+ import jax.numpy as jnp
95
+
96
+ from dense_evolution.config import ensure_x64
97
+
98
+ _DIIS_DIM = 8
99
+
100
+
101
+ @dataclasses.dataclass
102
+ class HFResult:
103
+ converged: bool
104
+ n_iterations: int
105
+ electronic_energy: float
106
+ nuclear_repulsion_energy: float
107
+ total_energy: float
108
+ orbital_energies: jax.Array
109
+ orbital_coefficients: jax.Array # C, shape (n_basis, n_basis)
110
+ density_matrix: jax.Array
111
+ energy_history: jax.Array # shape (max_iterations,), NaN past n_iterations
112
+
113
+
114
+ def nuclear_repulsion_energy(nuclear_charges: list[float], nuclear_positions: jax.Array) -> jax.Array:
115
+ positions = jnp.asarray(nuclear_positions)
116
+ energy = 0.0
117
+ n = len(nuclear_charges)
118
+ for i in range(n):
119
+ for j in range(i + 1, n):
120
+ r = jnp.linalg.norm(positions[i] - positions[j])
121
+ energy = energy + nuclear_charges[i] * nuclear_charges[j] / r
122
+ return energy
123
+
124
+
125
+ def _orthogonalizer(S: jax.Array) -> jax.Array:
126
+ w, v = jnp.linalg.eigh(S)
127
+ return v @ jnp.diag(1.0 / jnp.sqrt(w)) @ v.T
128
+
129
+
130
+ def _density_from_coefficients(C: jax.Array, n_occupied_pairs: int) -> jax.Array:
131
+ C_occ = C[:, :n_occupied_pairs]
132
+ return C_occ @ C_occ.T
133
+
134
+
135
+ def _diis_error(F: jax.Array, P: jax.Array, S: jax.Array, X: jax.Array) -> jax.Array:
136
+ """Pulay's DIIS error vector, `X.T @ (F@P@S - S@P@F) @ X` -- the
137
+ Fock/density commutator (zero at true self-consistency, since F and
138
+ P then commute), transformed into the same orthonormal basis the
139
+ Fock matrix itself is diagonalized in. F, P, S are all symmetric, so
140
+ this reduces to `Y - Y.T` for `Y = X.T @ F @ P @ S @ X` -- computed
141
+ that way to avoid forming two separate FPS/SPF products."""
142
+ Y = X.T @ F @ P @ S @ X
143
+ return Y - Y.T
144
+
145
+
146
+ def _diis_extrapolate(fock_history: jax.Array, error_history: jax.Array, history_count: jax.Array, diis_dim: int) -> jax.Array:
147
+ """fock_history/error_history: (diis_dim, n, n), newest entry always
148
+ in the last slot (see run_scf's roll-and-append). `valid` marks
149
+ which of the diis_dim slots hold a real (not yet overwritten, not a
150
+ zero-initialized placeholder) entry -- the last `history_count` of
151
+ them. Invalid slots are pinned to c_i=0 by giving their row/column
152
+ of the augmented linear system an identity-like equation instead of
153
+ a real DIIS constraint, so they can't contribute to the solution
154
+ regardless of their (garbage/zero) content."""
155
+ dtype = fock_history.dtype
156
+ zero = jnp.zeros((), dtype=dtype)
157
+ one = jnp.ones((), dtype=dtype)
158
+ valid = jnp.arange(diis_dim) >= (diis_dim - history_count)
159
+
160
+ errs_flat = error_history.reshape(diis_dim, -1)
161
+ B_full = errs_flat @ errs_flat.T
162
+ mask2d = valid[:, None] & valid[None, :]
163
+ B = jnp.where(mask2d, B_full, zero)
164
+ B = jnp.where(jnp.eye(diis_dim, dtype=bool) & ~mask2d, one, B)
165
+
166
+ A = jnp.zeros((diis_dim + 1, diis_dim + 1), dtype=dtype)
167
+ A = A.at[:diis_dim, :diis_dim].set(B)
168
+ col = jnp.where(valid, -one, zero)
169
+ A = A.at[:diis_dim, diis_dim].set(col)
170
+ A = A.at[diis_dim, :diis_dim].set(col)
171
+ b = jnp.zeros(diis_dim + 1, dtype=dtype).at[diis_dim].set(-one)
172
+
173
+ solution = jnp.linalg.solve(A, b)
174
+ is_finite = jnp.all(jnp.isfinite(solution))
175
+ coeffs = jnp.where(valid, jnp.where(is_finite, solution[:diis_dim], zero), zero)
176
+
177
+ F_diis = jnp.tensordot(coeffs, fock_history, axes=1)
178
+ return jnp.where(is_finite, F_diis, fock_history[-1])
179
+
180
+
181
+ def _level_shift_fock(F_ao: jax.Array, C_prev: jax.Array, S: jax.Array, n_occupied_pairs: int, level_shift: float) -> jax.Array:
182
+ """Saunders & Hillier level shifting (Int. J. Quantum Chem. 7, 699
183
+ (1973)): push the virtual orbitals of the PREVIOUS iteration's MO
184
+ basis up by `level_shift` before this iteration's diagonalization,
185
+ to open a numerical gap and stop the occupied/virtual split from
186
+ flip-flopping across a near-degeneracy (see this module's own Si2
187
+ docstring above -- damping/DIIS already fix the textbook case, but a
188
+ real, harder case (a 30-atom aromatic fragment from the CASMI26
189
+ wiring kernel) still took 1114 iterations, swinging through three
190
+ wildly different intermediate energies first). Exact no-op at
191
+ level_shift=0.0: since C_prev is a full, S-orthonormal basis
192
+ (C_prev.T @ S @ C_prev = I, hence C_prev @ C_prev.T = S^{-1}), the
193
+ round-trip S @ C_prev @ (C_prev.T @ F_ao @ C_prev) @ C_prev.T @ S
194
+ reduces algebraically to exactly F_ao before any shift is added."""
195
+ F_mo_prev = C_prev.T @ F_ao @ C_prev
196
+ n = F_mo_prev.shape[0]
197
+ level_shift = jnp.asarray(level_shift, dtype=F_ao.dtype)
198
+ shift_diag = jnp.where(jnp.arange(n) >= n_occupied_pairs, level_shift, jnp.zeros((), dtype=F_ao.dtype))
199
+ F_mo_prev_shifted = F_mo_prev + jnp.diag(shift_diag)
200
+ return S @ C_prev @ F_mo_prev_shifted @ C_prev.T @ S
201
+
202
+
203
+ def run_scf(
204
+ S: jax.Array,
205
+ H_core: jax.Array,
206
+ repulsion: jax.Array,
207
+ n_electrons: int,
208
+ nuclear_charges: list[float],
209
+ nuclear_positions: jax.Array,
210
+ max_iterations: int = 200,
211
+ convergence_tol: float = 1e-10,
212
+ energy_tol: float = 1e-10,
213
+ damping: float = 0.5,
214
+ diis_dim: int = _DIIS_DIM,
215
+ level_shift: float = 0.0,
216
+ ) -> HFResult:
217
+ ensure_x64()
218
+ if n_electrons % 2 != 0:
219
+ raise ValueError("Only closed-shell (even electron count) systems are supported.")
220
+ n_occupied_pairs = n_electrons // 2
221
+ n_basis = S.shape[0]
222
+
223
+ X = _orthogonalizer(S)
224
+ orbital_energies0, C_ortho0 = jnp.linalg.eigh(X.T @ H_core @ X)
225
+ C0 = X @ C_ortho0
226
+ P0 = _density_from_coefficients(C0, n_occupied_pairs)
227
+
228
+ def _fock_and_energy(P):
229
+ J = jnp.einsum("pqrs,rs->pq", repulsion, P)
230
+ K = jnp.einsum("prqs,rs->pq", repulsion, P)
231
+ F = H_core + 2.0 * J - K
232
+ energy = jnp.sum(P * (H_core + F))
233
+ return F, energy
234
+
235
+ def cond_fun(state):
236
+ iteration, _P, _C, _orbital_energies, _energy_prev, converged, _fock_history, _error_history, _energy_history = state
237
+ return jnp.logical_and(jnp.logical_not(converged), iteration < max_iterations)
238
+
239
+ def body_fun(state):
240
+ iteration, P, C_prev, _orbital_energies, energy_prev, _converged, fock_history, error_history, energy_history = state
241
+
242
+ F, energy = _fock_and_energy(P)
243
+ error = _diis_error(F, P, S, X)
244
+
245
+ fock_history = jnp.roll(fock_history, shift=-1, axis=0).at[-1].set(F)
246
+ error_history = jnp.roll(error_history, shift=-1, axis=0).at[-1].set(error)
247
+ history_count = jnp.minimum(iteration + 1, diis_dim)
248
+
249
+ F_step = jnp.where(
250
+ history_count >= 2,
251
+ _diis_extrapolate(fock_history, error_history, history_count, diis_dim),
252
+ F,
253
+ )
254
+
255
+ F_diag = _level_shift_fock(F_step, C_prev, S, n_occupied_pairs, level_shift)
256
+ orbital_energies, C_ortho = jnp.linalg.eigh(X.T @ F_diag @ X)
257
+ C = X @ C_ortho
258
+ P_new = _density_from_coefficients(C, n_occupied_pairs)
259
+
260
+ density_converged = jnp.linalg.norm(P_new - P) < convergence_tol
261
+ energy_converged = jnp.abs(energy - energy_prev) < energy_tol
262
+ converged = jnp.logical_and(density_converged, energy_converged)
263
+
264
+ P_damped = jnp.where(history_count < 2, damping * P_new + (1.0 - damping) * P, P_new)
265
+ P_next = jnp.where(converged, P_new, P_damped)
266
+
267
+ energy_history = energy_history.at[iteration].set(energy)
268
+
269
+ return (iteration + 1, P_next, C, orbital_energies, energy, converged, fock_history, error_history, energy_history)
270
+
271
+ init_state = (
272
+ jnp.array(0),
273
+ P0,
274
+ C0,
275
+ orbital_energies0,
276
+ jnp.array(jnp.inf, dtype=H_core.dtype),
277
+ jnp.array(False),
278
+ jnp.zeros((diis_dim, n_basis, n_basis), dtype=H_core.dtype),
279
+ jnp.zeros((diis_dim, n_basis, n_basis), dtype=H_core.dtype),
280
+ jnp.full((max_iterations,), jnp.nan, dtype=H_core.dtype),
281
+ )
282
+ iteration, P, C, orbital_energies, _energy_prev, converged, _fh, _eh, energy_history = jax.lax.while_loop(cond_fun, body_fun, init_state)
283
+
284
+ _F, electronic_energy = _fock_and_energy(P)
285
+ e_nuc = nuclear_repulsion_energy(nuclear_charges, nuclear_positions)
286
+
287
+ return HFResult(
288
+ converged=bool(converged),
289
+ n_iterations=int(iteration),
290
+ electronic_energy=float(electronic_energy),
291
+ nuclear_repulsion_energy=float(e_nuc),
292
+ total_energy=float(electronic_energy) + float(e_nuc),
293
+ orbital_energies=orbital_energies,
294
+ orbital_coefficients=C,
295
+ density_matrix=P,
296
+ energy_history=energy_history,
297
+ )
298
+
299
+
300
+ def _import_dense_armor_robust_filters():
301
+ try:
302
+ from dense_armor.utility.robust_filters import hampel_filter, tukey_fences
303
+ except ImportError as exc:
304
+ raise ImportError(
305
+ "native_hf.scf.diagnose_convergence needs Dense-Armor (pip install dense-evolution[armor])"
306
+ ) from exc
307
+ return hampel_filter, tukey_fences
308
+
309
+
310
+ def diagnose_convergence(result: HFResult, radius: int = 5, n_sigmas: float = 3.0) -> dict:
311
+ """Real automatic guard on HFResult.energy_history, instead of trusting
312
+ the final `converged` flag in isolation: Dense-Armor's Hampel filter
313
+ and Tukey fences (dense_evolution.utility.robust_filters -- the sister
314
+ project's own anomaly detectors, Chauvenet/Tukey/Hampel/sigma-clipping,
315
+ validated with 0 false positives on real H2 dissociation-curve
316
+ chemistry) applied to the real per-iteration electronic energy trace.
317
+
318
+ Validated on a real non-converging case (a 28-heavy-atom CASMI26
319
+ fragment, level_shift=0.0): the broken run flags 19-24% of its 200
320
+ iterations as anomalous; the same fragment fixed with level_shift=0.5
321
+ (converges in 55 iterations) flags only ~4% -- background noise, not a
322
+ false-alarm storm. Needs the `armor` extra (pip install
323
+ dense-evolution[armor]) -- Dense-Armor is not a hard dependency of this
324
+ module."""
325
+ hampel_filter, tukey_fences = _import_dense_armor_robust_filters()
326
+ trace = np.asarray(result.energy_history[:result.n_iterations])
327
+ n = max(1, result.n_iterations)
328
+
329
+ _cleaned_h, anomalies_h = hampel_filter(trace, radius=radius, n_sigmas=n_sigmas)
330
+ _cleaned_t, anomalies_t = tukey_fences(trace, radius=len(trace))
331
+
332
+ return {
333
+ "n_iterations": result.n_iterations,
334
+ "n_anomalies_hampel": len(anomalies_h),
335
+ "n_anomalies_tukey": len(anomalies_t),
336
+ "anomaly_fraction_hampel": len(anomalies_h) / n,
337
+ "anomaly_fraction_tukey": len(anomalies_t) / n,
338
+ }
339
+
340
+
341
+ @functools.partial(jax.custom_vjp, nondiff_argnums=(3,))
342
+ def scf_electronic_energy(S: jax.Array, H_core: jax.Array, repulsion: jax.Array, n_electrons: int) -> jax.Array:
343
+ """The RHF electronic energy (run_scf's own `electronic_energy`, not
344
+ counting nuclear repulsion) as a function of S/H_core/repulsion that
345
+ IS differentiable via jax.grad -- unlike run_scf itself, whose
346
+ jax.lax.while_loop can't be traced in reverse mode.
347
+
348
+ The gradient is NOT backprop through the SCF iteration (which
349
+ wouldn't even be possible) -- it's the analytic Hartree-Fock gradient
350
+ of Pople, Krishnan, Schlegel & Binkley, Int. J. Quantum Chem. Symp.
351
+ 13, 225 (1979), eq. (21)-(22): at self-consistency, dE/dtheta equals
352
+ the derivative of `Tr[P(H_core+F(P))] - Tr[W S]` with P and the
353
+ energy-weighted density matrix W held fixed at their converged
354
+ values (an envelope-theorem/Lagrangian result -- P and W are the
355
+ stationary point and multipliers of the constrained HF variational
356
+ problem, so their own dependence on theta drops out of the total
357
+ derivative). W is built from ONLY the occupied orbitals:
358
+ `W = C_occ @ diag(2 * orbital_energies_occ) @ C_occ.T` -- the factor
359
+ of 2 is this module's own P convention (no explicit 2 in P itself,
360
+ carried instead by F = H_core + 2J - K), not part of Pople et al.'s
361
+ original spin-orbital formula. This automatically
362
+ includes the "Pulay force" terms from the atom-centered basis moving
363
+ with the nuclei (via H_core/repulsion/S's own theta-dependence),
364
+ without hand-deriving them -- jax.grad on the frozen-P expression
365
+ below does that part for free.
366
+
367
+ Verified against central finite differences on H2/STO-3G (see
368
+ tests/unit/test_native_hf_differentiable.py)."""
369
+ result = run_scf(S, H_core, repulsion, n_electrons, [], jnp.zeros((0, 3)))
370
+ return result.electronic_energy
371
+
372
+
373
+ def _scf_electronic_energy_fwd(S, H_core, repulsion, n_electrons):
374
+ result = run_scf(S, H_core, repulsion, n_electrons, [], jnp.zeros((0, 3)))
375
+ residuals = (S, H_core, repulsion, result.density_matrix, result.orbital_coefficients, result.orbital_energies)
376
+ return result.electronic_energy, residuals
377
+
378
+
379
+ def _scf_electronic_energy_bwd(n_electrons, residuals, cotangent):
380
+ S, H_core, repulsion, P, C, orbital_energies = residuals
381
+ n_occupied_pairs = n_electrons // 2
382
+ C_occ = C[:, :n_occupied_pairs]
383
+ # Lagrange multiplier for the C_occ.T @ S @ C_occ = I constraint is
384
+ # 2*diag(orbital_energies_occ), not diag(orbital_energies_occ) --
385
+ # this module's P has no explicit factor of 2 (F = H_core + 2J - K
386
+ # carries it instead), so stationarity of Tr[P(H_core+F(P))] -
387
+ # Tr[Lambda(C_occ.T S C_occ - I)] w.r.t. C_occ gives F C_occ =
388
+ # S C_occ (Lambda/2), which must match the Roothaan-Hall equation
389
+ # F C_occ = S C_occ diag(eps_occ) -- so Lambda = 2*diag(eps_occ).
390
+ # Verified: omitting this factor of 2 gave a gradient that disagreed
391
+ # with central finite differences by exactly Tr[W_undoubled dS/dx].
392
+ W = C_occ @ jnp.diag(2.0 * orbital_energies[:n_occupied_pairs]) @ C_occ.T
393
+
394
+ def lagrangian(S_, H_core_, repulsion_):
395
+ J = jnp.einsum("pqrs,rs->pq", repulsion_, P)
396
+ K = jnp.einsum("prqs,rs->pq", repulsion_, P)
397
+ F = H_core_ + 2.0 * J - K
398
+ return jnp.sum(P * (H_core_ + F)) - jnp.sum(W * S_)
399
+
400
+ dS, dH_core, drepulsion = jax.grad(lagrangian, argnums=(0, 1, 2))(S, H_core, repulsion)
401
+ return (cotangent * dS, cotangent * dH_core, cotangent * drepulsion)
402
+
403
+
404
+ scf_electronic_energy.defvjp(_scf_electronic_energy_fwd, _scf_electronic_energy_bwd)
@@ -0,0 +1,79 @@
1
+ """Every way to put noise into a Dense-Evolution simulation, in one place.
2
+
3
+ - **`NoiseModel`** (`.kraus_channels`, dispatching to one file per channel
4
+ under `.kraus`: `ideal`, `depolarizing`, `bitflip`, `phaseflip`,
5
+ `amplitude_damping`, `combined`) -- 6 stochastic single-qubit Kraus
6
+ channels, applied directly to a statevector via `apply_to_sv`.
7
+ - **`NoiseSpec`** (`.differentiable`) -- the native JAX-differentiable
8
+ representation of a noise configuration, so noise strength itself can be
9
+ a traced/differentiable value inside `circuit_to_energy_fn`.
10
+ - **Coherent adversarial noise** (`.coherent_attack`) -- a genuinely
11
+ continuous, multi-qubit coherent error channel (`apply_rz_all`) and a
12
+ JAX-differentiable search (`craft_adversarial_delta`,
13
+ `craft_adversarial_delta_constrained`) for a worst-case direction against
14
+ a stabilizer code's syndrome -- promoted from Dense-Evolution-Discovery's
15
+ Steane [[7,1,3]] investigation, including its honest negative result
16
+ (see `coherent_attack`'s module docstring).
17
+ - **Density-matrix channels** (`.density_matrix_channels`) --
18
+ `global_depolarizing_channel`, `amplitude_damping_channel`: noise applied
19
+ directly to a density matrix instead of a statevector, for density-matrix
20
+ ZNE's noise ensemble. `phaseflip_channel_exact`: the same phaseflip model
21
+ as `NoiseModel`'s `'phaseflip'` statevector Kraus channel, but computed as
22
+ its exact zero-sampling-variance limit directly on a density matrix, for
23
+ use as a "classical node" in Classically Augmented ZNE (see
24
+ `dense_evolution.mitigation.classically_augmented_zne_phaseflip`).
25
+ - **`cosmic_ray_burst_profile`** (`.cosmic_ray`) -- a real, time-dependent
26
+ noise-strength profile for a cosmic-ray-induced quasiparticle burst.
27
+ - **`oscillating_p_eff`** (`.oscillating`) -- a noise strength that
28
+ oscillates instead of scaling smoothly, for stress-testing mitigation
29
+ techniques that assume smoothness.
30
+ - **`pink_noise_p_eff`** (`.pink`) -- a noise-strength trace with a real
31
+ 1/f (pink/flicker) power spectrum, the dominant decoherence mechanism
32
+ in real superconducting qubits, via the Timmer & Koenig (1995)
33
+ spectral-synthesis algorithm.
34
+
35
+ Real device noise from a Qiskit backend's own calibration data
36
+ (`noise_model_from_qiskit_backend`) lives in `dense_evolution.interop`,
37
+ not here, since it needs a Qiskit `BackendV2` object as input rather than
38
+ a noise-specific dependency.
39
+
40
+ Everything in this package previously lived scattered across
41
+ `dense_evolution.circuits.registry` (alongside unrelated hardware-detection
42
+ code) and `dense_evolution.mitigation.zne` (alongside unrelated mitigation
43
+ techniques, which only ever cancel noise, never generate it). Both modules
44
+ re-export the relevant names from here for backward compatibility, but
45
+ `dense_evolution.noise` is the canonical import path for new code.
46
+ """
47
+ from .kraus_channels import NoiseModel
48
+ from .differentiable import NoiseSpec
49
+ from .coherent_attack import (
50
+ apply_rz_all,
51
+ x_stabilizer_leakage,
52
+ craft_adversarial_delta,
53
+ project_l2_linf,
54
+ craft_adversarial_delta_constrained,
55
+ decoder_failure_rate,
56
+ random_delta_failure_stats,
57
+ )
58
+ from .density_matrix_channels import global_depolarizing_channel, amplitude_damping_channel, phaseflip_channel_exact
59
+ from .cosmic_ray import cosmic_ray_burst_profile
60
+ from .oscillating import oscillating_p_eff
61
+ from .pink import pink_noise_p_eff
62
+
63
+ __all__ = [
64
+ "NoiseModel",
65
+ "NoiseSpec",
66
+ "apply_rz_all",
67
+ "x_stabilizer_leakage",
68
+ "craft_adversarial_delta",
69
+ "project_l2_linf",
70
+ "craft_adversarial_delta_constrained",
71
+ "decoder_failure_rate",
72
+ "random_delta_failure_stats",
73
+ "global_depolarizing_channel",
74
+ "amplitude_damping_channel",
75
+ "phaseflip_channel_exact",
76
+ "cosmic_ray_burst_profile",
77
+ "oscillating_p_eff",
78
+ "pink_noise_p_eff",
79
+ ]