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,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
|
dashboard_core/engine.py
ADDED
|
@@ -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
|