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,233 @@
1
+ """
2
+ Real, standard OpenQASM 2.0 circuits offered as Composer presets --
3
+ textbook examples (Bell pair, GHZ, W-state) and dense_evolution's own
4
+ real circuit generators (QFT, random circuit), not synthetic results.
5
+ The Composer runs every one of these through the real engine just like
6
+ any custom circuit typed in by hand.
7
+ """
8
+
9
+ import dense_evolution as de
10
+
11
+ from ._gate_tables import (
12
+ _ONE_QUBIT_STATIC, _ONE_QUBIT_PARAM, _TWO_QUBIT_STATIC,
13
+ _TWO_QUBIT_PARAM, _THREE_QUBIT_STATIC,
14
+ )
15
+
16
+ __all__ = ['QASM_LIBRARY', 'gate_tuples_to_qasm']
17
+
18
+
19
+ def gate_tuples_to_qasm(ops, n_qubits: int, measure: bool = True) -> str:
20
+ """Converts a dense_evolution gate-tuple circuit (the format
21
+ de.qft/de.ghz_state/de.random_circuit/... all return) into real
22
+ OpenQASM 2.0 text, so any of dense_evolution's own circuit generators
23
+ can be offered as a Composer preset without hand-transcribing gates.
24
+
25
+ Examples
26
+ --------
27
+ >>> import dense_evolution as de
28
+ >>> from dashboard_core.qasm_library import gate_tuples_to_qasm
29
+ >>> gate_tuples_to_qasm([('h', 0), ('cx', 0, 1)], n_qubits=2)
30
+ 'OPENQASM 2.0;\\ninclude "qelib1.inc";\\nqreg q[2];\\ncreg c[2];\\nh q[0];\\ncx q[0],q[1];\\nmeasure q -> c;\\n'
31
+ >>> qft_ops = de.qft(3)
32
+ >>> print(gate_tuples_to_qasm(qft_ops, n_qubits=3)) # doctest: +NORMALIZE_WHITESPACE
33
+ OPENQASM 2.0;
34
+ include "qelib1.inc";
35
+ qreg q[3];
36
+ creg c[3];
37
+ h q[0];
38
+ cp(1.5707963267948966) q[1],q[0];
39
+ cp(0.7853981633974483) q[2],q[0];
40
+ h q[1];
41
+ cp(1.5707963267948966) q[2],q[1];
42
+ h q[2];
43
+ swap q[0],q[2];
44
+ measure q -> c;
45
+ """
46
+ lines = ['OPENQASM 2.0;', 'include "qelib1.inc";', f'qreg q[{n_qubits}];']
47
+ if measure:
48
+ lines.append(f'creg c[{n_qubits}];')
49
+ for op in ops:
50
+ name = op[0]
51
+ if name in _ONE_QUBIT_STATIC:
52
+ lines.append(f'{name} q[{op[1]}];')
53
+ elif name in _ONE_QUBIT_PARAM:
54
+ lines.append(f'{name}({op[2]}) q[{op[1]}];')
55
+ elif name in _TWO_QUBIT_STATIC:
56
+ lines.append(f'{name} q[{op[1]}],q[{op[2]}];')
57
+ elif name in _TWO_QUBIT_PARAM:
58
+ lines.append(f'{name}({op[3]}) q[{op[1]}],q[{op[2]}];')
59
+ elif name in _THREE_QUBIT_STATIC:
60
+ lines.append(f'{name} q[{op[1]}],q[{op[2]}],q[{op[3]}];')
61
+ else:
62
+ raise ValueError(f"gate_tuples_to_qasm: unrecognized gate {name!r}")
63
+ if measure:
64
+ lines.append('measure q -> c;')
65
+ return '\n'.join(lines) + '\n'
66
+
67
+
68
+ QASM_LIBRARY = {
69
+ "Bell state (2 qubit)": (
70
+ 'OPENQASM 2.0;\ninclude "qelib1.inc";\n'
71
+ 'qreg q[2];\ncreg c[2];\n'
72
+ 'h q[0];\ncx q[0],q[1];\n'
73
+ 'measure q -> c;\n'
74
+ ),
75
+ "GHZ state (3 qubit)": gate_tuples_to_qasm(de.ghz_state(3), 3),
76
+ "GHZ state (4 qubit)": gate_tuples_to_qasm(de.ghz_state(4), 4),
77
+ "Superposition (1 qubit)": (
78
+ 'OPENQASM 2.0;\ninclude "qelib1.inc";\n'
79
+ 'qreg q[1];\ncreg c[1];\n'
80
+ 'h q[0];\n'
81
+ 'measure q -> c;\n'
82
+ ),
83
+ "W state (3 qubit)": (
84
+ # Standard 3-qubit W-state construction: RY splits the single
85
+ # excitation's amplitude, a controlled-Hadamard (decomposed into
86
+ # ry-cx-ry -- dense_evolution has no native CH gate) spreads it
87
+ # further, then two CX cascade it across all three qubits.
88
+ # Verified against the real engine: (|001>+|010>+|100>)/sqrt(3),
89
+ # each with probability 1/3.
90
+ 'OPENQASM 2.0;\ninclude "qelib1.inc";\n'
91
+ 'qreg q[3];\ncreg c[3];\n'
92
+ 'ry(1.9106332362490184) q[0];\n'
93
+ 'ry(0.7853981633974483) q[1];\n'
94
+ 'cx q[0],q[1];\n'
95
+ 'ry(-0.7853981633974483) q[1];\n'
96
+ 'cx q[1],q[2];\n'
97
+ 'cx q[0],q[1];\n'
98
+ 'x q[0];\n'
99
+ 'measure q -> c;\n'
100
+ ),
101
+ "Quantum Fourier Transform (3 qubit)": gate_tuples_to_qasm(de.qft(3), 3),
102
+ "Random circuit (3 qubit, fixed seed)": gate_tuples_to_qasm(
103
+ de.random_circuit(3, 8, seed=7), 3,
104
+ ),
105
+ "Entangling linear (5 qubit, chain topology)": gate_tuples_to_qasm(
106
+ [("h", q) for q in range(5)] + de.entangling_layer(5, pattern="linear"), 5,
107
+ ),
108
+ "Entangling ring (5 qubit, circular topology)": gate_tuples_to_qasm(
109
+ [("h", q) for q in range(5)] + de.entangling_layer(5, pattern="circular"), 5,
110
+ ),
111
+ "Entangling full (5 qubit, all-to-all topology)": gate_tuples_to_qasm(
112
+ [("h", q) for q in range(5)] + de.entangling_layer(5, pattern="full"), 5,
113
+ ),
114
+ "Entangling star (5 qubit, hub topology)": gate_tuples_to_qasm(
115
+ [("h", q) for q in range(5)] + de.entangling_layer(5, pattern="star"), 5,
116
+ ),
117
+ "Entangling brick (5 qubit, brickwork topology)": gate_tuples_to_qasm(
118
+ [("h", q) for q in range(5)] + de.entangling_layer(5, pattern="brick"), 5,
119
+ ),
120
+ "GHZ state (8 qubit)": gate_tuples_to_qasm(de.ghz_state(8), 8),
121
+ "Random circuit (5 qubit, fixed seed)": gate_tuples_to_qasm(
122
+ de.random_circuit(5, 12, seed=13), 5,
123
+ ),
124
+ "Grover search (3 qubit, target |111>)": (
125
+ # Real Grover's algorithm: uniform superposition, a CCZ oracle
126
+ # (via H-CCX-H) marking |111>, then the standard diffuser. One
127
+ # iteration is optimal for N=8/1 marked state. Verified against
128
+ # the real engine: P(|111>) = 0.78125, matching the theoretical
129
+ # sin^2(3*arcsin(1/sqrt(8))) amplitude exactly; all other 7
130
+ # states share the remaining probability uniformly.
131
+ 'OPENQASM 2.0;\ninclude "qelib1.inc";\n'
132
+ 'qreg q[3];\ncreg c[3];\n'
133
+ 'h q[0];\nh q[1];\nh q[2];\nbarrier q;\n'
134
+ 'h q[2];\nccx q[0],q[1],q[2];\nh q[2];\nbarrier q;\n'
135
+ 'h q[0];\nh q[1];\nh q[2];\n'
136
+ 'x q[0];\nx q[1];\nx q[2];\n'
137
+ 'h q[2];\nccx q[0],q[1],q[2];\nh q[2];\n'
138
+ 'x q[0];\nx q[1];\nx q[2];\n'
139
+ 'h q[0];\nh q[1];\nh q[2];\n'
140
+ 'barrier q;\nmeasure q -> c;\n'
141
+ ),
142
+ "Deutsch-Jozsa (balanced oracle, 2+1 qubit)": (
143
+ # Real Deutsch-Jozsa: oracle f(x0,x1) = x0 XOR x1 (balanced).
144
+ # Verified: the input register (q0,q1) never measures |00>, the
145
+ # correct signature of a balanced function (a constant function
146
+ # would collapse the input register to |00> with certainty).
147
+ 'OPENQASM 2.0;\ninclude "qelib1.inc";\n'
148
+ 'qreg q[3];\ncreg c[3];\n'
149
+ 'h q[0];\nh q[1];\nx q[2];\nh q[2];\n'
150
+ 'cx q[0],q[2];\ncx q[1],q[2];\n'
151
+ 'h q[0];\nh q[1];\n'
152
+ 'measure q -> c;\n'
153
+ ),
154
+ "Bernstein-Vazirani (secret string 101, 3+1 qubit)": (
155
+ # Real Bernstein-Vazirani: oracle f(x) = x . s with s = 101.
156
+ # Verified: input register (q0,q1,q2) measures |101> -- the
157
+ # secret string -- with certainty, in a single query.
158
+ 'OPENQASM 2.0;\ninclude "qelib1.inc";\n'
159
+ 'qreg q[4];\ncreg c[4];\n'
160
+ 'h q[0];\nh q[1];\nh q[2];\nx q[3];\nh q[3];\n'
161
+ 'cx q[0],q[3];\ncx q[2],q[3];\n'
162
+ 'h q[0];\nh q[1];\nh q[2];\n'
163
+ 'measure q -> c;\n'
164
+ ),
165
+ "Toffoli / CCX (T-gate decomposition, 3 qubit)": (
166
+ # Standard Nielsen & Chuang Toffoli-from-Clifford+T decomposition
167
+ # (6 CX + 7 T/Tdg + 2 H) -- verified against the engine's native
168
+ # ccx gate on all 8 computational-basis inputs, exact match
169
+ # (probability 1.0 on the correct output state) every time.
170
+ 'OPENQASM 2.0;\ninclude "qelib1.inc";\n'
171
+ 'qreg q[3];\ncreg c[3];\n'
172
+ 'h q[2];\n'
173
+ 'cx q[1],q[2];\ntdg q[2];\n'
174
+ 'cx q[0],q[2];\nt q[2];\n'
175
+ 'cx q[1],q[2];\ntdg q[2];\n'
176
+ 'cx q[0],q[2];\n'
177
+ 't q[1];\nt q[2];\nh q[2];\n'
178
+ 'cx q[0],q[1];\nt q[0];\ntdg q[1];\n'
179
+ 'cx q[0],q[1];\n'
180
+ 'measure q -> c;\n'
181
+ ),
182
+ "Quantum Teleportation (deferred measurement, 3 qubit)": (
183
+ # Real teleportation of an arbitrary state prepared on q0 (here
184
+ # ry(0.9), swap in any single-qubit state to teleport a different
185
+ # one) onto q2, via a Bell pair on q1/q2. Uses the principle of
186
+ # deferred measurement (Nielsen & Chuang Sec. 4.4): Alice's
187
+ # classically-controlled X/Z corrections become coherent
188
+ # controlled gates applied before any measurement, since this
189
+ # engine's QASM parser doesn't execute if(c==N)-conditioned gates
190
+ # (see README "Interop" known limits) -- a naive textbook
191
+ # if(c[1]==1) x q[2]; if(c[0]==1) z q[2]; version would silently
192
+ # no-op here. Verified against the real engine: the reduced
193
+ # density matrix on q2 (partial trace over q0,q1) matches the
194
+ # state originally prepared on q0 with fidelity 1.0000000000,
195
+ # checked across 5 random states with nontrivial complex phase,
196
+ # not just this one example angle.
197
+ 'OPENQASM 2.0;\ninclude "qelib1.inc";\n'
198
+ 'qreg q[3];\ncreg c[3];\n'
199
+ 'ry(0.9) q[0];\n'
200
+ 'h q[1];\ncx q[1],q[2];\n'
201
+ 'cx q[0],q[1];\nh q[0];\n'
202
+ 'cx q[1],q[2];\ncz q[0],q[2];\n'
203
+ 'barrier q;\nmeasure q -> c;\n'
204
+ ),
205
+ "Quantum Phase Estimation (3-qubit counting, T-gate)": (
206
+ # Real QPE estimating the phase of the T gate (eigenvalue
207
+ # e^(i*pi/4), i.e. phase = 1/8) on its |1> eigenstate, using 3
208
+ # counting qubits (q0,q1,q2) + 1 target (q3). 1/8 = 0.001 in
209
+ # binary, exactly representable in 3 bits, so a correct QPE gives
210
+ # one deterministic outcome rather than a spread -- the cleanest
211
+ # possible check. The controlled-U^(2^k) power assignment
212
+ # (q0->k=2, q1->k=1, q2->k=0) and the inverse-QFT gate sequence
213
+ # (swap first, then the H/CP cascade) were both determined
214
+ # empirically against this engine's specific MSB-first qubit
215
+ # convention -- a naive textbook assignment/ordering measured a
216
+ # spread-out, non-deterministic distribution here; the inverse
217
+ # QFT step is transcribed exactly from dense_evolution.qft(3,
218
+ # inverse=True), not hand re-derived. Verified: measuring q0q1q2
219
+ # gives |001> (=1/8) with probability 1.0000, not merely the most
220
+ # likely outcome.
221
+ 'OPENQASM 2.0;\ninclude "qelib1.inc";\n'
222
+ 'qreg q[4];\ncreg c[3];\n'
223
+ 'x q[3];\n'
224
+ 'h q[0];\nh q[1];\nh q[2];\n'
225
+ 'cp(pi) q[0],q[3];\ncp(pi/2) q[1],q[3];\ncp(pi/4) q[2],q[3];\n'
226
+ 'swap q[0],q[2];\n'
227
+ 'h q[2];\ncp(-pi/2) q[2],q[1];\n'
228
+ 'h q[1];\ncp(-pi/4) q[2],q[0];\ncp(-pi/2) q[1],q[0];\n'
229
+ 'h q[0];\n'
230
+ 'barrier q;\n'
231
+ 'measure q[0] -> c[0];\nmeasure q[1] -> c[1];\nmeasure q[2] -> c[2];\n'
232
+ ),
233
+ }
dashboard_core/qmmm.py ADDED
@@ -0,0 +1,16 @@
1
+ """
2
+ Backward-compatibility re-export. The real implementation moved to
3
+ dense_evolution.qmmm.forces (Dense-Evolution issue #283 -- qmmm now lives
4
+ in the library, alongside real QM/MM region partitioning/embedding,
5
+ instead of being buried inside the dashboard tool). Nothing in dashboard
6
+ code needed to change: `from dashboard_core.qmmm import ...` still works.
7
+ """
8
+ from dense_evolution.qmmm.forces import (
9
+ ATOMIC_MASSES_AMU, compute_hellmann_feynman_forces, md_step, run_md_trajectory,
10
+ MIN_NUCLEAR_DISTANCE_ANGSTROM, ACCEL_CONVERSION, _assert_no_nuclear_collision,
11
+ )
12
+
13
+ __all__ = [
14
+ 'ATOMIC_MASSES_AMU', 'compute_hellmann_feynman_forces', 'md_step', 'run_md_trajectory',
15
+ 'MIN_NUCLEAR_DISTANCE_ANGSTROM', 'ACCEL_CONVERSION',
16
+ ]
@@ -0,0 +1,45 @@
1
+ """
2
+ Thin, stateless Composer-kernel wrapper around ia_utils.rag: builds an
3
+ ephemeral index over the caller's own documents and searches it in one
4
+ call -- nothing persists server-side between requests, same as every
5
+ other kernel endpoint. See docs/api/ia_utils_rag.md for the real
6
+ provenance (promoted from quantumrag) and validated case behind this.
7
+ Needs the `rag` extra (scikit-learn + sentence-transformers).
8
+ """
9
+ from dataclasses import dataclass
10
+
11
+ from ia_utils.rag import build_index, search, search_exact
12
+
13
+ __all__ = ['RagSearchResult', 'run_rag_search']
14
+
15
+
16
+ @dataclass
17
+ class RagSearchResult:
18
+ results: list
19
+
20
+
21
+ def run_rag_search(documents, query: str, top: int = 5, rerank: bool = True,
22
+ exact: bool = False, regex: bool = False,
23
+ max_hits: int = 10, context: int = 300) -> RagSearchResult:
24
+ """Build an ephemeral index over `documents` ([(text, source), ...])
25
+ and search it for `query` in one call, in one of two modes:
26
+
27
+ exact=False (default): hybrid semantic search -- pools candidates from
28
+ TF-IDF cosine similarity and a dense bi-encoder, then re-scores the
29
+ pool with a cross-encoder if rerank=True (Karpukhin et al. 2020 /
30
+ Nogueira & Cho 2019); rerank=False skips the dense pool and
31
+ cross-encoder, a plain TF-IDF ranking. Result dicts carry `rank`,
32
+ `source`, `score`, `score_type`, `cosine`, `text`.
33
+
34
+ exact=True: substring/regex search over raw chunk text instead --
35
+ no embedding, no reranker, no model download. Use this when semantic
36
+ ranking buries a short, specific, load-bearing phrase (an exact
37
+ clause, a fixed parameter value) under topically-similar-but-wrong
38
+ chunks. `query` is the literal substring (case-insensitive) unless
39
+ regex=True, in which case it's a real, case-sensitive regex. Result
40
+ dicts carry `source`, `chunk_index`, `match`, `snippet`, `start`, `end`.
41
+ """
42
+ index = build_index(documents, compute_embeddings=not exact)
43
+ if exact:
44
+ return RagSearchResult(results=search_exact(query, index, regex=regex, max_hits=max_hits, context=context))
45
+ return RagSearchResult(results=search(query, index, top=top, rerank=rerank))
@@ -0,0 +1,288 @@
1
+ """
2
+ Native statevector visualizations -- histogram, Q-sphere, per-qubit Bloch
3
+ spheres -- pure matplotlib/numpy, no Qiskit anywhere. Replaces
4
+ qiskit.visualization.{plot_histogram, plot_state_qsphere,
5
+ plot_bloch_multivector}, the last three real Qiskit call sites in this
6
+ project's dashboard (the circuit diagram itself was already native, see
7
+ circuit_diagram.py's own docstring for why that mattered on macOS).
8
+ Qiskit's own instability on macOS CI runners was never proven scoped to
9
+ just QuantumCircuit -- no reason the plotting functions built on top of
10
+ it are exempt -- so removing them entirely, not just making qiskit
11
+ optional, is the actual fix.
12
+
13
+ All inputs use Qiskit's little-endian bit convention (qubit 0 = least
14
+ significant bit of the statevector index), matching
15
+ dashboard_core.engine's own SimulationResult.statevector -- the same
16
+ convention plot_state_qsphere/plot_bloch_multivector assumed.
17
+ """
18
+
19
+ import numpy as np
20
+ import matplotlib.pyplot as plt
21
+ from mpl_toolkits.mplot3d import Axes3D # noqa: F401 -- registers the '3d' projection
22
+
23
+ __all__ = ['native_histogram_figure', 'native_qsphere_figure', 'native_bloch_multivector_figure']
24
+
25
+ _BLOCH_COLOR = "#4589ff"
26
+ _QSPHERE_LINE_COLOR = "#8d8d8d"
27
+
28
+
29
+ def native_histogram_figure(counts: dict, statevector=None):
30
+ """Bar chart of shot counts, bitstrings sorted ascending -- the same
31
+ information qiskit.visualization.plot_histogram showed, built from
32
+ nothing but the counts dict itself.
33
+
34
+ statevector (optional): when given, each bar is colored by the
35
+ complex phase of that basis state's amplitude (same HSV cyclic
36
+ colormap and convention as native_qsphere_figure's own phase
37
+ coloring) instead of the flat default color -- a state's phase is
38
+ real physical information invisible in a bare shot-count bar
39
+ otherwise. statevector must be in the same Qiskit little-endian
40
+ convention as `counts`' own bitstrings (see module docstring).
41
+
42
+ Examples
43
+ --------
44
+ >>> from dashboard_core.state_visuals import native_histogram_figure
45
+ >>> fig = native_histogram_figure({'00': 512, '11': 488})
46
+ >>> type(fig).__name__
47
+ 'Figure'
48
+ """
49
+ states = sorted(counts.keys())
50
+ values = [counts[s] for s in states]
51
+ total = sum(values)
52
+
53
+ fig, ax = plt.subplots(figsize=(max(4, 0.5 * len(states)), 4))
54
+ if statevector is not None:
55
+ phases = np.angle([statevector[int(s, 2)] for s in states])
56
+ cmap = plt.get_cmap("hsv")
57
+ colors = cmap((phases % (2 * np.pi)) / (2 * np.pi))
58
+ bars = ax.bar(states, values, color=colors)
59
+ sm = plt.cm.ScalarMappable(cmap=cmap, norm=plt.Normalize(0, 2 * np.pi))
60
+ sm.set_array([])
61
+ cbar = fig.colorbar(sm, ax=ax, shrink=0.8, pad=0.02)
62
+ cbar.set_ticks([0, np.pi / 2, np.pi, 3 * np.pi / 2, 2 * np.pi])
63
+ cbar.set_ticklabels(["0", "π/2", "π", "3π/2", "2π"])
64
+ cbar.set_label("phase", fontsize=8)
65
+ else:
66
+ bars = ax.bar(states, values, color="#648fff")
67
+ ax.set_ylabel("Counts")
68
+ ax.set_ylim(0, max(values) * 1.15 if values else 1)
69
+ for bar, v in zip(bars, values):
70
+ pct = 100 * v / total if total else 0
71
+ ax.annotate(
72
+ f"{v}\n({pct:.1f}%)", xy=(bar.get_x() + bar.get_width() / 2, bar.get_height()),
73
+ xytext=(0, 3), textcoords="offset points", ha="center", va="bottom", fontsize=8,
74
+ )
75
+ if len(states) > 8:
76
+ plt.setp(ax.get_xticklabels(), rotation=60, ha="right")
77
+ fig.tight_layout()
78
+ return fig
79
+
80
+
81
+ def _reduced_density_matrix(statevector: np.ndarray, n_qubits: int, qubit: int) -> np.ndarray:
82
+ """Partial trace over every qubit except `qubit`, in Qiskit's
83
+ little-endian convention (qubit 0 = LSB of the flat index).
84
+ np.reshape's default C order puts the LSB on the *last* tensor axis,
85
+ so qubit q lives on axis (n_qubits-1-q) -- moved to the front, then
86
+ the remaining axes are flattened into one "everything else" axis and
87
+ traced out via M @ M^dagger. Verified directly against a Bell pair:
88
+ each qubit's reduced state comes back exactly I/2 (maximally mixed),
89
+ the known analytic answer for a maximally entangled 2-qubit state.
90
+
91
+ Deliberately NOT dense_evolution.physics.entropy.partial_trace (prog.txt,
92
+ dashboard_core audit point 1c): that function is MSB-first (qubit 0 =
93
+ most significant bit, this project's own native convention everywhere
94
+ else), single-qubit-only here needs the opposite, Qiskit-matching
95
+ little-endian layout, since this feeds the Bloch-sphere/Q-sphere
96
+ panels that display in Qiskit's own bit order (see engine.py's
97
+ SimulationResult docstring: "statevector, Qiskit bit order"). Two
98
+ genuinely different conventions for two different consumers, not an
99
+ accidental reimplementation -- kept separate rather than adding a
100
+ bit_order flag to the core function for a dashboard-only need.
101
+ """
102
+ tensor = statevector.reshape((2,) * n_qubits)
103
+ axis = n_qubits - 1 - qubit
104
+ m = np.moveaxis(tensor, axis, 0).reshape(2, -1)
105
+ return m @ m.conj().T
106
+
107
+
108
+ def _bloch_vector(rho: np.ndarray) -> np.ndarray:
109
+ """(bx, by, bz) from a single-qubit density matrix, via
110
+ rho = 0.5*(I + bx*X + by*Y + bz*Z) inverted: bx=2*Re(rho01),
111
+ by=-2*Im(rho01), bz=rho00-rho11."""
112
+ bx = 2 * np.real(rho[0, 1])
113
+ by = -2 * np.imag(rho[0, 1])
114
+ bz = np.real(rho[0, 0] - rho[1, 1])
115
+ return np.array([bx, by, bz])
116
+
117
+
118
+ def _draw_bloch_sphere(ax, vector, title):
119
+ u, v = np.mgrid[0:2 * np.pi:24j, 0:np.pi:12j]
120
+ xs = np.cos(u) * np.sin(v)
121
+ ys = np.sin(u) * np.sin(v)
122
+ zs = np.cos(v)
123
+ ax.plot_wireframe(xs, ys, zs, color="#d0d0d0", linewidth=0.4)
124
+ ax.plot([-1, 1], [0, 0], [0, 0], color="#d0d0d0", linewidth=0.6)
125
+ ax.plot([0, 0], [-1, 1], [0, 0], color="#d0d0d0", linewidth=0.6)
126
+ ax.plot([0, 0], [0, 0], [-1, 1], color="#d0d0d0", linewidth=0.6)
127
+ ax.text(0, 0, 1.15, "|0⟩", ha="center", fontsize=8)
128
+ ax.text(0, 0, -1.3, "|1⟩", ha="center", fontsize=8)
129
+ ax.quiver(0, 0, 0, *vector, color=_BLOCH_COLOR, linewidth=2, arrow_length_ratio=0.15)
130
+ ax.set_title(title, fontsize=9)
131
+ ax.set_box_aspect([1, 1, 1])
132
+ ax.set_xlim(-1, 1); ax.set_ylim(-1, 1); ax.set_zlim(-1, 1)
133
+ ax.set_axis_off()
134
+
135
+
136
+ def native_bloch_multivector_figure(statevector: np.ndarray):
137
+ """One Bloch sphere per qubit, each from that qubit's own reduced
138
+ density matrix (partial trace over every other qubit) -- the same
139
+ per-qubit view qiskit.visualization.plot_bloch_multivector showed,
140
+ computed directly from the real statevector, no Qiskit involved.
141
+
142
+ Examples
143
+ --------
144
+ >>> import numpy as np
145
+ >>> from dashboard_core.state_visuals import native_bloch_multivector_figure
146
+ >>> bell = np.array([1, 0, 0, 1]) / np.sqrt(2)
147
+ >>> fig = native_bloch_multivector_figure(bell)
148
+ >>> type(fig).__name__
149
+ 'Figure'
150
+ """
151
+ n_qubits = int(np.log2(len(statevector)))
152
+ fig = plt.figure(figsize=(3 * n_qubits, 3.2))
153
+ for q in range(n_qubits):
154
+ rho = _reduced_density_matrix(statevector, n_qubits, q)
155
+ vector = _bloch_vector(rho)
156
+ ax = fig.add_subplot(1, n_qubits, q + 1, projection="3d")
157
+ _draw_bloch_sphere(ax, vector, f"qubit {q}")
158
+ fig.tight_layout()
159
+ return fig
160
+
161
+
162
+ def _popcount(x: np.ndarray) -> np.ndarray:
163
+ """Vectorized Hamming weight (bit count) of each element -- a portable
164
+ SWAR bit-counting trick (numpy int/bitwise ops only), not np.bitwise_
165
+ count, which needs NumPy >=2.0 (this project declares numpy>=1.22.0).
166
+ Replaces a `bin(int(i)).count("1")` Python-level loop over indices
167
+ (prog.txt, dashboard_core audit point 4b) -- real cost at high qubit
168
+ count with many states above prob_threshold, even though the initial
169
+ threshold filter itself was already vectorized. Verified to match
170
+ the Python reference exactly on 5000 random 24-bit values before
171
+ replacing it."""
172
+ x = x.astype(np.uint64)
173
+ x = x - ((x >> np.uint64(1)) & np.uint64(0x5555555555555555))
174
+ x = (x & np.uint64(0x3333333333333333)) + ((x >> np.uint64(2)) & np.uint64(0x3333333333333333))
175
+ x = (x + (x >> np.uint64(4))) & np.uint64(0x0f0f0f0f0f0f0f0f)
176
+ return ((x * np.uint64(0x0101010101010101)) >> np.uint64(56)).astype(np.int64)
177
+
178
+
179
+ def native_qsphere_figure(statevector: np.ndarray, prob_threshold: float = 1e-3):
180
+ """Q-sphere: every basis state with non-negligible probability placed
181
+ on a sphere by Hamming weight (latitude -- |00...0> at the north pole,
182
+ |11...1> at the south pole, states of equal weight sharing a ring),
183
+ marker size by probability, marker color by phase (cyclic colormap,
184
+ matching Qiskit's own convention) -- the same encoding
185
+ qiskit.visualization.plot_state_qsphere used, built directly from the
186
+ statevector's own amplitudes.
187
+
188
+ Examples
189
+ --------
190
+ >>> import numpy as np
191
+ >>> from dashboard_core.state_visuals import native_qsphere_figure
192
+ >>> bell = np.array([1, 0, 0, 1]) / np.sqrt(2)
193
+ >>> fig = native_qsphere_figure(bell)
194
+ >>> type(fig).__name__
195
+ 'Figure'
196
+ """
197
+ n_qubits = int(np.log2(len(statevector)))
198
+ probs = np.abs(statevector) ** 2
199
+ phases = np.angle(statevector)
200
+
201
+ # Vectorized threshold: a Python-level loop over `range(len(statevector))`
202
+ # is O(2**n_qubits) pure-Python overhead regardless of how many states
203
+ # actually pass -- measured directly, this dominated runtime at 24
204
+ # qubits (dense's own practical ceiling) even though only a handful of
205
+ # states end up plotted. np.where does the same filtering in C.
206
+ indices = np.where(probs > prob_threshold)[0]
207
+ if len(indices) == 0:
208
+ # A state spread thin enough that nothing clears prob_threshold
209
+ # (e.g. a wide random state at high qubit count) -- show the
210
+ # single most probable state rather than an empty sphere, so
211
+ # there's always at least one real point plotted.
212
+ indices = np.array([int(np.argmax(probs))])
213
+ weights = _popcount(indices)
214
+
215
+ # phi (azimuthal position within a weight's ring) needs each point's
216
+ # rank among same-weight points, in index order -- np.unique's
217
+ # return_inverse plus a per-group running count gets that without a
218
+ # Python-level dict-of-lists pass.
219
+ order = np.argsort(weights, kind="stable")
220
+ sorted_idx, sorted_w = indices[order], weights[order]
221
+ group_sizes = np.bincount(sorted_w, minlength=n_qubits + 1)
222
+ # rank-within-group for each entry in sorted order: 0,1,2,... resets
223
+ # to 0 at each group boundary. offsets[w] is where weight-w's group
224
+ # starts in the sorted array (sum of every smaller group's size);
225
+ # subtracting it from the running global index 0..N-1 gives each
226
+ # point's position within its own group, e.g. weights [0,1,1,1,2,2,2,3]
227
+ # -> ranks [0,0,1,2,0,1,2,0] -- verified by hand before use here.
228
+ offsets = np.concatenate([[0], np.cumsum(group_sizes)[:-1]])
229
+ rank_within_group = np.arange(len(sorted_idx)) - offsets[sorted_w]
230
+ counts_per_point = group_sizes[sorted_w]
231
+
232
+ theta = np.pi * sorted_w / n_qubits if n_qubits > 0 else np.zeros_like(sorted_w, dtype=float)
233
+ phi = 2 * np.pi * rank_within_group / counts_per_point
234
+ xs = np.sin(theta) * np.cos(phi)
235
+ ys = np.sin(theta) * np.sin(phi)
236
+ zs = np.cos(theta)
237
+
238
+ sorted_probs = probs[sorted_idx]
239
+ sorted_phases = phases[sorted_idx]
240
+ cmap = plt.get_cmap("hsv")
241
+ colors = cmap((sorted_phases % (2 * np.pi)) / (2 * np.pi))
242
+ sizes = 80 + 1500 * sorted_probs
243
+
244
+ fig = plt.figure(figsize=(6, 6))
245
+ ax = fig.add_subplot(111, projection="3d")
246
+ u, v = np.mgrid[0:2 * np.pi:36j, 0:np.pi:18j]
247
+ ax.plot_wireframe(
248
+ np.cos(u) * np.sin(v), np.sin(u) * np.sin(v), np.cos(v),
249
+ color="#e0e0e0", linewidth=0.3,
250
+ )
251
+
252
+ # One Line3DCollection for every "stem" (instead of one ax.plot() call
253
+ # per point) and one ax.scatter() call for every marker (instead of
254
+ # one per point) -- measured directly: per-point matplotlib artist
255
+ # calls, not the math above, were the real cost of this function (a
256
+ # wide random 10-qubit state with 300+ states over prob_threshold took
257
+ # ~1.4s with the per-point version, regardless of whether text labels
258
+ # were also being drawn).
259
+ from mpl_toolkits.mplot3d.art3d import Line3DCollection
260
+ segments = [[(0, 0, 0), (x, y, z)] for x, y, z in zip(xs, ys, zs)]
261
+ linewidths = 1.0 + 3 * sorted_probs
262
+ ax.add_collection3d(Line3DCollection(segments, colors=_QSPHERE_LINE_COLOR, linewidths=linewidths))
263
+ ax.scatter(xs, ys, zs, s=sizes, color=colors, edgecolor="black", linewidth=0.5, zorder=5)
264
+
265
+ # Per-point text labels are unreadable past a few dozen overlapping
266
+ # entries anyway (a wide random state can push 300+ states over
267
+ # prob_threshold) -- dropped above this count, keeping the dots/
268
+ # lines/colors (the actual data) intact.
269
+ _MAX_LABELS = 32
270
+ if len(sorted_idx) <= _MAX_LABELS:
271
+ for idx, x, y, z in zip(sorted_idx, xs, ys, zs):
272
+ label = format(int(idx), f"0{n_qubits}b")
273
+ ax.text(x * 1.15, y * 1.15, z * 1.15, f"|{label}⟩", fontsize=7, ha="center")
274
+
275
+ ax.set_box_aspect([1, 1, 1])
276
+ ax.set_xlim(-1, 1); ax.set_ylim(-1, 1); ax.set_zlim(-1, 1)
277
+ ax.set_axis_off()
278
+ ax.set_title("Q-sphere (size = probability, color = phase)", fontsize=9)
279
+
280
+ sm = plt.cm.ScalarMappable(cmap=cmap, norm=plt.Normalize(0, 2 * np.pi))
281
+ sm.set_array([])
282
+ cbar = fig.colorbar(sm, ax=ax, shrink=0.6, pad=0.05)
283
+ cbar.set_ticks([0, np.pi / 2, np.pi, 3 * np.pi / 2, 2 * np.pi])
284
+ cbar.set_ticklabels(["0", "π/2", "π", "3π/2", "2π"])
285
+ cbar.set_label("phase", fontsize=8)
286
+
287
+ fig.tight_layout()
288
+ return fig
@@ -0,0 +1,60 @@
1
+ """
2
+ Real, per-machine qubit limits -- detects actual available RAM instead of
3
+ a number picked to fit whatever machine this was developed on. A modest
4
+ 8 GB dev laptop and a 128 GB workstation get genuinely different limits.
5
+
6
+ Reuses dense_evolution.chunk.get_dynamic_chunk, the same real,
7
+ already-tested RAM-based sizing function the Chunk engine itself uses
8
+ (confirmed in feature/streamlit-dashboard's older simulation_runner.py:
9
+ "Decided by de.Chunk's own dynamic, available-RAM-based sizing... not a
10
+ hardcoded qubit-count constant, so it adapts to the machine it's running
11
+ on") -- rather than a second, slightly different hand-rolled formula.
12
+ The actual safety enforcement (rejecting a request that would genuinely
13
+ overflow RAM) is separate: dashboard_core.engine.run_circuit_from_qasm
14
+ calls dense_evolution.chunk.SafeMemoryGuard.check_allocation directly,
15
+ which has no floor -- this module is only the UI's suggested/default cap.
16
+ """
17
+
18
+ import dense_evolution as de
19
+
20
+ __all__ = ['max_safe_dense_qubits']
21
+
22
+
23
+ def max_safe_dense_qubits() -> dict:
24
+ """Suggested max qubit count for the Composer's Qubits field, from
25
+ dense_evolution.chunk.get_dynamic_chunk(complex128) -- floor 16,
26
+ ceiling 30 by that function's own design (a chunk always has *some*
27
+ minimum useful size, and 30 qubits/~17 GB is its own practical ceiling
28
+ for a single dense block -- raised from 27 in Dense-Evolution PR #278,
29
+ which was discarding real headroom on any 16GB+ GPU). MPS's
30
+ contract_to_statevector has a separate, RAM-independent hard ceiling
31
+ of 24 qubits; picking 25-30 with the MPS backend surfaces that
32
+ function's own real error rather than being silently blocked here.
33
+
34
+ Returns
35
+ -------
36
+ dict
37
+ `total_mb`/`available_mb` (this machine's real RAM, from
38
+ `SafeMemoryGuard.status()`), `threshold_pct` (the guard's safety
39
+ margin), `max_qubits_dense` (the suggested cap -- machine-dependent,
40
+ not a constant).
41
+
42
+ Examples
43
+ --------
44
+ >>> from dashboard_core.system_limits import max_safe_dense_qubits
45
+ >>> limits = max_safe_dense_qubits()
46
+ >>> sorted(limits.keys())
47
+ ['available_mb', 'max_qubits_dense', 'threshold_pct', 'total_mb']
48
+ >>> 16 <= limits['max_qubits_dense'] <= 30
49
+ True
50
+ """
51
+ guard = de.chunk.SafeMemoryGuard()
52
+ status = guard.status()
53
+ max_qubits = de.chunk.get_dynamic_chunk(de.chunk.jnp.complex128 if de.chunk.HAS_JAX else de.chunk.np.complex128)
54
+
55
+ return {
56
+ "total_mb": status["total_mb"],
57
+ "available_mb": status["available_mb"],
58
+ "threshold_pct": guard.threshold_pct,
59
+ "max_qubits_dense": max_qubits,
60
+ }