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.
- dashboard_core/__init__.py +115 -0
- dashboard_core/_gate_tables.py +30 -0
- dashboard_core/band_structure.py +71 -0
- dashboard_core/circuit_builder_component.py +232 -0
- dashboard_core/circuit_diagram.py +216 -0
- dashboard_core/crypto_protocols.py +77 -0
- dashboard_core/engine.py +326 -0
- dashboard_core/graphical_builder.py +114 -0
- dashboard_core/hamiltonians.py +593 -0
- dashboard_core/mass_decomposition_tool.py +47 -0
- dashboard_core/mitigation.py +343 -0
- dashboard_core/native_hf_diagnostics.py +62 -0
- dashboard_core/noise_tools.py +125 -0
- dashboard_core/qasm_library.py +233 -0
- dashboard_core/qmmm.py +16 -0
- dashboard_core/rag_tool.py +45 -0
- dashboard_core/state_visuals.py +288 -0
- dashboard_core/system_limits.py +60 -0
- dashboard_core/vector_healing.py +102 -0
- dashboard_core/visuals.py +158 -0
- dashboard_core/vqe.py +533 -0
- dashboard_core/wormhole.py +580 -0
- dense_evolution/__init__.py +114 -0
- dense_evolution/autodiff.py +10 -0
- dense_evolution/backends/__init__.py +5 -0
- dense_evolution/backends/chunk/__init__.py +37 -0
- dense_evolution/backends/chunk/_engine_imports.py +57 -0
- dense_evolution/backends/chunk/circuit_chunker.py +55 -0
- dense_evolution/backends/chunk/core.py +432 -0
- dense_evolution/backends/chunk/disk_overflow.py +232 -0
- dense_evolution/backends/chunk/geometry.py +95 -0
- dense_evolution/backends/chunk/guard.py +190 -0
- dense_evolution/backends/chunk/kernels.py +531 -0
- dense_evolution/backends/mps.py +1569 -0
- dense_evolution/backends/statevector.py +616 -0
- dense_evolution/chunk.py +25 -0
- dense_evolution/circuits/__init__.py +20 -0
- dense_evolution/circuits/compiler.py +488 -0
- dense_evolution/circuits/diagram.py +94 -0
- dense_evolution/circuits/gates.py +91 -0
- dense_evolution/circuits/parser.py +632 -0
- dense_evolution/circuits/qft.py +66 -0
- dense_evolution/circuits/random_circuit.py +85 -0
- dense_evolution/circuits/registry.py +74 -0
- dense_evolution/circuits/topology.py +79 -0
- dense_evolution/circuits/trotter.py +265 -0
- dense_evolution/circuits/uccsd.py +275 -0
- dense_evolution/cli.py +199 -0
- dense_evolution/compiler.py +9 -0
- dense_evolution/config.py +49 -0
- dense_evolution/drawing.py +10 -0
- dense_evolution/entropy.py +9 -0
- dense_evolution/fermions.py +9 -0
- dense_evolution/gates.py +9 -0
- dense_evolution/harrison_tb.py +16 -0
- dense_evolution/healing.py +18 -0
- dense_evolution/interop/__init__.py +18 -0
- dense_evolution/interop/qiskit_pennylane.py +406 -0
- dense_evolution/measurement.py +10 -0
- dense_evolution/mitigation/__init__.py +54 -0
- dense_evolution/mitigation/healing.py +215 -0
- dense_evolution/mitigation/kl_divergence.py +93 -0
- dense_evolution/mitigation/magic_entropy.py +163 -0
- dense_evolution/mitigation/magic_entropy_shadows.py +262 -0
- dense_evolution/mitigation/renyi.py +168 -0
- dense_evolution/mitigation/stabilizer_renyi_entropy.py +103 -0
- dense_evolution/mitigation/zne.py +990 -0
- dense_evolution/mps.py +9 -0
- dense_evolution/native_hf/__init__.py +26 -0
- dense_evolution/native_hf/_libcint/LICENSE-libcint +10 -0
- dense_evolution/native_hf/_libcint/libdecint.dll +0 -0
- dense_evolution/native_hf/assembly.py +304 -0
- dense_evolution/native_hf/basis.py +117 -0
- dense_evolution/native_hf/boys.py +35 -0
- dense_evolution/native_hf/bridge.py +112 -0
- dense_evolution/native_hf/cartesian.py +64 -0
- dense_evolution/native_hf/coulomb.py +196 -0
- dense_evolution/native_hf/differentiable.py +53 -0
- dense_evolution/native_hf/gaussians.py +79 -0
- dense_evolution/native_hf/kinetic.py +52 -0
- dense_evolution/native_hf/libcint_bridge.py +167 -0
- dense_evolution/native_hf/overlap.py +91 -0
- dense_evolution/native_hf/scf.py +404 -0
- dense_evolution/noise/__init__.py +79 -0
- dense_evolution/noise/coherent_attack.py +264 -0
- dense_evolution/noise/cosmic_ray.py +61 -0
- dense_evolution/noise/density_matrix_channels.py +78 -0
- dense_evolution/noise/differentiable.py +66 -0
- dense_evolution/noise/kraus/__init__.py +6 -0
- dense_evolution/noise/kraus/amplitude_damping.py +47 -0
- dense_evolution/noise/kraus/bitflip.py +22 -0
- dense_evolution/noise/kraus/combined.py +16 -0
- dense_evolution/noise/kraus/depolarizing.py +47 -0
- dense_evolution/noise/kraus/ideal.py +10 -0
- dense_evolution/noise/kraus/phaseflip.py +21 -0
- dense_evolution/noise/kraus_channels.py +285 -0
- dense_evolution/noise/oscillating.py +32 -0
- dense_evolution/noise/pink.py +80 -0
- dense_evolution/observables.py +11 -0
- dense_evolution/parser.py +9 -0
- dense_evolution/physics/__init__.py +27 -0
- dense_evolution/physics/entropy.py +161 -0
- dense_evolution/physics/fermions.py +322 -0
- dense_evolution/physics/observables.py +523 -0
- dense_evolution/physics/qec.py +1113 -0
- dense_evolution/physics/spectral.py +143 -0
- dense_evolution/physics/states.py +43 -0
- dense_evolution/protocols/__init__.py +27 -0
- dense_evolution/protocols/bb84.py +133 -0
- dense_evolution/protocols/di_qkd_ghz.py +199 -0
- dense_evolution/protocols/dicka_protocol2.py +124 -0
- dense_evolution/qec.py +20 -0
- dense_evolution/qft.py +9 -0
- dense_evolution/qmmm/__init__.py +13 -0
- dense_evolution/qmmm/ase_bridge.py +97 -0
- dense_evolution/qmmm/forces.py +388 -0
- dense_evolution/qmmm/propagation.py +80 -0
- dense_evolution/qmmm/region.py +137 -0
- dense_evolution/random_circuit.py +15 -0
- dense_evolution/registry.py +9 -0
- dense_evolution/simulator.py +10 -0
- dense_evolution/solvers/__init__.py +19 -0
- dense_evolution/solvers/autodiff.py +169 -0
- dense_evolution/solvers/harrison_tb.py +189 -0
- dense_evolution/solvers/vhd_tb.py +187 -0
- dense_evolution/states.py +9 -0
- dense_evolution/topology.py +9 -0
- dense_evolution/trotter.py +9 -0
- dense_evolution/utils/__init__.py +13 -0
- dense_evolution/utils/drawing.py +101 -0
- dense_evolution/utils/mass_decomposition.py +246 -0
- dense_evolution/utils/measurement.py +94 -0
- dense_evolution/vhd_tb.py +16 -0
- dense_evolution-8.3.0.dist-info/METADATA +366 -0
- dense_evolution-8.3.0.dist-info/RECORD +165 -0
- dense_evolution-8.3.0.dist-info/WHEEL +5 -0
- dense_evolution-8.3.0.dist-info/entry_points.txt +2 -0
- dense_evolution-8.3.0.dist-info/licenses/license.md +58 -0
- dense_evolution-8.3.0.dist-info/top_level.txt +5 -0
- ia_utils/__init__.py +0 -0
- ia_utils/adversarial_vector_attack.py +196 -0
- ia_utils/rag.py +288 -0
- ia_utils/vector_healing.py +399 -0
- local_site/__init__.py +0 -0
- local_site/app/__init__.py +0 -0
- local_site/app/server.py +1009 -0
- mcp_server/__init__.py +0 -0
- mcp_server/client.py +324 -0
- mcp_server/config.py +32 -0
- mcp_server/models.py +347 -0
- mcp_server/molecules.py +71 -0
- mcp_server/server.py +119 -0
- mcp_server/tools/__init__.py +0 -0
- mcp_server/tools/chemistry_tools.py +225 -0
- mcp_server/tools/circuit_tools.py +83 -0
- mcp_server/tools/crypto_tools.py +66 -0
- mcp_server/tools/mitigation_tools.py +81 -0
- mcp_server/tools/noise_tools.py +60 -0
- mcp_server/tools/retrieval_tools.py +44 -0
- mcp_server/tools/system_tools.py +149 -0
- mcp_server/tools/wormhole_tools.py +142 -0
- mcp_server/utils/__init__.py +0 -0
- mcp_server/utils/cache.py +55 -0
- mcp_server/utils/images.py +67 -0
- 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
|
+
}
|