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,275 @@
1
+ """
2
+ Native (PennyLane-free) UCCSD excitation circuits.
3
+
4
+ UCCSD's single/double fermionic excitation generators
5
+ (a_p^dagger a_q - h.c., a_p^dagger a_q^dagger a_r a_s - h.c.) were
6
+ previously only reachable through PennyLane's own FermionicSingleExcitation/
7
+ FermionicDoubleExcitation decomposition (see dashboard/core/vqe.py's
8
+ _uccsd_tape_to_qasm) -- not because a native circuit is theoretically
9
+ impossible, but because deriving the exact (non-Trotterized) circuit
10
+ identity requires real derivation, not just wiring PennyLane through.
11
+
12
+ This module IS that derivation, done directly against
13
+ dense_evolution.physics.fermions.majorana_pauli_terms (already-verified
14
+ Jordan-Wigner mapping) rather than against PennyLane's internals, and
15
+ every circuit identity below was verified against scipy.linalg.expm of
16
+ the exact generator matrix (independently reconstructed from raw
17
+ Jordan-Wigner ladder operators, not from PennyLane) before being written
18
+ here -- see tests/unit/test_uccsd.py.
19
+
20
+ Single excitation (any orbital distance): exact closed form, always.
21
+ G_pq = a_p^dagger a_q - a_q^dagger a_p decomposes (via
22
+ a_p^dagger=(chi_X(p)-i*chi_Y(p))/2) into exactly 2 Pauli strings that
23
+ share the Jordan-Wigner Z-string between p and q and differ only by an
24
+ X<->Y swap at p and q. This is the standard "Givens rotation" generator
25
+ (G^2 = 2I): exact circuit is CNOT(p,q), fold each Z-string qubit's
26
+ parity onto p via CNOT(k,p), CRY(2*theta, control=q, target=p), then
27
+ undo. Verified for adjacent and long-Z-string pairs alike (any p<q).
28
+
29
+ Double excitation: exact closed form when the occupied pair (p,q) is
30
+ adjacent (q=p+1) AND the virtual pair (r,s) is adjacent (s=r+1) -- the
31
+ gap between q and r (HOMO-LUMO gap) can be any length, no restriction.
32
+ This is the case dashboard/core/vqe.py's UCCSD ansatz always produces
33
+ for a 2-occupied/2-virtual active space (e.g. H2 in a minimal basis),
34
+ and generally whenever qml.qchem.excitations pairs consecutive occupied
35
+ orbitals with consecutive virtual orbitals. G_pqrs (8 Pauli terms)
36
+ reduces to a clean 2-level coupling between |p=0,q=1,r=1,s=1,...> and
37
+ its bit-flip, realized via a CNOT fan (mirroring the p,q,r,s pattern
38
+ onto q) plus a triple-controlled RY (Toffoli-mediated, 2 ancilla
39
+ qubits, always returned to |0>).
40
+
41
+ For a double excitation where the occupied pair or the virtual pair is
42
+ itself non-adjacent (occurs for active spaces with 3+ occupied or 3+
43
+ virtual orbitals, e.g. LiH/BeH2), the same closed-form Z-string folding
44
+ used for single excitations does NOT carry over cleanly (verified
45
+ directly: the naive fold leaves genuine leakage, not just an
46
+ unsimplified-but-correct circuit) -- deriving a comparably compact
47
+ closed form for that case is unresolved, so double_excitation_ops falls
48
+ back to per-term exponentiation of the 8 Pauli terms via
49
+ dense_evolution.circuits.trotter.pauli_rotation_ops for that specific
50
+ case, applied sequentially in a fixed term order.
51
+
52
+ That fallback turned out to be exact too, not a Trotter approximation:
53
+ verified exhaustively (every computational basis state, several p<q<r<s
54
+ choices including non-adjacent pairs, theta up to pi) against
55
+ scipy.linalg.expm of the exact generator matrix, to floating-point
56
+ precision (~1e-15) at every theta tested, not just small theta the way
57
+ a genuine first-order Trotter error would show. The 8 terms don't
58
+ commute as operators on the full Hilbert space, but the generator only
59
+ ever couples two basis states for any fixed setting of the qubits it
60
+ doesn't act on (same structure as the closed-form case), and within
61
+ that 2-dimensional invariant subspace the per-term exponentials
62
+ apparently compose without error -- not derived from first principles
63
+ here, just confirmed by the exhaustive check above. Only the GATE COUNT
64
+ differs from the closed form, not correctness.
65
+ """
66
+ from typing import List, Tuple
67
+
68
+ from .trotter import pauli_rotation_ops
69
+
70
+ __all__ = ['find_excitations', 'single_excitation_ops', 'double_excitation_ops']
71
+
72
+
73
+ def find_excitations(electrons: int, n_qubits: int):
74
+ """
75
+ Enumerate spin-conserving single/double excitation index tuples for
76
+ a Hartree-Fock reference with the given electron count -- pure
77
+ classical combinatorics (occupied = orbitals [0, electrons), virtual
78
+ = the rest, even index = spin-up, odd = spin-down under the standard
79
+ Jordan-Wigner interleaved spin-orbital ordering), no quantum
80
+ computation involved. Verified to reproduce
81
+ qml.qchem.excitations(electrons, n_qubits) exactly across several
82
+ (electrons, n_qubits) pairs -- see tests/unit/test_uccsd.py -- kept
83
+ here so finding *which* excitations exist doesn't need PennyLane
84
+ installed any more than building their circuits does.
85
+
86
+ Returns (singles, doubles): singles is a list of [p, q] (p occupied,
87
+ q virtual, same spin); doubles is a list of [p, q, r, s] with
88
+ p<q both occupied, r<s both virtual, total spin conserved.
89
+ """
90
+ occupied = list(range(electrons))
91
+ virtual = list(range(electrons, n_qubits))
92
+ singles = [[p, q] for p in occupied for q in virtual if p % 2 == q % 2]
93
+ doubles = []
94
+ for i, p in enumerate(occupied):
95
+ for q in occupied[i + 1:]:
96
+ for j, r in enumerate(virtual):
97
+ for s in virtual[j + 1:]:
98
+ if (p % 2 + q % 2) == (r % 2 + s % 2):
99
+ doubles.append([p, q, r, s])
100
+ return singles, doubles
101
+
102
+
103
+ # ── Pauli-string algebra (needed only for the per-term fallback path's
104
+ # generator decomposition -- the closed-form circuits below are pure
105
+ # combinatorial gate templates and never call this at all) ──────────────
106
+
107
+ _MUL = {
108
+ ('I', 'I'): ('I', 1), ('I', 'X'): ('X', 1), ('I', 'Y'): ('Y', 1), ('I', 'Z'): ('Z', 1),
109
+ ('X', 'I'): ('X', 1), ('Y', 'I'): ('Y', 1), ('Z', 'I'): ('Z', 1),
110
+ ('X', 'X'): ('I', 1), ('Y', 'Y'): ('I', 1), ('Z', 'Z'): ('I', 1),
111
+ ('X', 'Y'): ('Z', 1j), ('Y', 'X'): ('Z', -1j),
112
+ ('Y', 'Z'): ('X', 1j), ('Z', 'Y'): ('X', -1j),
113
+ ('Z', 'X'): ('Y', 1j), ('X', 'Z'): ('Y', -1j),
114
+ }
115
+
116
+
117
+ def _pauli_mul_term(term_a, term_b):
118
+ ca, da = term_a
119
+ cb, db = term_b
120
+ phase = ca * cb
121
+ out = {}
122
+ for q in set(da) | set(db):
123
+ la, lb = da.get(q, 'I'), db.get(q, 'I')
124
+ letter, ph = _MUL[(la, lb)]
125
+ phase *= ph
126
+ if letter != 'I':
127
+ out[q] = letter
128
+ return (phase, out)
129
+
130
+
131
+ def _psum_mul(terms_a, terms_b):
132
+ return [_pauli_mul_term(ta, tb) for ta in terms_a for tb in terms_b]
133
+
134
+
135
+ def _psum_scale(terms, s):
136
+ return [(c * s, d) for c, d in terms]
137
+
138
+
139
+ def _psum_combine(terms):
140
+ acc = {}
141
+ for c, d in terms:
142
+ key = tuple(sorted(d.items()))
143
+ acc[key] = acc.get(key, 0) + c
144
+ return [(c, dict(key)) for key, c in acc.items() if abs(c) > 1e-12]
145
+
146
+
147
+ def _chi(mode_index, n_qubits):
148
+ from ..physics.fermions import majorana_pauli_terms
149
+ return majorana_pauli_terms(mode_index, n_qubits)[1]
150
+
151
+
152
+ def _a_dagger(p, n_qubits):
153
+ return [(0.5, _chi(2 * p + 1, n_qubits)), (-0.5j, _chi(2 * p + 2, n_qubits))]
154
+
155
+
156
+ def _a(p, n_qubits):
157
+ return [(0.5, _chi(2 * p + 1, n_qubits)), (0.5j, _chi(2 * p + 2, n_qubits))]
158
+
159
+
160
+ def _double_excitation_generator(p, q, r, s, n_qubits):
161
+ """8 Pauli terms of a_p^dagger a_q^dagger a_r a_s - h.c. (JW-mapped).
162
+ Used only by the per-term fallback path -- verified against direct
163
+ fermionic ladder operators in tests/unit/test_uccsd.py."""
164
+ term1 = _psum_mul(_psum_mul(_a_dagger(p, n_qubits), _a_dagger(q, n_qubits)),
165
+ _psum_mul(_a(r, n_qubits), _a(s, n_qubits)))
166
+ term2 = _psum_mul(_psum_mul(_a_dagger(s, n_qubits), _a_dagger(r, n_qubits)),
167
+ _psum_mul(_a(q, n_qubits), _a(p, n_qubits)))
168
+ return _psum_combine(term1 + _psum_scale(term2, -1))
169
+
170
+
171
+ # ── Single excitation: exact closed form, any p<q ───────────────────────
172
+
173
+ def single_excitation_ops(p: int, q: int, theta: float) -> List[Tuple]:
174
+ """
175
+ Exact circuit for exp(theta * (a_p^dagger a_q - a_q^dagger a_p)),
176
+ the UCCS single-excitation unitary, for ANY p < q (any Jordan-Wigner
177
+ Z-string length in between) -- no Trotter error, ever.
178
+
179
+ Gate count: 2*(1 + (q-p-1)) CX + 1 CRY (itself 2 CX + 2 RY), so
180
+ 2*(q-p) + 2 CX total.
181
+ """
182
+ if not p < q:
183
+ raise ValueError(f"single_excitation_ops requires p < q, got p={p}, q={q}")
184
+ inter = list(range(p + 1, q))
185
+ ops: List[Tuple] = []
186
+ ops.append(('cx', p, q))
187
+ for k in inter:
188
+ ops.append(('cx', k, p))
189
+ ops.extend(_cry_ops(2 * theta, control=q, target=p))
190
+ for k in reversed(inter):
191
+ ops.append(('cx', k, p))
192
+ ops.append(('cx', p, q))
193
+ return ops
194
+
195
+
196
+ def _cry_ops(theta, control, target):
197
+ """CRY(theta) from native RY+CX: RY(theta/2)_t ; CX(c,t) ; RY(-theta/2)_t ; CX(c,t)."""
198
+ return [
199
+ ('ry', target, theta / 2),
200
+ ('cx', control, target),
201
+ ('ry', target, -theta / 2),
202
+ ('cx', control, target),
203
+ ]
204
+
205
+
206
+ # ── Double excitation ────────────────────────────────────────────────────
207
+
208
+ def double_excitation_ops(p: int, q: int, r: int, s: int, theta: float,
209
+ ancilla1: int = None, ancilla2: int = None) -> List[Tuple]:
210
+ """
211
+ Circuit for exp(theta * (a_p^dagger a_q^dagger a_r a_s - h.c.)), the
212
+ UCCD double-excitation unitary, for p < q < r < s.
213
+
214
+ Exact closed form (no Trotter error) when ancilla1/ancilla2 are both
215
+ given AND the occupied pair is adjacent (q == p+1) AND the virtual
216
+ pair is adjacent (s == r+1) -- the gap between q and r can be any
217
+ length. This is what qml.qchem.excitations always produces for a
218
+ 2-occupied/2-virtual active space, and sometimes for larger ones.
219
+
220
+ Falls back to per-term exponentiation of the 8-term Pauli
221
+ decomposition whenever no ancillas are given, or the pairs aren't
222
+ adjacent -- verified exact (not an approximation), just more gates
223
+ than the closed form; see the module docstring for why the closed
224
+ form doesn't generalize to a non-adjacent occupied or virtual pair
225
+ yet.
226
+
227
+ ancilla1, ancilla2 : two qubits that MUST be |0> on entry and are
228
+ guaranteed returned to |0> on exit -- only touched by the
229
+ closed-form path (never referenced by the per-term fallback), so
230
+ the same two ancillas can be reused across every double excitation
231
+ in an ansatz. Omit both (leave as None) to force the ancilla-free
232
+ per-term path unconditionally -- e.g. when the caller's qubit
233
+ register has no spare qubits to offer.
234
+ """
235
+ if not p < q < r < s:
236
+ raise ValueError(f"double_excitation_ops requires p<q<r<s, got {p},{q},{r},{s}")
237
+
238
+ have_ancillas = ancilla1 is not None and ancilla2 is not None
239
+ if have_ancillas and q == p + 1 and s == r + 1:
240
+ return _double_excitation_ops_closed_form(p, q, r, s, theta, ancilla1, ancilla2)
241
+ return _double_excitation_ops_per_term(p, q, r, s, theta)
242
+
243
+
244
+ def _double_excitation_ops_closed_form(p, q, r, s, theta, a1, a2) -> List[Tuple]:
245
+ ops: List[Tuple] = []
246
+ ops.append(('cx', q, s))
247
+ ops.append(('cx', q, r))
248
+ ops.append(('cx', q, p))
249
+ ops.append(('x', p))
250
+ ops.extend(_toffoli_ops(p, r, a1))
251
+ ops.extend(_toffoli_ops(a1, s, a2))
252
+ ops.extend(_cry_ops(-2 * theta, control=a2, target=q))
253
+ ops.extend(_toffoli_ops(a1, s, a2))
254
+ ops.extend(_toffoli_ops(p, r, a1))
255
+ ops.append(('x', p))
256
+ ops.append(('cx', q, p))
257
+ ops.append(('cx', q, r))
258
+ ops.append(('cx', q, s))
259
+ return ops
260
+
261
+
262
+ def _toffoli_ops(c1, c2, t):
263
+ from .compiler import QuantumTranspiler
264
+ return QuantumTranspiler.decompose_toffoli(c1, c2, t)
265
+
266
+
267
+ def _double_excitation_ops_per_term(p, q, r, s, theta) -> List[Tuple]:
268
+ n_qubits = s + 1
269
+ terms = _double_excitation_generator(p, q, r, s, n_qubits)
270
+ ops: List[Tuple] = []
271
+ for coeff, pauli_dict in terms:
272
+ real_part = (coeff / 1j).real
273
+ angle = -theta * real_part
274
+ ops.extend(pauli_rotation_ops(pauli_dict, angle))
275
+ return ops
dense_evolution/cli.py ADDED
@@ -0,0 +1,199 @@
1
+ """
2
+ `dense-evolution` console script (see pyproject.toml [project.scripts]).
3
+
4
+ Three real subcommands:
5
+ serve starts the local Composer kernel (local_site.app.server)
6
+ that the published Composer page (docs/composer.md)
7
+ talks to.
8
+ offline-composer downloads the real published Composer page (the same
9
+ HTML Github Pages serves, not a hand-rolled copy) plus
10
+ the same-origin assets it references, into a local
11
+ folder -- so it opens via file:// with no internet at
12
+ all, while still talking to the local kernel above.
13
+ mcp starts the dense_evolution_mcp MCP server (mcp_server/),
14
+ a thin adapter that exposes the same Composer kernel
15
+ endpoints as MCP tools for an agent to call directly.
16
+ Requires `serve` running separately -- this process
17
+ talks to that kernel over HTTP, it doesn't embed it.
18
+
19
+ fastapi/uvicorn/pydantic (composer) and mcp/httpx (mcp) are optional
20
+ extras, not core dependencies -- imported here, inside their own command
21
+ branch, not at module level, so `import dense_evolution` itself never
22
+ requires them.
23
+ """
24
+
25
+ import sys
26
+
27
+ USAGE = """usage: dense-evolution <command>
28
+
29
+ commands:
30
+ serve Start the local Composer kernel (http://127.0.0.1:8800)
31
+ that the published Composer page (docs/composer.md)
32
+ connects to.
33
+ offline-composer [DEST] Download the real published Composer page and its
34
+ assets into DEST (default: ./composer-offline) so
35
+ it works via file:// with no internet.
36
+ mcp Start the dense_evolution_mcp MCP server (stdio
37
+ transport) so an agent can call the Composer
38
+ kernel's endpoints directly. Requires `serve`
39
+ running separately (default http://127.0.0.1:8800,
40
+ override with DENSE_EVOLUTION_KERNEL_URL).
41
+
42
+ Requires the composer extra: pip install dense-evolution[composer]
43
+ The mcp command requires the mcp extra: pip install dense-evolution[mcp]
44
+ """
45
+
46
+ COMPOSER_PAGE_URL = "https://tatopenn-cell.github.io/Dense-Evolution/composer/"
47
+
48
+
49
+ def _require_composer_extra():
50
+ try:
51
+ import fastapi, uvicorn, pydantic # noqa: F401
52
+ except ImportError as exc:
53
+ print(
54
+ "dense-evolution needs the composer extra:\n"
55
+ " pip install dense-evolution[composer]\n"
56
+ f"(missing: {exc.name})",
57
+ file=sys.stderr,
58
+ )
59
+ sys.exit(1)
60
+
61
+
62
+ def _cmd_serve():
63
+ _require_composer_extra()
64
+ from local_site.app.server import main as serve_main
65
+ serve_main()
66
+
67
+
68
+ def _require_mcp_extra():
69
+ try:
70
+ import mcp, httpx # noqa: F401
71
+ except ImportError as exc:
72
+ print(
73
+ "dense-evolution needs the mcp extra:\n"
74
+ " pip install dense-evolution[mcp]\n"
75
+ f"(missing: {exc.name})",
76
+ file=sys.stderr,
77
+ )
78
+ sys.exit(1)
79
+
80
+
81
+ def _cmd_mcp():
82
+ _require_mcp_extra()
83
+ from mcp_server.server import main as mcp_main
84
+ mcp_main()
85
+
86
+
87
+ def _cmd_offline_composer(dest: str):
88
+ """Mirrors COMPOSER_PAGE_URL into `dest`: the page itself plus every
89
+ same-origin <link href>/<script src> it references (Material's shared
90
+ theme CSS/JS bundle, the Composer-specific app.js/style.css), each
91
+ saved at the same relative path it already uses on the live site --
92
+ mkdocs/Material already generate those as page-relative, precisely so
93
+ a subtree like this stays self-consistent once copied elsewhere. Not a
94
+ full recursive mirror (fonts referenced only from inside a CSS url()
95
+ are not followed -- Material falls back to system fonts without them,
96
+ a cosmetic gap, not a functional one): scoped to what the page's own
97
+ <head>/<body> actually link to, which is everything the Composer UI
98
+ itself needs to run."""
99
+ import os
100
+ import urllib.request
101
+ from html.parser import HTMLParser
102
+ from urllib.parse import urljoin, urlparse
103
+
104
+ class _AssetFinder(HTMLParser):
105
+ def __init__(self):
106
+ super().__init__()
107
+ self.assets = []
108
+
109
+ def handle_starttag(self, tag, attrs):
110
+ attrs = dict(attrs)
111
+ # mkdocs Material's <head> has plenty of other <link> rels
112
+ # (canonical, prev, next, preconnect, ...) that point at other
113
+ # *pages* (directories, not files) or aren't fetchable assets
114
+ # at all -- verified directly against the real page: following
115
+ # rel="prev"/"next" tried to write a file at a directory-shaped
116
+ # path and failed with a permission error. Only stylesheet/icon
117
+ # links are real same-origin files worth mirroring.
118
+ if tag == "link" and attrs.get("href") and attrs.get("rel") in ("stylesheet", "icon"):
119
+ self.assets.append(attrs["href"])
120
+ elif tag == "script" and attrs.get("src"):
121
+ self.assets.append(attrs["src"])
122
+ elif tag == "img" and attrs.get("src"):
123
+ self.assets.append(attrs["src"])
124
+
125
+ print(f"Scarico {COMPOSER_PAGE_URL} ...")
126
+ with urllib.request.urlopen(COMPOSER_PAGE_URL, timeout=30) as resp:
127
+ html_bytes = resp.read()
128
+ html_text = html_bytes.decode("utf-8", errors="replace")
129
+
130
+ parser = _AssetFinder()
131
+ parser.feed(html_text)
132
+
133
+ page_url = urlparse(COMPOSER_PAGE_URL)
134
+ base_origin = f"{page_url.scheme}://{page_url.netloc}"
135
+ # mkdocs generates every relative link (../assets/...) relative to the
136
+ # SITE root, not to whatever folder happens to hold index.html -- so
137
+ # the page itself has to be saved at the same depth under `dest` it
138
+ # already has under the site root (site_root_path stripped from its
139
+ # own path leaves "composer/"), or its own "../assets/..." references
140
+ # end up pointing one directory above `dest` instead of inside it.
141
+ # site_root_path is derived from COMPOSER_PAGE_URL itself (this
142
+ # project's one fixed, known URL), not guessed from each asset URL.
143
+ site_root_path = page_url.path.rsplit("composer/", 1)[0]
144
+
145
+ def _relative_to_site_root(url: str) -> str:
146
+ path = urlparse(url).path
147
+ if path.startswith(site_root_path):
148
+ path = path[len(site_root_path):]
149
+ return path.lstrip("/")
150
+
151
+ os.makedirs(dest, exist_ok=True)
152
+ page_relative_path = _relative_to_site_root(COMPOSER_PAGE_URL) + "index.html"
153
+ page_local_path = os.path.join(dest, page_relative_path)
154
+ os.makedirs(os.path.dirname(page_local_path), exist_ok=True)
155
+ with open(page_local_path, "w", encoding="utf-8") as f:
156
+ f.write(html_text)
157
+
158
+ seen = set()
159
+ for href in parser.assets:
160
+ absolute_url = urljoin(COMPOSER_PAGE_URL, href)
161
+ if not absolute_url.startswith(base_origin) or absolute_url in seen:
162
+ continue # a genuinely external resource (e.g. a CDN) -- not ours to mirror
163
+ seen.add(absolute_url)
164
+
165
+ relative_path = _relative_to_site_root(absolute_url)
166
+ local_path = os.path.join(dest, relative_path)
167
+ os.makedirs(os.path.dirname(local_path), exist_ok=True)
168
+ try:
169
+ with urllib.request.urlopen(absolute_url, timeout=30) as resp:
170
+ data = resp.read()
171
+ with open(local_path, "wb") as f:
172
+ f.write(data)
173
+ print(f" scaricato: {relative_path}")
174
+ except Exception as exc:
175
+ print(f" saltato (non essenziale): {relative_path} ({exc})")
176
+
177
+ print(f"\nCopia offline pronta: {page_local_path}")
178
+ return page_local_path
179
+
180
+
181
+ def main(argv=None):
182
+ args = argv if argv is not None else sys.argv[1:]
183
+ if args == ["serve"]:
184
+ _cmd_serve()
185
+ return
186
+ if args and args[0] == "offline-composer":
187
+ dest = args[1] if len(args) > 1 else "composer-offline"
188
+ _cmd_offline_composer(dest)
189
+ return
190
+ if args == ["mcp"]:
191
+ _cmd_mcp()
192
+ return
193
+
194
+ print(USAGE, file=sys.stderr if args else sys.stdout)
195
+ sys.exit(1 if args else 0)
196
+
197
+
198
+ if __name__ == "__main__":
199
+ main()
@@ -0,0 +1,9 @@
1
+ """Backward-compatibility shim -- the real implementation moved to
2
+ dense_evolution.circuits.compiler as part of the Phase 2 subpackage split
3
+ (see prog.txt). Kept so `from dense_evolution.compiler import QuantumTranspiler`
4
+ (used by external consumers, e.g. Dense-Evolution-Discovery) keeps working
5
+ unchanged. Import from dense_evolution.circuits.compiler directly in new code.
6
+ """
7
+ from dense_evolution.circuits.compiler import QuantumTranspiler
8
+
9
+ __all__ = ['QuantumTranspiler']
@@ -0,0 +1,49 @@
1
+ """
2
+ Centralized JAX precision control for dense_evolution.
3
+
4
+ jax_enable_x64 is a process-wide JAX flag: once set, it affects every
5
+ JAX array in the process, not just dense_evolution's own. Before this
6
+ module existed, registry.py, compiler.py and statevector.py each called
7
+ jax.config.update("jax_enable_x64", True) unconditionally at MODULE
8
+ IMPORT time -- so merely `import dense_evolution`, even without ever
9
+ constructing a simulator, silently overrode a precision a caller had
10
+ already configured for unrelated JAX code running earlier in the same
11
+ process. mps.py and chunk.py never did this; their own docstrings
12
+ already documented the intended convention (see mps.py): dense_evolution
13
+ enables x64 lazily, only when something that actually needs complex128
14
+ precision is used, not as an import-time side effect.
15
+
16
+ This module is that single point. ensure_x64() is idempotent and safe
17
+ to call from every float64 entry point (DenseSVSimulator.__init__,
18
+ QuantumHardwareRegistry.__init__, circuit_to_energy_fn); set_precision()
19
+ is the public opt-out for a caller who wants to choose precision
20
+ explicitly, before constructing anything, and have that choice stick.
21
+ """
22
+ import jax
23
+
24
+ _explicit = False
25
+
26
+
27
+ def set_precision(float64: bool = True) -> None:
28
+ """
29
+ Explicitly configure JAX's process-wide numeric precision.
30
+
31
+ Call this yourself, before constructing anything, if you need
32
+ control over exactly when/whether dense_evolution enables x64 --
33
+ for example because another float32-only JAX library must be
34
+ initialized first. Once called, dense_evolution's own lazy
35
+ ensure_x64() (used internally by DenseSVSimulator, etc.) no longer
36
+ forces float64 back on, so an explicit set_precision(False) sticks.
37
+ """
38
+ global _explicit
39
+ jax.config.update("jax_enable_x64", float64)
40
+ _explicit = True
41
+
42
+
43
+ def ensure_x64() -> None:
44
+ """Enable jax_enable_x64 unless a caller has already explicitly
45
+ configured precision via set_precision(). Called internally,
46
+ lazily, by dense_evolution components that need complex128
47
+ precision -- never at import time."""
48
+ if not _explicit:
49
+ jax.config.update("jax_enable_x64", True)
@@ -0,0 +1,10 @@
1
+ """Backward-compatibility shim -- the real implementation moved to
2
+ dense_evolution.utils.drawing as part of the Phase 2 subpackage split
3
+ (see prog.txt). Kept so `from dense_evolution.drawing import draw_circuit`
4
+ (used by external consumers, e.g. Dense-Evolution-Discovery) keeps working
5
+ unchanged. Import from dense_evolution.utils.drawing directly in new code.
6
+ """
7
+
8
+ from dense_evolution.utils.drawing import draw_circuit
9
+
10
+ __all__ = ["draw_circuit"]
@@ -0,0 +1,9 @@
1
+ """Backward-compatibility shim -- the real implementation moved to
2
+ dense_evolution.physics.entropy as part of the Phase 2 subpackage split
3
+ (see prog.txt). Kept so `from dense_evolution.entropy import partial_trace, von_neumann_entropy, mutual_information`
4
+ (used by external consumers, e.g. Dense-Evolution-Discovery) keeps working
5
+ unchanged. Import from dense_evolution.physics.entropy directly in new code.
6
+ """
7
+ from dense_evolution.physics.entropy import partial_trace, von_neumann_entropy, mutual_information, central_charge
8
+
9
+ __all__ = ['partial_trace', 'von_neumann_entropy', 'mutual_information', 'central_charge']
@@ -0,0 +1,9 @@
1
+ """Backward-compatibility shim -- the real implementation moved to
2
+ dense_evolution.physics.fermions as part of the Phase 2 subpackage split
3
+ (see prog.txt). Kept so `from dense_evolution.fermions import majorana_pauli_terms`
4
+ (used by external consumers, e.g. Dense-Evolution-Discovery) keeps working
5
+ unchanged. Import from dense_evolution.physics.fermions directly in new code.
6
+ """
7
+ from dense_evolution.physics.fermions import majorana_pauli_terms
8
+
9
+ __all__ = ['majorana_pauli_terms']
@@ -0,0 +1,9 @@
1
+ """Backward-compatibility shim -- the real implementation moved to
2
+ dense_evolution.circuits.gates as part of the Phase 2 subpackage split
3
+ (see prog.txt). Kept so `from dense_evolution.gates import GATES`
4
+ (used by external consumers, e.g. Dense-Evolution-Discovery) keeps working
5
+ unchanged. Import from dense_evolution.circuits.gates directly in new code.
6
+ """
7
+ from dense_evolution.circuits.gates import GATES, PARAMETRIC_GATES, GATE_IDS
8
+
9
+ __all__ = ['GATES', 'PARAMETRIC_GATES', 'GATE_IDS']
@@ -0,0 +1,16 @@
1
+ """Backward-compatibility shim -- the real implementation moved to
2
+ dense_evolution.solvers.harrison_tb as part of the Phase 2 subpackage
3
+ split (see prog.txt). Kept so `from dense_evolution.harrison_tb import
4
+ zincblende_hamiltonian` (used by external consumers, e.g.
5
+ Dense-Evolution-Discovery) keeps working unchanged. Import from
6
+ dense_evolution.solvers.harrison_tb directly in new code.
7
+ """
8
+ from dense_evolution.solvers.harrison_tb import (
9
+ ELEMENTS, ETA, HBAR2_OVER_M_EV_ANG2,
10
+ hopping_integral, sp3_bond_block, sp3_dimer_hamiltonian, zincblende_hamiltonian,
11
+ )
12
+
13
+ __all__ = [
14
+ "ELEMENTS", "ETA", "HBAR2_OVER_M_EV_ANG2",
15
+ "hopping_integral", "sp3_bond_block", "sp3_dimer_hamiltonian", "zincblende_hamiltonian",
16
+ ]
@@ -0,0 +1,18 @@
1
+ """Backward-compatibility shim -- the real implementation moved to
2
+ dense_evolution.mitigation.healing as part of the Phase 2 subpackage
3
+ split (see prog.txt). Kept so `from dense_evolution.healing import
4
+ calculate_phi_ab` (used by external consumers, e.g. tools/ia_utils and
5
+ Dense-Evolution-Discovery) keeps working unchanged. Import from
6
+ dense_evolution.mitigation.healing directly in new code.
7
+ """
8
+ from dense_evolution.mitigation.healing import (
9
+ calculate_advanced_sigma, calculate_phi_ab, calculate_vettore_dinamico,
10
+ calculate_vettore_statico, calculate_delta_preemp, evaluate_phi_trigger,
11
+ calculate_jax_reflection, MemoryReflectionEngine, GLOBAL_CONSTANTS,
12
+ )
13
+
14
+ __all__ = [
15
+ "calculate_advanced_sigma", "calculate_phi_ab", "calculate_vettore_dinamico",
16
+ "calculate_vettore_statico", "calculate_delta_preemp", "evaluate_phi_trigger",
17
+ "calculate_jax_reflection", "MemoryReflectionEngine", "GLOBAL_CONSTANTS",
18
+ ]
@@ -0,0 +1,18 @@
1
+ """Interop subpackage: bridges to Qiskit and PennyLane circuits/backends."""
2
+ from .qiskit_pennylane import (
3
+ from_qiskit,
4
+ from_pennylane,
5
+ run_qiskit_circuit,
6
+ run_pennylane_circuit,
7
+ noise_model_from_qiskit_backend,
8
+ to_stim,
9
+ )
10
+
11
+ __all__ = [
12
+ "from_qiskit",
13
+ "from_pennylane",
14
+ "run_qiskit_circuit",
15
+ "run_pennylane_circuit",
16
+ "noise_model_from_qiskit_backend",
17
+ "to_stim",
18
+ ]