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
dashboard_core/vqe.py ADDED
@@ -0,0 +1,533 @@
1
+ """
2
+ Real, dynamically-generated VQE ansatz circuits for molecular Hamiltonians
3
+ -- no fixed/hardcoded rotation angles. Every circuit this module returns
4
+ is produced by an actual classical optimization run against the real
5
+ molecular Hamiltonian for the requested geometry/mapping, not a stored
6
+ constant.
7
+
8
+ Two real ansatz families:
9
+
10
+ - **hardware-efficient** (Kandala et al., Nature 2017): a Hartree-Fock
11
+ computational-basis initial state, then n_layers of single-qubit RY
12
+ rotations followed by a linear CNOT entangling ladder. Generic --
13
+ doesn't know anything about the molecule's own fermionic structure,
14
+ just a NISQ-friendly template. Optimized entirely on dense_evolution's
15
+ own engine: the ansatz is built as real OpenQASM, parsed with
16
+ dense_evolution.QASMParser, and turned into a JAX-differentiable energy
17
+ function via dense_evolution.autodiff.circuit_to_energy_fn (the exact
18
+ pattern already used and tested in this project's own
19
+ feature/streamlit-dashboard history, dashboard_core/vqe_engine.py --
20
+ reused here without its unrelated QM/MM-telemetry code, not
21
+ reinvented). A hand-rolled Adam loop (jax.value_and_grad, jax.jit)
22
+ optimizes it -- no PennyLane device/QNode/optimizer involved at all
23
+ for this ansatz; PennyLane's only remaining role anywhere in this
24
+ module is the real Hartree-Fock + Jordan-Wigner mapping itself
25
+ (dashboard_core.hamiltonians), which isn't something worth
26
+ reimplementing (see research/quantum_chemistry_vqe_pipeline.md).
27
+ Verified to match the PennyLane-optimized version's convergence (same
28
+ order of residual error against the exact energy, same physics).
29
+ - **UCCSD** (Unitary Coupled-Cluster Singles and Doubles): the standard
30
+ chemically-motivated VQE ansatz. Built from the molecule's *real*
31
+ single/double fermionic excitation operators
32
+ (dense_evolution.find_excitations -- pure combinatorics, verified to
33
+ reproduce qml.qchem.excitations exactly), applied to the Hartree-Fock
34
+ reference via dense_evolution.single_excitation_ops/
35
+ double_excitation_ops -- exact closed-form circuits derived directly
36
+ against dense_evolution's own Jordan-Wigner mapping
37
+ (physics.fermions.majorana_pauli_terms), not PennyLane's decomposition;
38
+ see dense_evolution/circuits/uccsd.py for the derivation and the exact
39
+ scope of the closed form vs. its (also verified exact) per-term
40
+ fallback. Fewer parameters than hardware-efficient for the same
41
+ molecule (H2: 3 vs 32), and converges to the exact energy faster
42
+ because the ansatz form actually matches the physics. Also optimized
43
+ entirely on dense_evolution's own engine, same as hardware-efficient --
44
+ the obstacle was that these excitation circuits reuse the same weight
45
+ across several RY/RZ gates per excitation (single_excitation_ops' CRY
46
+ is 2 RY gates; double_excitation_ops' per-term path is up to 8 RZ
47
+ gates), whereas circuit_to_energy_fn treats every parametric gate
48
+ occurrence as an independent free parameter. Solved with an affine
49
+ parameter expansion (_uccsd_native_expansion): probing
50
+ _uccsd_native_ops at weights=0 and at each basis vector gives a fixed
51
+ (baseline, expansion_matrix) pair such that
52
+ full_gate_values = baseline + expansion_matrix @ real_weights exactly
53
+ reproduces the real per-gate values for any weights (verified by
54
+ direct probing, not derived from theory) -- composed with
55
+ circuit_to_energy_fn this is still JAX-differentiable in the small real
56
+ weight vector by ordinary chain rule, so the same hand-rolled Adam loop
57
+ optimizes it with no PennyLane device/QNode/optimizer, or PennyLane
58
+ import of any kind, involved.
59
+
60
+ The Hartree-Fock initial state (computed via qml.qchem.hf_state) only
61
+ has a simple X-gate encoding under the Jordan-Wigner mapping, so VQE
62
+ generation here is JW-only. Bravyi-Kitaev stays available for exact
63
+ ground-state-energy queries in hamiltonians.py, where the eigenvalue
64
+ spectrum is mapping-invariant.
65
+
66
+ PennyLane's only remaining role anywhere in this module is the real
67
+ Hartree-Fock + Jordan-Wigner Hamiltonian construction itself
68
+ (dashboard_core.hamiltonians), which isn't something worth
69
+ reimplementing (see research/quantum_chemistry_vqe_pipeline.md) -- the
70
+ ansatz circuits themselves (hardware-efficient and UCCSD alike) never
71
+ touch PennyLane at all.
72
+ """
73
+
74
+ import numpy as np
75
+
76
+ import dense_evolution as de
77
+
78
+ from .hamiltonians import _get_pennylane_hamiltonian, build_molecular_hamiltonian
79
+
80
+ __all__ = ['run_vqe', 'scan_hardware_efficient_energy_landscape']
81
+
82
+
83
+ def _hardware_efficient_ansatz(params, n_qubits, n_layers, hf_occupation):
84
+ import pennylane as qml
85
+ for q, occ in enumerate(hf_occupation):
86
+ if occ:
87
+ qml.PauliX(wires=q)
88
+ idx = 0
89
+ for _layer in range(n_layers):
90
+ for q in range(n_qubits):
91
+ qml.RY(params[idx], wires=q)
92
+ idx += 1
93
+ for q in range(n_qubits - 1):
94
+ qml.CNOT(wires=[q, q + 1])
95
+
96
+
97
+ def _hardware_efficient_qasm(params, n_qubits, n_layers, hf_occupation):
98
+ lines = ['OPENQASM 2.0;', 'include "qelib1.inc";', f'qreg q[{n_qubits}];', f'creg c[{n_qubits}];']
99
+ for q, occ in enumerate(hf_occupation):
100
+ if occ:
101
+ lines.append(f'x q[{q}];')
102
+ idx = 0
103
+ for _layer in range(n_layers):
104
+ for q in range(n_qubits):
105
+ lines.append(f'ry({params[idx]:.10f}) q[{q}];')
106
+ idx += 1
107
+ for q in range(n_qubits - 1):
108
+ lines.append(f'cx q[{q}],q[{q + 1}];')
109
+ lines.append('measure q -> c;')
110
+ return '\n'.join(lines)
111
+
112
+
113
+ def _uccsd_excitations(electrons, n_qubits):
114
+ """Which single/double excitations exist for this electron count --
115
+ pure combinatorics (occupied/virtual orbital pairing respecting spin
116
+ conservation), no quantum circuit involved. de.find_excitations is
117
+ dense_evolution's own reimplementation, verified to reproduce
118
+ qml.qchem.excitations exactly (see tests/unit/test_uccsd.py) --
119
+ kept native rather than calling PennyLane here for the same reason
120
+ the circuits themselves are native: this is chemistry index-finding,
121
+ not the deliberately-kept PennyLane dependency (Hartree-Fock +
122
+ Jordan-Wigner Hamiltonian construction, see module docstring)."""
123
+ return de.find_excitations(electrons, n_qubits)
124
+
125
+
126
+ def _uccsd_native_ops(weights, n_qubits, singles, doubles, hf_occupation):
127
+ """Real UCCSD circuit for the given weights, built entirely from
128
+ dense_evolution.single_excitation_ops/double_excitation_ops (exact
129
+ closed-form / exact per-term circuits, see
130
+ dense_evolution/circuits/uccsd.py) -- no PennyLane device, QNode, or
131
+ gate decomposition involved anywhere in this function. Doubles
132
+ always use the ancilla-free path (omitting ancilla1/ancilla2) so the
133
+ circuit's qubit count stays exactly n_qubits, matching H_dense's own
134
+ dimension with no padding needed."""
135
+ ops = []
136
+ for wire, occ in enumerate(hf_occupation):
137
+ if occ:
138
+ ops.append(('x', wire))
139
+ idx = 0
140
+ for (p, q) in singles:
141
+ ops.extend(de.single_excitation_ops(p, q, weights[idx]))
142
+ idx += 1
143
+ for (p, q, r, s) in doubles:
144
+ ops.extend(de.double_excitation_ops(p, q, r, s, weights[idx]))
145
+ idx += 1
146
+ return ops
147
+
148
+
149
+ def _ops_to_qasm(ops, n_qubits):
150
+ """Gate-tuple list -> OpenQASM 2.0 text. Handles every gate name
151
+ dense_evolution.single_excitation_ops/double_excitation_ops can
152
+ produce: 2-qubit no-param (cx), 1-qubit no-param (x, h, s, sdg, ...),
153
+ 1-qubit with param (ry, rz, ...)."""
154
+ lines = ['OPENQASM 2.0;', 'include "qelib1.inc";', f'qreg q[{n_qubits}];', f'creg c[{n_qubits}];']
155
+ for op in ops:
156
+ name, rest = op[0], op[1:]
157
+ if name == 'cx':
158
+ q0, q1 = rest
159
+ lines.append(f'cx q[{q0}],q[{q1}];')
160
+ elif len(rest) == 2:
161
+ q0, param = rest
162
+ lines.append(f'{name}({float(param):.12f}) q[{q0}];')
163
+ else:
164
+ (q0,) = rest
165
+ lines.append(f'{name} q[{q0}];')
166
+ lines.append('measure q -> c;')
167
+ return '\n'.join(lines)
168
+
169
+
170
+ def _uccsd_tape_to_qasm(weights, n_qubits, singles, doubles, hf_occupation):
171
+ """Builds the real UCCSD circuit for the given (converged) weights
172
+ and translates it to OpenQASM 2.0 -- the literal native circuit
173
+ dense_evolution runs, not an approximation of it."""
174
+ return _ops_to_qasm(_uccsd_native_ops(weights, n_qubits, singles, doubles, hf_occupation), n_qubits)
175
+
176
+
177
+ def _uccsd_native_expansion(n_qubits, singles, doubles, hf_occupation, n_params):
178
+ """Makes UCCSD optimizable through dense_evolution's own
179
+ circuit_to_energy_fn (not a black-box optimizer) despite
180
+ circuit_to_energy_fn treating every parametric gate occurrence as an
181
+ independent free value: _uccsd_native_ops's own excitation circuits
182
+ reuse the *same* weight across several RY/RZ gates per excitation
183
+ (single_excitation_ops' CRY is 2 RY gates; double_excitation_ops'
184
+ per-term path is up to 8 RZ gates, one per Pauli-string term), so the
185
+ true relationship between the small real weight vector (length
186
+ n_params, one per excitation) and the full per-gate value vector
187
+ (length n_params_full, one per parametric gate occurrence -- most of
188
+ them *not* free parameters at all, but fixed pi/2 basis-change
189
+ rotations) is affine: full = baseline + expansion_matrix @ weights.
190
+
191
+ Verified exact (not approximate) by direct probing rather than
192
+ derived from theory -- same technique this function always used,
193
+ just probing dense_evolution's own native circuit builder now
194
+ instead of PennyLane's UCCSD decomposition: evaluating
195
+ _uccsd_native_ops at weights=0 (-> baseline) and at each basis
196
+ vector e_i (-> baseline's i-th deviation, i.e. expansion_matrix's
197
+ i-th column) reproduces the real downstream energy for arbitrary
198
+ weight vectors to floating-point precision when fed through this
199
+ affine map into circuit_to_energy_fn (see
200
+ tests/integration/test_dashboard_vqe.py).
201
+
202
+ Since expansion_matrix/baseline are fixed (non-trainable) arrays, the
203
+ composition `energy_fn_full(baseline + expansion_matrix @ real_theta,
204
+ h_matrix)` is itself JAX-differentiable w.r.t. real_theta by ordinary
205
+ chain rule -- no special-cased gradient logic needed.
206
+
207
+ Returns (qasm_structure, baseline, expansion_matrix). qasm_structure
208
+ uses the weights=0 reference circuit -- gate order/wires depend only
209
+ on singles/doubles/hf_occupation, never on the numeric weight
210
+ values, so any reference weight vector would produce the same
211
+ structure.
212
+
213
+ Cost (prog.txt, dashboard_core audit point 4d): probing builds the
214
+ full gate list n_params + 1 times (the zero_weights baseline, then
215
+ one basis vector e_i per excitation) -- necessary for correctness
216
+ (expansion_matrix is derived empirically here, not assumed), but
217
+ each build is O(n_qubits) Python-level gate-tuple construction, so
218
+ this is real, linear-in-n_params up-front cost before optimization
219
+ even starts. Fine for the handful-to-dozens of excitations typical
220
+ molecules in MOLECULE_CATALOG produce; a molecule with many more
221
+ excitations would feel this as a real, if one-time, per-run delay.
222
+ Not parallelized (e.g. via jax.vmap over the n_params probing
223
+ vectors) -- this is plain Python/NumPy gate-tuple assembly, not a
224
+ JAX computation, so vmap would need restructuring this as a JAX-
225
+ traceable operation first, not just wrapping the existing loop."""
226
+ def param_values(weights):
227
+ ops = _uccsd_native_ops(weights, n_qubits, singles, doubles, hf_occupation)
228
+ # 'cx' is also a 3-tuple (name, control, target) -- must be
229
+ # excluded explicitly, not just by tuple length, or its target
230
+ # qubit index gets misread as a rotation angle.
231
+ return np.array([float(op[-1]) for op in ops if op[0] in ('ry', 'rz', 'rx')])
232
+
233
+ zero_weights = np.zeros(n_params)
234
+ baseline = param_values(zero_weights)
235
+ columns = []
236
+ for i in range(n_params):
237
+ e_i = np.zeros(n_params)
238
+ e_i[i] = 1.0
239
+ columns.append(param_values(e_i) - baseline)
240
+ expansion_matrix = np.array(columns).T if n_params else np.zeros((len(baseline), 0))
241
+
242
+ qasm_structure = _uccsd_tape_to_qasm(zero_weights, n_qubits, singles, doubles, hf_occupation)
243
+ return qasm_structure, baseline, expansion_matrix
244
+
245
+
246
+ def run_vqe(symbols, geometry, charge=0, ansatz_type="hardware_efficient", n_layers=8, maxiter=200,
247
+ step_size=0.1, beta1=0.9, beta2=0.999, active_electrons=None, active_orbitals=None, seed=0):
248
+ """Runs a real VQE optimization (hand-rolled Adam over
249
+ dense_evolution's own JAX-differentiable circuit_to_energy_fn, no
250
+ PennyLane optimizer/device involved) for the molecule's
251
+ Jordan-Wigner qubit Hamiltonian. step_size/beta1/beta2 are Adam's own
252
+ real hyperparameters (learning rate and first/second moment decay),
253
+ not cosmetic -- they change the real optimization trajectory computed
254
+ below, the same way they would in any other Adam implementation.
255
+ ansatz_type is
256
+ "hardware_efficient" (generic, n_layers deep) or "uccsd" (chemically
257
+ motivated, real fermionic single/double excitations -- n_layers is
258
+ ignored, the parameter count comes from the molecule's own occupied/
259
+ virtual orbital structure). Returns a dict with the real energy
260
+ convergence trace, the final variational energy, the exact ground-
261
+ state energy (dense diagonalization -- feasible for every qubit
262
+ count this function is meant to be called with, capped by the
263
+ caller's active-space choice), and the OpenQASM circuit for the
264
+ converged parameters.
265
+
266
+ maxiter=0 (or, for hardware_efficient, n_layers=0) is a real fast
267
+ path, not a special case faked up separately: with zero ansatz
268
+ parameters there's nothing for Adam to optimize, so this returns the
269
+ bare Hartree-Fock reference circuit and its (real, exact) HF energy
270
+ immediately -- the "pick a molecule, get a circuit" mechanic the UI
271
+ uses before committing to a minutes-long optimization.
272
+
273
+ Requires the optional `pennylane` extra (used internally to build the
274
+ molecular Hamiltonian and Hartree-Fock reference state -- the native
275
+ UCCSD ansatz circuits themselves, see `dense_evolution.circuits.uccsd`,
276
+ do not need PennyLane, but Hamiltonian construction still does):
277
+ `pip install dense-evolution[pennylane]`.
278
+
279
+ Parameters
280
+ ----------
281
+ symbols : list of str
282
+ Atomic symbols, e.g. `["H", "H"]`.
283
+ geometry : list of [float, float, float]
284
+ Cartesian coordinates in Angstrom, one triplet per atom, same
285
+ order as `symbols`.
286
+ charge : int, optional
287
+ Molecular charge. Defaults to 0.
288
+ ansatz_type : str, optional
289
+ `"hardware_efficient"` (generic, `n_layers` deep) or `"uccsd"`
290
+ (chemically motivated; `n_layers` is ignored, the parameter count
291
+ comes from the molecule's own occupied/virtual orbital
292
+ structure). Defaults to `"hardware_efficient"`.
293
+ n_layers : int, optional
294
+ Ansatz depth (`hardware_efficient` only). Defaults to 8.
295
+ maxiter : int, optional
296
+ Adam iterations. Defaults to 200.
297
+ step_size, beta1, beta2 : float, optional
298
+ Adam hyperparameters (learning rate, first/second moment decay).
299
+ active_electrons, active_orbitals : int, optional
300
+ Active-space restriction, forwarded to PennyLane's Hamiltonian
301
+ builder. Defaults to the molecule's full space.
302
+ seed : int, optional
303
+ RNG seed for the initial ansatz parameters. Defaults to 0.
304
+
305
+ Returns
306
+ -------
307
+ dict
308
+ `vqe_energy_hartree` (final variational energy),
309
+ `exact_energy_hartree` (dense-diagonalization ground state, for
310
+ comparison), `energy_history` (per-iteration trace), `qasm` (the
311
+ converged circuit as OpenQASM 2.0), plus `n_qubits`, `n_params`,
312
+ `ansatz_type`, `n_layers`, `hf_occupation`.
313
+
314
+ Examples
315
+ --------
316
+ >>> from dashboard_core.vqe import run_vqe
317
+ >>> result = run_vqe(
318
+ ... symbols=["H", "H"],
319
+ ... geometry=[[0, 0, 0], [0, 0, 0.7414]],
320
+ ... ansatz_type="hardware_efficient",
321
+ ... n_layers=4,
322
+ ... maxiter=200,
323
+ ... )
324
+ >>> round(result["vqe_energy_hartree"], 4) # doctest: +SKIP
325
+ -1.1373
326
+ """
327
+ import pennylane as qml
328
+
329
+ H, n_qubits = _get_pennylane_hamiltonian(symbols, geometry, charge, "jordan_wigner",
330
+ active_electrons, active_orbitals)
331
+
332
+ # Molecule() is cheap (~15ms, no HF solve) -- just needed here for its
333
+ # real total-electron count, to pick the right Hartree-Fock occupation
334
+ # when the caller didn't already fix it via an active-space choice.
335
+ if active_electrons is not None:
336
+ electrons = active_electrons
337
+ else:
338
+ molecule = qml.qchem.Molecule(symbols, np.asarray(geometry), charge=charge, unit="angstrom")
339
+ electrons = molecule.n_electrons
340
+ hf_occupation = qml.qchem.hf_state(electrons, n_qubits)
341
+
342
+ singles = doubles = None
343
+ if ansatz_type == "uccsd":
344
+ singles, doubles = _uccsd_excitations(electrons, n_qubits)
345
+ n_params = len(singles) + len(doubles)
346
+ else:
347
+ n_params = n_qubits * n_layers
348
+
349
+ rng = np.random.default_rng(seed)
350
+
351
+ if n_params == 0 or maxiter == 0:
352
+ dev = qml.device("lightning.qubit", wires=n_qubits)
353
+
354
+ @qml.qnode(dev)
355
+ def hf_energy_fn():
356
+ _hardware_efficient_ansatz(np.zeros(0), n_qubits, 0, hf_occupation)
357
+ return qml.expval(H)
358
+
359
+ final_energy = float(hf_energy_fn())
360
+ energy_history = [final_energy]
361
+ params = np.zeros(0)
362
+ n_layers = 0
363
+ n_params = 0
364
+ elif ansatz_type == "uccsd":
365
+ import jax
366
+ import jax.numpy as jnp
367
+
368
+ H_dense, _ = build_molecular_hamiltonian(symbols, geometry, charge, "jordan_wigner",
369
+ active_electrons, active_orbitals)
370
+ qasm_structure, baseline, expansion_matrix = _uccsd_native_expansion(
371
+ n_qubits, singles, doubles, hf_occupation, n_params,
372
+ )
373
+ parsed = de.QASMParser().parse(qasm_structure)
374
+ energy_fn_full, n_params_full = de.circuit_to_energy_fn(parsed, n_qubits)
375
+ if n_params_full != len(baseline):
376
+ # BUG FIX: was `assert`, silently stripped under python -O,
377
+ # letting a UCCSD/circuit_to_energy_fn desync through to a
378
+ # shape mismatch far downstream instead of failing here with
379
+ # a clear cause.
380
+ raise ValueError(
381
+ f"circuit_to_energy_fn found {n_params_full} parametric gates, "
382
+ f"expected {len(baseline)} from the UCCSD decomposition probe"
383
+ )
384
+
385
+ h_matrix = jnp.array(H_dense)
386
+ baseline_jax = jnp.array(baseline)
387
+ expansion_matrix_jax = jnp.array(expansion_matrix)
388
+
389
+ def real_energy_fn(real_theta, h_mat):
390
+ theta_full = baseline_jax + expansion_matrix_jax @ real_theta
391
+ return energy_fn_full(theta_full, h_mat)
392
+
393
+ theta = jnp.array(rng.uniform(-0.1, 0.1, size=n_params))
394
+ m_moment = jnp.zeros(n_params)
395
+ v_moment = jnp.zeros(n_params)
396
+ eps = 1e-8
397
+ energy_and_grad = jax.jit(jax.value_and_grad(real_energy_fn, argnums=0, has_aux=True))
398
+
399
+ energy_history = []
400
+ for t in range(1, maxiter + 1):
401
+ (energy, _sv), grad = energy_and_grad(theta, h_matrix)
402
+ m_moment = beta1 * m_moment + (1 - beta1) * grad
403
+ v_moment = beta2 * v_moment + (1 - beta2) * (grad ** 2)
404
+ m_hat = m_moment / (1 - beta1 ** t)
405
+ v_hat = v_moment / (1 - beta2 ** t)
406
+ theta = theta - step_size * m_hat / (jnp.sqrt(v_hat) + eps)
407
+ energy_history.append(float(energy))
408
+ final_energy_jax, _sv_final = real_energy_fn(theta, h_matrix)
409
+ final_energy = float(final_energy_jax)
410
+ energy_history.append(final_energy)
411
+ params = np.asarray(theta)
412
+ else:
413
+ import jax
414
+ import jax.numpy as jnp
415
+
416
+ H_dense, _ = build_molecular_hamiltonian(symbols, geometry, charge, "jordan_wigner",
417
+ active_electrons, active_orbitals)
418
+ qasm_template = _hardware_efficient_qasm(np.zeros(n_params), n_qubits, n_layers, hf_occupation)
419
+ parsed = de.QASMParser().parse(qasm_template)
420
+ energy_fn, n_params_native = de.circuit_to_energy_fn(parsed, n_qubits)
421
+ if n_params_native != n_params:
422
+ # BUG FIX: was `assert`, silently stripped under python -O.
423
+ raise ValueError(
424
+ f"circuit_to_energy_fn found {n_params_native} parametric gates, expected {n_params}"
425
+ )
426
+
427
+ h_matrix = jnp.array(H_dense)
428
+ theta = jnp.array(rng.uniform(-0.1, 0.1, size=n_params))
429
+ m_moment = jnp.zeros(n_params)
430
+ v_moment = jnp.zeros(n_params)
431
+ eps = 1e-8
432
+ energy_and_grad = jax.jit(jax.value_and_grad(energy_fn, argnums=0, has_aux=True))
433
+
434
+ energy_history = []
435
+ for t in range(1, maxiter + 1):
436
+ (energy, _sv), grad = energy_and_grad(theta, h_matrix)
437
+ m_moment = beta1 * m_moment + (1 - beta1) * grad
438
+ v_moment = beta2 * v_moment + (1 - beta2) * (grad ** 2)
439
+ m_hat = m_moment / (1 - beta1 ** t)
440
+ v_hat = v_moment / (1 - beta2 ** t)
441
+ theta = theta - step_size * m_hat / (jnp.sqrt(v_hat) + eps)
442
+ energy_history.append(float(energy))
443
+ final_energy_jax, _sv_final = energy_fn(theta, h_matrix)
444
+ final_energy = float(final_energy_jax)
445
+ energy_history.append(final_energy)
446
+ params = np.asarray(theta)
447
+
448
+ exact_energy = None
449
+ dim = 2 ** n_qubits
450
+ if dim <= 4096: # dense diagonalization budget: 4096^2 complex128 = 128 MB
451
+ # Both the uccsd and hardware_efficient-with-params branches above
452
+ # already built H_dense with these exact same arguments -- only
453
+ # the n_params==0 (Hartree-Fock-only, no ansatz built at all)
454
+ # branch never did. Reusing it there instead of a second
455
+ # "H_dense_check" call was previously a redundant cache lookup
456
+ # every time, not a bug (build_molecular_hamiltonian is cached,
457
+ # so it returned the identical matrix either way), but confusing
458
+ # flow (prog.txt, dashboard_core audit point 3c).
459
+ if n_params == 0:
460
+ H_dense, _ = build_molecular_hamiltonian(symbols, geometry, charge, "jordan_wigner",
461
+ active_electrons, active_orbitals)
462
+ exact_energy = float(np.linalg.eigvalsh(H_dense).min())
463
+
464
+ if n_params == 0:
465
+ qasm = _hardware_efficient_qasm(params, n_qubits, 0, hf_occupation)
466
+ elif ansatz_type == "uccsd":
467
+ qasm = _uccsd_tape_to_qasm(params, n_qubits, singles, doubles, hf_occupation)
468
+ else:
469
+ qasm = _hardware_efficient_qasm(params, n_qubits, n_layers, hf_occupation)
470
+
471
+ return {
472
+ 'n_qubits': n_qubits,
473
+ 'ansatz_type': ansatz_type if n_params > 0 else 'hartree_fock',
474
+ 'n_layers': n_layers if ansatz_type != "uccsd" else None,
475
+ 'n_params': n_params,
476
+ 'hf_occupation': [int(b) for b in hf_occupation],
477
+ 'energy_history': energy_history,
478
+ 'vqe_energy_hartree': final_energy,
479
+ 'exact_energy_hartree': exact_energy,
480
+ 'qasm': qasm,
481
+ 'params': params.tolist(),
482
+ }
483
+
484
+
485
+ def scan_hardware_efficient_energy_landscape(
486
+ symbols, geometry, charge, n_layers, hf_occupation, base_params,
487
+ param_i, param_j, values_i, values_j,
488
+ active_electrons=None, active_orbitals=None,
489
+ ):
490
+ """Real 2D energy-landscape scan around a converged hardware_efficient
491
+ VQE result: re-evaluates <psi(theta)|H|psi(theta)> on
492
+ dense_evolution's own circuit_to_energy_fn for every (values_i,
493
+ values_j) grid point, holding every parameter except param_i/param_j
494
+ fixed at its converged value from base_params -- the same real
495
+ Hamiltonian and ansatz circuit run_vqe itself used for this molecule,
496
+ not a separate or approximate model.
497
+
498
+ hardware_efficient only: UCCSD's parameter space is an affine
499
+ expansion over full per-gate values (_uccsd_native_expansion), not a
500
+ direct one-parameter-per-rotation-gate mapping, so "parameter i"
501
+ doesn't correspond to a single rotation angle the way it does here.
502
+
503
+ Returns a (len(values_i), len(values_j)) numpy array of energies in
504
+ Hartree.
505
+ """
506
+ import jax
507
+ import jax.numpy as jnp
508
+
509
+ n_qubits = len(hf_occupation)
510
+ n_params = n_qubits * n_layers
511
+ H_dense, _ = build_molecular_hamiltonian(
512
+ symbols, geometry, charge, "jordan_wigner", active_electrons, active_orbitals,
513
+ )
514
+ qasm_template = _hardware_efficient_qasm(np.zeros(n_params), n_qubits, n_layers, hf_occupation)
515
+ parsed = de.QASMParser().parse(qasm_template)
516
+ energy_fn, n_params_native = de.circuit_to_energy_fn(parsed, n_qubits)
517
+ if n_params_native != n_params:
518
+ raise ValueError(
519
+ f"circuit_to_energy_fn found {n_params_native} parametric gates, expected {n_params}"
520
+ )
521
+
522
+ h_matrix = jnp.array(H_dense)
523
+ base = jnp.array(base_params)
524
+ energy_fn_jit = jax.jit(energy_fn)
525
+
526
+ energies = np.zeros((len(values_i), len(values_j)))
527
+ for a, vi in enumerate(values_i):
528
+ theta_a = base.at[param_i].set(vi)
529
+ for b, vj in enumerate(values_j):
530
+ theta_ab = theta_a.at[param_j].set(vj)
531
+ energy, _sv = energy_fn_jit(theta_ab, h_matrix)
532
+ energies[a, b] = float(energy)
533
+ return energies