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,77 @@
1
+ """
2
+ Thin wrappers around dense_evolution.protocols (crypto-q, promoted from
3
+ Dense-Evolution-Discovery issue #189) for the Composer kernel -- see
4
+ docs/api/protocols.md for the real, validated numbers behind each
5
+ protocol. Each function here just calls straight into the real protocol
6
+ module and returns a plain dict; no new cryptographic logic lives here.
7
+ """
8
+ from dataclasses import dataclass
9
+
10
+ from dense_evolution.protocols import bb84 as _bb84
11
+ from dense_evolution.protocols import di_qkd_ghz as _di_qkd_ghz
12
+ from dense_evolution.protocols import dicka_protocol2 as _dicka
13
+
14
+ __all__ = ['Bb84Result', 'run_bb84', 'DiQkdGhzResult', 'run_di_qkd_ghz', 'run_dicka_protocol']
15
+
16
+
17
+ @dataclass
18
+ class Bb84Result:
19
+ n_rounds: int
20
+ p_channel: float
21
+ eve: bool
22
+ qber: float
23
+ sifted_key_length: int
24
+
25
+
26
+ def run_bb84(n_rounds: int, p_channel: float = 0.0, eve: bool = False, seed=None) -> Bb84Result:
27
+ """BB84: prepare -> channel -> measure -> sift -> QBER
28
+ (dense_evolution.protocols.bb84.bb84_run). QBER=0 on a perfect
29
+ channel, `2*p_channel/3` under isotropic depolarizing noise,
30
+ `0.25` under an intercept-resend attack (`eve=True`) -- see
31
+ docs/api/protocols.md for the validated numbers this reproduces."""
32
+ qber, sifted_key_length = _bb84.bb84_run(n_rounds, p_channel, eve=eve, seed=seed)
33
+ return Bb84Result(
34
+ n_rounds=n_rounds, p_channel=p_channel, eve=eve,
35
+ qber=float(qber), sifted_key_length=int(sifted_key_length),
36
+ )
37
+
38
+
39
+ @dataclass
40
+ class DiQkdGhzResult:
41
+ n_rounds: int
42
+ p_dep: float
43
+ win_rate: float
44
+ expected_win_rate: float
45
+ qber_b1: float
46
+ qber_b2: float
47
+
48
+
49
+ def run_di_qkd_ghz(n_rounds: int, p_dep: float = 0.0, seed=None) -> DiQkdGhzResult:
50
+ """Three-party device-independent conference key agreement via GHZ(3)
51
+ (Ribeiro, Murta & Wehner 2018, arXiv:1708.00798;
52
+ dense_evolution.protocols.di_qkd_ghz). `win_rate` is the real,
53
+ simulated Parity-CHSH win rate over `n_rounds` test rounds;
54
+ `expected_win_rate` is the exact theoretical value at this `p_dep`
55
+ (quantum max `0.85355...` at `p_dep=0`) -- comparing the two directly
56
+ checks the simulation against the closed-form prediction, not just
57
+ against itself."""
58
+ win_rate = _di_qkd_ghz.parity_chsh_win_rate(n_rounds, p_dep, seed=seed)
59
+ qber_b1, qber_b2 = _di_qkd_ghz.key_qber(n_rounds, p_dep, seed=seed)
60
+ return DiQkdGhzResult(
61
+ n_rounds=n_rounds, p_dep=p_dep,
62
+ win_rate=float(win_rate), expected_win_rate=float(_di_qkd_ghz.expected_win_rate(p_dep)),
63
+ qber_b1=float(qber_b1), qber_b2=float(qber_b2),
64
+ )
65
+
66
+
67
+ def run_dicka_protocol(n_rounds: int, gamma: float, beta: float, p_dep: float = 0.0, seed=None) -> dict:
68
+ """Full multi-round DICKA structure (Appendix Protocol 2 of Ribeiro,
69
+ Murta & Wehner 2018; dense_evolution.protocols.dicka_protocol2):
70
+ round selection, parameter estimation, and the abort decision.
71
+ Already returns a plain, JSON-safe dict -- see run_protocol's own
72
+ docstring for exactly what each field means. Deliberately does not
73
+ report a secure key length: Theorem 4's exact value depends on a
74
+ numerical optimization the source paper never reduces to closed form."""
75
+ result = _dicka.run_protocol(n_rounds=n_rounds, gamma=gamma, beta=beta, p_dep=p_dep, seed=seed)
76
+ result["aborted"] = bool(result["aborted"])
77
+ return result
@@ -0,0 +1,326 @@
1
+ """
2
+ Real simulation engine for the dashboard.
3
+
4
+ OpenQASM text -> dense_evolution's own QASMParser -> executed on
5
+ dense_evolution's actual DenseSVSimulator/MPSSimulator (not Qiskit's own
6
+ simulator, and not Qiskit's QASM parser either) -> statevector,
7
+ probabilities and shot counts, all reordered into Qiskit's little-endian
8
+ qubit convention so they line up with the Circuit tab's qubit labels and
9
+ with qiskit.visualization's functions (which assume that convention).
10
+
11
+ No Qiskit QuantumCircuit is ever constructed here, not even for the
12
+ Circuit-diagram panel: qiskit.circuit.QuantumCircuit.__init__ itself
13
+ segfaults (SIGSEGV, inside qiskit's own compiled extension) on macOS,
14
+ independent of *how* the circuit is built or what's done with it
15
+ afterwards -- see tests/integration/test_interop.py::TestQiskitInterop for the full
16
+ reproduction story (QuantumCircuit(3) alone, no QASM, no method calls,
17
+ still crashes there). SimulationResult/LargeScaleMPSResult below carry
18
+ the plain gate tuples instead, and dashboard_core.circuit_diagram draws
19
+ the diagram directly from those with plain matplotlib.
20
+
21
+ No synthetic/placeholder data anywhere here: every quantity returned is
22
+ computed from a real run of the real engine.
23
+ """
24
+
25
+ from dataclasses import dataclass
26
+ from functools import lru_cache
27
+ from typing import Optional
28
+
29
+ import numpy as np
30
+
31
+ import dense_evolution as de
32
+
33
+ __all__ = [
34
+ 'SimulationResult', 'run_circuit_from_qasm', 'LargeScaleMPSResult',
35
+ 'run_large_circuit_mps', 'run_bond_convergence_check',
36
+ ]
37
+
38
+
39
+ def _qasm_to_tuples(qasm_text: str):
40
+ """Parses qasm_text with dense_evolution's own QASMParser (never
41
+ Qiskit's) and returns (n_qubits, ops) where ops is the same
42
+ (name, *qubits[, param]) tuple format every other dense_evolution
43
+ entry point (run_circuit, QuantumTranspiler.transpile, ...) accepts."""
44
+ parsed = de.QASMParser().parse(qasm_text)
45
+ return parsed.n_qubits, parsed.to_tuples()
46
+
47
+ # MPSSimulator.contract_to_statevector's own hard ceiling (RAM-independent
48
+ # -- it refuses above this regardless of available memory, since a dense
49
+ # 2**n array stops being the point of using MPS at all). Circuits at or
50
+ # below this go through the normal dense-statevector path (same
51
+ # Probabilities/Q-sphere/Statevector panels as the Dense backend); above
52
+ # it, run_large_circuit_mps is the only real path.
53
+ MPS_DENSE_CONTRACTION_LIMIT = 24
54
+
55
+
56
+ @lru_cache(maxsize=None)
57
+ def _qiskit_bit_order_perm(n_qubits: int) -> np.ndarray:
58
+ """The bit-reverse index permutation itself, cached per n_qubits --
59
+ it's O(2**n_qubits) to build and depends only on n_qubits, so a
60
+ circuit run repeatedly at the same qubit count (the common case: a
61
+ fixed molecule/preset re-run with different parameters) was
62
+ rebuilding an identical array every single call. lru_cache keys on
63
+ the (hashable) n_qubits argument only -- the returned array itself
64
+ doesn't need to be hashable.
65
+
66
+ Vectorized bit-reversal (prog.txt, dashboard_core audit point 1b/4c):
67
+ loops over n_qubits bit positions, not over the 2**n_qubits indices
68
+ themselves like the previous `format(i, ...)[::-1]`-per-index Python
69
+ loop did -- the same fix already applied to
70
+ dense_evolution.interop.qiskit_pennylane's own copy of this exact
71
+ permutation."""
72
+ idx = np.arange(2 ** n_qubits)
73
+ perm = np.zeros_like(idx)
74
+ for b in range(n_qubits):
75
+ perm |= ((idx >> b) & 1) << (n_qubits - 1 - b)
76
+ perm.setflags(write=False) # cached and shared across calls -- never mutate in place
77
+ return perm
78
+
79
+
80
+ def _to_qiskit_bit_order(values: np.ndarray, n_qubits: int) -> np.ndarray:
81
+ """Bit-reverse index order: Dense-Evolution is MSB-first (qubit 0 =
82
+ most significant bit of the index), Qiskit is little-endian (qubit 0
83
+ = least significant bit) -- the same remap dense_evolution.interop
84
+ applies to probabilities internally, used here for the raw complex
85
+ statevector too since it is the identical index permutation."""
86
+ return values[_qiskit_bit_order_perm(n_qubits)]
87
+
88
+
89
+ @dataclass
90
+ class SimulationResult:
91
+ ops: list # dense_evolution gate tuples -- feeds dashboard_core.circuit_diagram for the Circuit tab
92
+ n_qubits: int
93
+ statevector: np.ndarray # complex128, Qiskit bit order
94
+ probabilities: np.ndarray # Qiskit bit order
95
+ counts: dict # Qiskit-style bitstring -> shot count
96
+ noise_model: str = "ideal"
97
+ noise_p: float = 0.0
98
+ fidelity_vs_ideal: Optional[float] = None
99
+ backend: str = "dense"
100
+ mps_max_bond_used: Optional[int] = None
101
+ mps_memory_mb: Optional[float] = None
102
+ mps_avg_jsd: Optional[float] = None
103
+
104
+
105
+ def run_circuit_from_qasm(
106
+ qasm_text: str,
107
+ n_shots: int = 1000,
108
+ seed: Optional[int] = None,
109
+ noise_model: str = "ideal",
110
+ noise_p: float = 0.0,
111
+ backend: str = "dense",
112
+ ) -> SimulationResult:
113
+ """Parse `qasm_text` and run it on a real dense_evolution engine,
114
+ returning every quantity the dashboard's tabs need.
115
+
116
+ noise_model/noise_p: one of dense_evolution.NoiseModel.MODELS
117
+ ('ideal', 'depolarizing', 'bitflip', 'phaseflip', 'amplitude_damping',
118
+ 'combined') applied as a real stochastic Kraus channel to the
119
+ statevector via NoiseModel.apply_to_sv -- not a fabricated decay
120
+ curve, the actual channel math. 'ideal'/p<=0 skips it entirely (and
121
+ leaves SimulationResult.fidelity_vs_ideal as None -- comparing a run
122
+ against itself is not a real quantity). Otherwise fidelity_vs_ideal
123
+ is the real dense_evolution.statevector_fidelity(|<ideal|noisy>|^2)
124
+ between this run's one noisy trajectory and the same circuit's ideal
125
+ statevector, both computed in this same call (not re-simulated by the
126
+ caller).
127
+
128
+ backend: 'dense' (DenseSVSimulator, the default) or 'mps'
129
+ (MPSSimulator -- adaptive SVD-truncated matrix product state, scales
130
+ to far more qubits for low-entanglement circuits; contracted back to
131
+ a dense statevector afterwards so the same Probabilities/Q-sphere/
132
+ Statevector panels work unchanged regardless of which engine ran the
133
+ circuit). Verified to match 'dense' exactly (atol=1e-6) on every
134
+ preset in QASM_LIBRARY, and to run a 12-qubit GHZ state in ~2.5s
135
+ (max_bond=2) where 'dense' would need 2**12 complex amplitudes.
136
+
137
+ Examples
138
+ --------
139
+ >>> from dashboard_core.engine import run_circuit_from_qasm
140
+ >>> qasm = '''
141
+ ... OPENQASM 2.0;
142
+ ... include "qelib1.inc";
143
+ ... qreg q[2];
144
+ ... creg c[2];
145
+ ... h q[0];
146
+ ... cx q[0],q[1];
147
+ ... measure q -> c;
148
+ ... '''
149
+ >>> result = run_circuit_from_qasm(qasm, n_shots=1000, seed=0)
150
+ >>> result.probabilities.round(3).tolist() # Bell pair: only |00> and |11> populated
151
+ [0.5, 0.0, 0.0, 0.5]
152
+ >>> set(result.counts.keys()) <= {'00', '11'} # shot counts only land on those two states
153
+ True
154
+ """
155
+ n_qubits, ops = _qasm_to_tuples(qasm_text)
156
+ if n_qubits < 1:
157
+ raise ValueError("circuit must declare at least 1 qubit")
158
+ if backend not in ("dense", "mps"):
159
+ raise ValueError(f"unknown backend {backend!r}, must be 'dense' or 'mps'")
160
+
161
+ # Both backends end up holding a real 2**n_qubits complex128 dense
162
+ # statevector (Dense always; MPS after contract_to_statevector() for
163
+ # display) -- check against this machine's *actual* free RAM before
164
+ # allocating it, via the same real anti-OOM guard dense_evolution.chunk
165
+ # already uses (15% safety threshold, psutil-backed), rather than
166
+ # letting a manually-typed large QASM circuit (which bypasses the
167
+ # UI's qubit cap entirely) crash the process.
168
+ required_mb = (2 ** n_qubits) * 16 / 1e6
169
+ de.chunk.SafeMemoryGuard().check_allocation(required_mb, context=f"{n_qubits}-qubit statevector")
170
+
171
+ mps_max_bond_used = mps_memory_mb = mps_avg_jsd = None
172
+
173
+ if backend == "mps":
174
+ # MPSSimulator.run_circuit_jit does not auto-transpile (unlike
175
+ # DenseSVSimulator.run_circuit) -- swap/ccx must be decomposed
176
+ # first or it raises on 'swap' not being in its native GATE_IDS
177
+ # (verified directly: the QFT preset's trailing swap fails here
178
+ # without this step).
179
+ transpiled = de.QuantumTranspiler.transpile(ops)
180
+ mps = de.MPSSimulator(n_qubits)
181
+ mps.run_circuit_jit(transpiled)
182
+ sv_native = np.asarray(mps.contract_to_statevector())
183
+ mps_max_bond_used = mps.max_bond_used()
184
+ mps_memory_mb = mps.memory_mb()
185
+ mps_avg_jsd = mps.avg_jsd()
186
+ else:
187
+ sim = de.DenseSVSimulator(n_qubits, use_float32=False)
188
+ sim.run_circuit(ops)
189
+ sv_native = np.asarray(sim.sv)
190
+
191
+ rng = np.random.default_rng(seed)
192
+ fidelity_vs_ideal = None
193
+ if noise_model != "ideal" and noise_p > 0:
194
+ # apply_to_sv mutates its numpy input in-place, so the pre-noise
195
+ # amplitudes have to be copied out first -- this is one stochastic
196
+ # Kraus-channel trajectory (a real single noisy realization, not a
197
+ # density matrix), so the result is still a valid pure state and
198
+ # dense_evolution.statevector_fidelity (the pure-state counterpart
199
+ # to the density-matrix uhlmann_fidelity already used by the ZNE
200
+ # panels below) is the right comparison against the ideal state.
201
+ sv_ideal = sv_native.copy()
202
+ sv_native = de.NoiseModel.apply_to_sv(sv_native, n_qubits, noise_model, noise_p, rng=rng)
203
+ fidelity_vs_ideal = float(de.statevector_fidelity(sv_ideal, sv_native))
204
+
205
+ statevector = _to_qiskit_bit_order(sv_native, n_qubits)
206
+ probabilities = np.abs(statevector) ** 2
207
+
208
+ # sample_counts expects/returns dense_evolution's native MSB-first
209
+ # bitstring convention -- reverse each key to relabel into Qiskit's
210
+ # little-endian convention (same physical samples, just relabeled to
211
+ # match the Circuit/Probabilities tabs' qubit numbering).
212
+ counts_native = de.sample_counts(sv_native, n_shots, rng=rng)
213
+ counts = {key[::-1]: n for key, n in counts_native.items()}
214
+
215
+ return SimulationResult(
216
+ ops=ops,
217
+ n_qubits=n_qubits,
218
+ statevector=statevector,
219
+ probabilities=probabilities,
220
+ counts=counts,
221
+ noise_model=noise_model,
222
+ noise_p=noise_p,
223
+ fidelity_vs_ideal=fidelity_vs_ideal,
224
+ backend=backend,
225
+ mps_max_bond_used=mps_max_bond_used,
226
+ mps_memory_mb=mps_memory_mb,
227
+ mps_avg_jsd=mps_avg_jsd,
228
+ )
229
+
230
+
231
+ @dataclass
232
+ class LargeScaleMPSResult:
233
+ ops: list # dense_evolution gate tuples -- feeds dashboard_core.circuit_diagram for the Circuit tab
234
+ n_qubits: int
235
+ top_k_states: list # [(bitstring, probability), ...] Qiskit bit order, sorted descending
236
+ k_requested: int
237
+ mps_max_bond_used: int
238
+ mps_memory_mb: float
239
+ mps_avg_jsd: float
240
+
241
+
242
+ def run_large_circuit_mps(qasm_text: str, k: int = 32, seed: Optional[int] = None) -> LargeScaleMPSResult:
243
+ """For circuits beyond MPS_DENSE_CONTRACTION_LIMIT qubits, where no
244
+ dense (2**n,) statevector/probabilities array can exist at all: runs
245
+ the real MPS circuit and finds the top-k most probable basis states
246
+ via MPSSimulator.get_top_k_probable_states -- a real greedy beam
247
+ search, EXACT probabilities for the states it finds (not sampled or
248
+ approximated). Verified directly against exact dense diagonalization
249
+ at 10 qubits: every state with non-negligible probability was found,
250
+ matching to 6 decimal places (recall isn't guaranteed complete for
251
+ highly-entangled circuits at fixed k -- see MPSSimulator's own
252
+ docstring -- but the probabilities reported are always exact, never
253
+ estimated).
254
+
255
+ Chosen over MPSSimulator.get_probabilities_sampled for this UI:
256
+ measured directly, sampling 2000 shots takes 130s/257s/582s at
257
+ 30/50/100 qubits (grows with n -- roughly O(n_samples * n_qubits)),
258
+ while get_top_k_probable_states stays under 2s even at 100 qubits.
259
+
260
+ Examples
261
+ --------
262
+ >>> from dashboard_core.engine import run_large_circuit_mps
263
+ >>> qasm = '''
264
+ ... OPENQASM 2.0;
265
+ ... include "qelib1.inc";
266
+ ... qreg q[5];
267
+ ... creg c[5];
268
+ ... h q[0];
269
+ ... cx q[0],q[1];
270
+ ... cx q[1],q[2];
271
+ ... cx q[2],q[3];
272
+ ... cx q[3],q[4];
273
+ ... measure q -> c;
274
+ ... '''
275
+ >>> result = run_large_circuit_mps(qasm, k=4, seed=0) # a 5-qubit GHZ state
276
+ >>> top2 = sorted(result.top_k_states, key=lambda t: -t[1])[:2]
277
+ >>> [bits for bits, _ in top2]
278
+ ['00000', '11111']
279
+ >>> all(abs(p - 0.5) < 1e-9 for _, p in top2)
280
+ True
281
+ """
282
+ n_qubits, raw_ops = _qasm_to_tuples(qasm_text)
283
+ if n_qubits < 1:
284
+ raise ValueError("circuit must declare at least 1 qubit")
285
+
286
+ ops = de.QuantumTranspiler.transpile(raw_ops)
287
+ mps = de.MPSSimulator(n_qubits)
288
+ mps.run_circuit_jit(ops)
289
+
290
+ idx, probs = mps.get_top_k_probable_states(k=k)
291
+
292
+ top_k_states = []
293
+ for i, p in zip(idx.tolist(), probs.tolist()):
294
+ native_bits = format(int(i), f'0{n_qubits}b')
295
+ qiskit_bits = native_bits[::-1] # same MSB-first -> little-endian flip as _to_qiskit_bit_order
296
+ top_k_states.append((qiskit_bits, float(p)))
297
+ top_k_states.sort(key=lambda t: t[1], reverse=True)
298
+
299
+ return LargeScaleMPSResult(
300
+ ops=raw_ops,
301
+ n_qubits=n_qubits,
302
+ top_k_states=top_k_states,
303
+ k_requested=k,
304
+ mps_max_bond_used=mps.max_bond_used(),
305
+ mps_memory_mb=mps.memory_mb(),
306
+ mps_avg_jsd=mps.avg_jsd(),
307
+ )
308
+
309
+
310
+ def run_bond_convergence_check(qasm_text: str, observables: list, bonds: list, tol: float = 1e-3):
311
+ """Runs the circuit at every bond dimension in `bonds` and checks
312
+ whether each observable's expectation value has actually converged
313
+ as bond dimension grows -- a single MPS run's own diagnostics
314
+ (avg_jsd, budget_violations) say nothing about whether a *specific*
315
+ observable has settled, since they are truncation-error proxies
316
+ averaged over the whole state, not the observable itself.
317
+
318
+ Thin wrapper around dense_evolution.backends.mps.bond_convergence:
319
+ parses qasm_text the same way as every other entry point here, then
320
+ delegates. See that function's own docstring for the "undecidable"
321
+ verdict (fires when chi_used at the top bond already equals its own
322
+ cap, i.e. truncation never had headroom to demonstrate convergence).
323
+ """
324
+ n_qubits, raw_ops = _qasm_to_tuples(qasm_text)
325
+ ops = de.QuantumTranspiler.transpile(raw_ops)
326
+ return de.backends.mps.bond_convergence(ops, n_qubits, observables, bonds, tol=tol)
@@ -0,0 +1,114 @@
1
+ """
2
+ Turns the ops list produced by the graphical (drag-and-drop) circuit
3
+ builder component into dense_evolution's own (name, *qubits[, param])
4
+ gate tuples -- the same format dashboard_core.engine already runs on the
5
+ real dense_evolution DenseSVSimulator (via dashboard_core.qasm_library.
6
+ gate_tuples_to_qasm for the QASM round-trip). No separate execution path
7
+ for graphically-built circuits, and no Qiskit QuantumCircuit ever built
8
+ here: they go through the exact same engine as typed OpenQASM.
9
+ """
10
+
11
+ import numpy as np
12
+
13
+ __all__ = ['GATE_PALETTE', 'ops_to_native_tuples']
14
+
15
+ # Palette offered by the drag-and-drop grid (dashboard_core.circuit_builder_component).
16
+ # kind:
17
+ # 'single' -- dropped directly on one qubit's cell
18
+ # 'control' -- the (.) end of a 2-qubit gate; pairs with a 'target' in the same column
19
+ # 'target' -- the other end of a 2-qubit gate, carries which gate (x/y/z -> cx/cy/cz)
20
+ # 'swap' -- two 'swap' markers in the same column form a SWAP
21
+ GATE_PALETTE = [
22
+ {"id": "h", "label": "H", "kind": "single", "gate": "h"},
23
+ {"id": "x", "label": "X", "kind": "single", "gate": "x"},
24
+ {"id": "y", "label": "Y", "kind": "single", "gate": "y"},
25
+ {"id": "z", "label": "Z", "kind": "single", "gate": "z"},
26
+ {"id": "s", "label": "S", "kind": "single", "gate": "s"},
27
+ {"id": "sdg", "label": "S†", "kind": "single", "gate": "sdg"},
28
+ {"id": "t", "label": "T", "kind": "single", "gate": "t"},
29
+ {"id": "tdg", "label": "T†", "kind": "single", "gate": "tdg"},
30
+ {"id": "sx", "label": "√X", "kind": "single", "gate": "sx"},
31
+ {"id": "rx", "label": "Rx(π/2)", "kind": "single", "gate": "rx"},
32
+ {"id": "ry", "label": "Ry(π/2)", "kind": "single", "gate": "ry"},
33
+ {"id": "rz", "label": "Rz(π/2)", "kind": "single", "gate": "rz"},
34
+ {"id": "ctrl", "label": "●", "kind": "control", "gate": None},
35
+ {"id": "tgt_x", "label": "⊕", "kind": "target", "gate": "x"},
36
+ {"id": "tgt_z", "label": "● Z", "kind": "target", "gate": "z"},
37
+ {"id": "tgt_y", "label": "⊗ Y", "kind": "target", "gate": "y"},
38
+ {"id": "swap", "label": "×", "kind": "swap", "gate": None},
39
+ ]
40
+
41
+ _SINGLE_QUBIT_METHODS = {"h", "x", "y", "z", "s", "sdg", "t", "tdg", "sx"}
42
+ _ROTATION_METHODS = {"rx", "ry", "rz"}
43
+
44
+ # Fixed rotation angle for graphically-placed Rx/Ry/Rz -- the grid has no
45
+ # angle-entry UI yet (out of scope for this pass), so these place a real,
46
+ # working pi/2 rotation rather than a fake/placeholder gate.
47
+ _DEFAULT_ROTATION_ANGLE = np.pi / 2
48
+
49
+
50
+ def ops_to_native_tuples(n_qubits: int, ops: list) -> list:
51
+ """Build dense_evolution's own (name, *qubits[, param]) gate tuples
52
+ from the builder's op list -- the same shape dashboard_core.engine's
53
+ QASMParser-based pipeline and qasm_library.gate_tuples_to_qasm both
54
+ already accept, and the same validation the old QuantumCircuit-based
55
+ version did (qubit range, unknown gate name), just performed by hand
56
+ instead of relying on Qiskit's own checks.
57
+
58
+ Parameters
59
+ ----------
60
+ n_qubits : int
61
+ ops : list[dict]
62
+ Each dict has 'gate' (one of the GATE_PALETTE gate ids: h/x/y/z/s/t/
63
+ rx/ry/rz/cx/cy/cz/swap) and 'qubits' (list[int]).
64
+
65
+ Notes
66
+ -----
67
+ A graphically-placed rx/ry/rz always gets a fixed pi/2 rotation angle
68
+ (_DEFAULT_ROTATION_ANGLE, prog.txt dashboard_core audit point 5d) --
69
+ the drag-and-drop grid has no angle-entry UI yet, so this places a
70
+ real, working rotation rather than a fake/placeholder one. A user
71
+ who drops an Rx expecting to pick its own angle should know this is
72
+ fixed, not something this function silently decided for them.
73
+
74
+ Returns
75
+ -------
76
+ list[tuple]
77
+ Pass to qasm_library.gate_tuples_to_qasm(tuples, n_qubits) for the
78
+ QASM text the rest of the Composer already runs on (measure_all
79
+ equivalent included there by default).
80
+
81
+ Examples
82
+ --------
83
+ >>> from dashboard_core.graphical_builder import ops_to_native_tuples
84
+ >>> ops = [{'gate': 'h', 'qubits': [0]}, {'gate': 'cx', 'qubits': [0, 1]}]
85
+ >>> ops_to_native_tuples(2, ops)
86
+ [('h', 0), ('cx', 0, 1)]
87
+ """
88
+ if n_qubits < 1:
89
+ raise ValueError("circuit must have at least 1 qubit")
90
+
91
+ tuples = []
92
+ for op in ops:
93
+ gate = op.get("gate")
94
+ qubits = op.get("qubits", [])
95
+ for q in qubits:
96
+ if not (0 <= q < n_qubits):
97
+ raise ValueError(f"qubit index {q} out of range for {n_qubits}-qubit circuit")
98
+
99
+ if gate in _SINGLE_QUBIT_METHODS:
100
+ (q,) = qubits
101
+ tuples.append((gate, q))
102
+ elif gate in _ROTATION_METHODS:
103
+ (q,) = qubits
104
+ tuples.append((gate, q, _DEFAULT_ROTATION_ANGLE))
105
+ elif gate == "swap":
106
+ a, b = qubits
107
+ tuples.append(("swap", a, b))
108
+ elif gate in ("cx", "cy", "cz"):
109
+ control, target = qubits
110
+ tuples.append((gate, control, target))
111
+ else:
112
+ raise ValueError(f"unknown gate from circuit builder: {gate!r}")
113
+
114
+ return tuples