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,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dashboard_core -- real compute + visualization layer for app_dashboard.py.
|
|
3
|
+
|
|
4
|
+
Rebuilt from scratch on the structure of IBM Quantum Composer (circuit
|
|
5
|
+
editor, statevector, probabilities, Q-sphere), wired to dense_evolution's
|
|
6
|
+
actual DenseSVSimulator and to Qiskit's own real visualization functions
|
|
7
|
+
-- no hand-rolled plotting, no synthetic data. The previous dashboard_core
|
|
8
|
+
(VQE, molecular Hamiltonians, mitigation, AI vector-healing) lives intact
|
|
9
|
+
on the feature/streamlit-dashboard and feature/ipywidgets-dash-panel
|
|
10
|
+
branches and will be reintegrated selectively once this base is solid.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import dense_evolution as _de
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def enable_dashboard_precision():
|
|
17
|
+
"""Enables float64 precision for the dashboard (prog.txt,
|
|
18
|
+
dashboard_core audit point 2). dense_evolution no longer forces
|
|
19
|
+
jax_enable_x64 as an import-time side effect (see
|
|
20
|
+
dense_evolution/config.py) -- it's enabled lazily, only by the
|
|
21
|
+
specific call that needs complex128 (e.g. DenseSVSimulator). Some
|
|
22
|
+
paths here (MPSSimulator-only branches in engine.py, Chunk usage in
|
|
23
|
+
system_limits.py/wormhole.py) never construct a DenseSVSimulator, so
|
|
24
|
+
this dashboard -- which always wants float64 -- must set it
|
|
25
|
+
explicitly rather than relying on that now-removed side effect.
|
|
26
|
+
|
|
27
|
+
This used to run as a bare module-level `_de.set_precision(True)`
|
|
28
|
+
call here, so merely `from dashboard_core.circuit_diagram import
|
|
29
|
+
draw_native_circuit_diagram` paid the x64-enable cost even though
|
|
30
|
+
that submodule never touches precision -- exactly the import-time
|
|
31
|
+
side effect dense_evolution's own config.py was written to eliminate
|
|
32
|
+
(see its module docstring), just not yet applied one level up here.
|
|
33
|
+
Call this explicitly from the dashboard's own entry point
|
|
34
|
+
(tools/dashboard/app.py) instead."""
|
|
35
|
+
_de.set_precision(True)
|
|
36
|
+
|
|
37
|
+
from .qasm_library import QASM_LIBRARY, gate_tuples_to_qasm
|
|
38
|
+
from .engine import (
|
|
39
|
+
SimulationResult, run_circuit_from_qasm,
|
|
40
|
+
LargeScaleMPSResult, run_large_circuit_mps, MPS_DENSE_CONTRACTION_LIMIT,
|
|
41
|
+
run_bond_convergence_check,
|
|
42
|
+
)
|
|
43
|
+
from .visuals import (
|
|
44
|
+
draw_circuit_figure, histogram_figure, qsphere_figure, bloch_multivector_figure,
|
|
45
|
+
energy_landscape_figure, zne_bar_figure, qec_syndrome_map_figure,
|
|
46
|
+
)
|
|
47
|
+
from .graphical_builder import GATE_PALETTE, ops_to_native_tuples
|
|
48
|
+
from .circuit_builder_component import mount_circuit_builder
|
|
49
|
+
from .hamiltonians import (
|
|
50
|
+
MOLECULE_CATALOG, build_molecular_hamiltonian, get_compatible_molecules,
|
|
51
|
+
get_all_molecules, get_molecule_n_qubits,
|
|
52
|
+
get_molecular_hamiltonian_matrix, ground_state_energy, ground_state_energy_sparse,
|
|
53
|
+
linear_chain_geometry, ring_geometry, mix_hamiltonians,
|
|
54
|
+
)
|
|
55
|
+
from .mitigation import (
|
|
56
|
+
MitigationResult, run_zne_mitigation, DensityMatrixZNEResult, run_density_matrix_zne,
|
|
57
|
+
CoherenceZNEResult, run_coherence_zne_mitigation,
|
|
58
|
+
)
|
|
59
|
+
from .crypto_protocols import (
|
|
60
|
+
Bb84Result, run_bb84, DiQkdGhzResult, run_di_qkd_ghz, run_dicka_protocol,
|
|
61
|
+
)
|
|
62
|
+
from .native_hf_diagnostics import NativeHfDiagnosticsResult, run_native_hf_diagnostics
|
|
63
|
+
from .mass_decomposition_tool import MassDecompositionResult, run_mass_decomposition
|
|
64
|
+
from .rag_tool import RagSearchResult, run_rag_search
|
|
65
|
+
from .system_limits import max_safe_dense_qubits
|
|
66
|
+
from .vqe import run_vqe, scan_hardware_efficient_energy_landscape
|
|
67
|
+
from .qmmm import (
|
|
68
|
+
ATOMIC_MASSES_AMU,
|
|
69
|
+
compute_hellmann_feynman_forces, md_step, run_md_trajectory,
|
|
70
|
+
)
|
|
71
|
+
from .wormhole import (
|
|
72
|
+
build_sparse_syk_terms, commuting_pair_count, select_good_instance,
|
|
73
|
+
run_wormhole_protocol, run_wormhole_protocol_trotter,
|
|
74
|
+
)
|
|
75
|
+
from .vector_healing import VectorHealingResult, run_vector_healing
|
|
76
|
+
from .noise_tools import (
|
|
77
|
+
CosmicRayBurstResult, run_cosmic_ray_burst,
|
|
78
|
+
OscillatingNoiseResult, run_oscillating_noise,
|
|
79
|
+
DensityMatrixChannelResult, run_density_matrix_channel,
|
|
80
|
+
)
|
|
81
|
+
from .band_structure import scan_bands_along_path, band_structure_figure
|
|
82
|
+
|
|
83
|
+
__all__ = [
|
|
84
|
+
'enable_dashboard_precision',
|
|
85
|
+
'QASM_LIBRARY', 'gate_tuples_to_qasm',
|
|
86
|
+
'SimulationResult', 'run_circuit_from_qasm',
|
|
87
|
+
'LargeScaleMPSResult', 'run_large_circuit_mps', 'MPS_DENSE_CONTRACTION_LIMIT',
|
|
88
|
+
'run_bond_convergence_check',
|
|
89
|
+
'draw_circuit_figure', 'histogram_figure', 'qsphere_figure', 'bloch_multivector_figure',
|
|
90
|
+
'energy_landscape_figure', 'zne_bar_figure', 'qec_syndrome_map_figure',
|
|
91
|
+
'GATE_PALETTE', 'ops_to_native_tuples',
|
|
92
|
+
'mount_circuit_builder',
|
|
93
|
+
'MOLECULE_CATALOG', 'build_molecular_hamiltonian', 'get_compatible_molecules',
|
|
94
|
+
'get_all_molecules', 'get_molecule_n_qubits',
|
|
95
|
+
'get_molecular_hamiltonian_matrix', 'ground_state_energy', 'ground_state_energy_sparse',
|
|
96
|
+
'linear_chain_geometry', 'ring_geometry', 'mix_hamiltonians',
|
|
97
|
+
'MitigationResult', 'run_zne_mitigation',
|
|
98
|
+
'DensityMatrixZNEResult', 'run_density_matrix_zne',
|
|
99
|
+
'CoherenceZNEResult', 'run_coherence_zne_mitigation',
|
|
100
|
+
'Bb84Result', 'run_bb84', 'DiQkdGhzResult', 'run_di_qkd_ghz', 'run_dicka_protocol',
|
|
101
|
+
'NativeHfDiagnosticsResult', 'run_native_hf_diagnostics',
|
|
102
|
+
'MassDecompositionResult', 'run_mass_decomposition',
|
|
103
|
+
'RagSearchResult', 'run_rag_search',
|
|
104
|
+
'max_safe_dense_qubits',
|
|
105
|
+
'run_vqe', 'scan_hardware_efficient_energy_landscape',
|
|
106
|
+
'ATOMIC_MASSES_AMU',
|
|
107
|
+
'compute_hellmann_feynman_forces', 'md_step', 'run_md_trajectory',
|
|
108
|
+
'build_sparse_syk_terms', 'commuting_pair_count', 'select_good_instance',
|
|
109
|
+
'run_wormhole_protocol', 'run_wormhole_protocol_trotter',
|
|
110
|
+
'VectorHealingResult', 'run_vector_healing',
|
|
111
|
+
'CosmicRayBurstResult', 'run_cosmic_ray_burst',
|
|
112
|
+
'OscillatingNoiseResult', 'run_oscillating_noise',
|
|
113
|
+
'DensityMatrixChannelResult', 'run_density_matrix_channel',
|
|
114
|
+
'scan_bands_along_path', 'band_structure_figure',
|
|
115
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Gate-name classification, shared by qasm_library.py and
|
|
2
|
+
circuit_diagram.py (prog.txt, dashboard_core audit point 2). Was: two
|
|
3
|
+
independently hand-copied sets of the same 5 constants, each with a
|
|
4
|
+
comment claiming to mirror a different, non-existent "canonical" source
|
|
5
|
+
(dense_evolution/compiler.py's gate categories, dashboard_core/engine.py's
|
|
6
|
+
dispatch tables) -- neither actually defines these as a literal data
|
|
7
|
+
structure. The two copies had already drifted apart in practice:
|
|
8
|
+
circuit_diagram.py's _ONE_QUBIT_PARAM was missing the 'u1'/'phase'
|
|
9
|
+
aliases qasm_library.py's copy had, so a circuit using either of those
|
|
10
|
+
two (both real gate names dense_evolution.circuits.gates.GATE_IDS
|
|
11
|
+
recognizes, both mapped to the same gate as 'p') crashed
|
|
12
|
+
draw_native_circuit_diagram with "unsupported gate for native circuit
|
|
13
|
+
diagram" -- a real bug, not just a duplication risk, fixed by making
|
|
14
|
+
this the one place these sets are defined.
|
|
15
|
+
|
|
16
|
+
Matches dense_evolution.circuits.gates.GATE_IDS's own gate-name
|
|
17
|
+
vocabulary (the actual source of truth for which names QASMParser
|
|
18
|
+
accepts) -- verified directly, not assumed, before writing this down.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
'_ONE_QUBIT_STATIC', '_ONE_QUBIT_PARAM', '_TWO_QUBIT_STATIC',
|
|
23
|
+
'_TWO_QUBIT_PARAM', '_THREE_QUBIT_STATIC',
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
_ONE_QUBIT_STATIC = {"h", "x", "y", "z", "s", "sdg", "t", "tdg", "sx", "id"}
|
|
27
|
+
_ONE_QUBIT_PARAM = {"rx", "ry", "rz", "p", "u1", "phase"}
|
|
28
|
+
_TWO_QUBIT_STATIC = {"cx", "cz", "cy", "swap"}
|
|
29
|
+
_TWO_QUBIT_PARAM = {"cp", "crz"}
|
|
30
|
+
_THREE_QUBIT_STATIC = {"ccx"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Real sp3s* tight-binding band structure along a k-path
|
|
3
|
+
(dense_evolution.sp3s_star_hamiltonian, the same real Hamiltonian
|
|
4
|
+
direct_gap_at_gamma/band_extrema_along_path already use -- all 10 bands
|
|
5
|
+
here, not just the VBM/CBM pair those two track), plus an interactive
|
|
6
|
+
Plotly figure with real hover tooltips (k-point and energy per band).
|
|
7
|
+
|
|
8
|
+
This is the one interactive (hover/zoom) visualization in the dashboard;
|
|
9
|
+
everywhere else in dashboard_core.visuals uses static matplotlib. The
|
|
10
|
+
choice here is unrelated to that module's own reason for staying
|
|
11
|
+
matplotlib-only (Qiskit's macOS instability, see visuals.py) -- this is
|
|
12
|
+
purely about wanting real per-point hover data that a static image
|
|
13
|
+
cannot provide, and needed adding plotly as a declared dependency
|
|
14
|
+
(pyproject.toml's `dashboard` extra) since it was only ever an
|
|
15
|
+
incidental transitive package before.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
import plotly.graph_objects as go
|
|
20
|
+
|
|
21
|
+
import dense_evolution as de
|
|
22
|
+
|
|
23
|
+
__all__ = ['scan_bands_along_path', 'band_structure_figure']
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def scan_bands_along_path(material, k_start, k_end, n_points=201):
|
|
27
|
+
"""All 10 sp3s* tight-binding bands (real eigenvalues of
|
|
28
|
+
dense_evolution.sp3s_star_hamiltonian at each sampled k-point, not
|
|
29
|
+
just the single VBM/CBM pair band_extrema_along_path tracks) along
|
|
30
|
+
the straight line from k_start to k_end, both in the same 2*pi/a
|
|
31
|
+
cubic-axis units as sp3s_star_hamiltonian itself.
|
|
32
|
+
|
|
33
|
+
Returns (t, bands): t is the fractional position along the path
|
|
34
|
+
(0..1, n_points), bands is a (n_points, 10) array with band j's
|
|
35
|
+
energy at t[i] in bands[i, j] (sorted ascending at every k-point).
|
|
36
|
+
"""
|
|
37
|
+
k_start = np.asarray(k_start, dtype=float)
|
|
38
|
+
k_end = np.asarray(k_end, dtype=float)
|
|
39
|
+
t = np.linspace(0.0, 1.0, n_points)
|
|
40
|
+
ks = k_start[None, :] + t[:, None] * (k_end - k_start)[None, :]
|
|
41
|
+
|
|
42
|
+
bands = np.empty((n_points, 10))
|
|
43
|
+
for i, k in enumerate(ks):
|
|
44
|
+
bands[i] = np.sort(np.linalg.eigvalsh(de.sp3s_star_hamiltonian(k, material)).real)
|
|
45
|
+
return t, bands
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def band_structure_figure(t, bands, material_name, k_start_label='Γ', k_end_label='X'):
|
|
49
|
+
"""Interactive Plotly band-structure plot: one line per band, real
|
|
50
|
+
hover tooltip per point showing the exact k-path position and
|
|
51
|
+
energy (not read off a static image). Bands 3/4 (0-indexed, the
|
|
52
|
+
valence-band maximum / conduction-band minimum pair
|
|
53
|
+
band_extrema_along_path itself tracks) are highlighted."""
|
|
54
|
+
fig = go.Figure()
|
|
55
|
+
n_bands = bands.shape[1]
|
|
56
|
+
for j in range(n_bands):
|
|
57
|
+
is_frontier = j in (3, 4)
|
|
58
|
+
fig.add_trace(go.Scatter(
|
|
59
|
+
x=t, y=bands[:, j], mode='lines',
|
|
60
|
+
name=f'banda {j}' + (' (VBM)' if j == 3 else ' (CBM)' if j == 4 else ''),
|
|
61
|
+
line=dict(width=3 if is_frontier else 1, color='#d62728' if j == 3 else '#1f77b4' if j == 4 else '#999999'),
|
|
62
|
+
hovertemplate=f'banda {j}<br>t=%{{x:.3f}}<br>E=%{{y:.4f}} eV<extra></extra>',
|
|
63
|
+
))
|
|
64
|
+
fig.update_layout(
|
|
65
|
+
title=f'Struttura a bande sp3s* -- {material_name} ({k_start_label}→{k_end_label})',
|
|
66
|
+
xaxis_title=f'{k_start_label} → {k_end_label}',
|
|
67
|
+
yaxis_title='Energia (eV)',
|
|
68
|
+
hovermode='closest',
|
|
69
|
+
template='plotly_white',
|
|
70
|
+
)
|
|
71
|
+
return fig
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Graphical (drag-and-drop) circuit builder -- the fourth pillar of IBM
|
|
3
|
+
Quantum Composer's layout (graphical editor + code editor + Statevector +
|
|
4
|
+
Probabilities), the one missing piece the rest of dashboard_core didn't
|
|
5
|
+
have yet.
|
|
6
|
+
|
|
7
|
+
Built on Streamlit's own native bidirectional component API
|
|
8
|
+
(st.components.v2.component -- real HTML/CSS/JS running in the browser,
|
|
9
|
+
not a hand-waved "coming soon" panel). Gates are dragged from a palette
|
|
10
|
+
onto a qubit x time-step grid using plain HTML5 drag-and-drop; every drop
|
|
11
|
+
re-serializes the grid into an ops list and pushes it back to Python via
|
|
12
|
+
setStateValue, so Streamlit always has the real current state of what's
|
|
13
|
+
on the canvas.
|
|
14
|
+
|
|
15
|
+
The ops list this emits is consumed by dashboard_core.graphical_builder.
|
|
16
|
+
ops_to_native_tuples, which builds dense_evolution's own gate tuples
|
|
17
|
+
(no Qiskit) executed by the exact same dense_evolution engine as typed
|
|
18
|
+
OpenQASM -- no separate/fake execution path for graphically-built
|
|
19
|
+
circuits.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .graphical_builder import GATE_PALETTE
|
|
23
|
+
|
|
24
|
+
__all__ = ['mount_circuit_builder']
|
|
25
|
+
|
|
26
|
+
_CSS = """
|
|
27
|
+
.cb-root { display: flex; flex-direction: column; gap: 10px; font-family: monospace; }
|
|
28
|
+
.cb-palette { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px; border: 1px solid var(--st-border-color, #444); border-radius: 6px; }
|
|
29
|
+
.cb-chip {
|
|
30
|
+
padding: 4px 10px; border-radius: 4px; cursor: grab; user-select: none;
|
|
31
|
+
background: var(--st-secondary-background-color, #262730);
|
|
32
|
+
color: var(--st-text-color, #fafafa);
|
|
33
|
+
border: 1px solid var(--st-border-color, #444);
|
|
34
|
+
font-size: 14px;
|
|
35
|
+
}
|
|
36
|
+
.cb-chip:active { cursor: grabbing; }
|
|
37
|
+
.cb-chip[data-kind="control"] { background: #3a5; color: #fff; }
|
|
38
|
+
.cb-chip[data-kind="target"] { background: #35a; color: #fff; }
|
|
39
|
+
.cb-chip[data-kind="swap"] { background: #a53; color: #fff; }
|
|
40
|
+
.cb-toolbar { display: flex; gap: 8px; align-items: center; }
|
|
41
|
+
.cb-toolbar button {
|
|
42
|
+
padding: 4px 10px; border-radius: 4px; cursor: pointer;
|
|
43
|
+
background: var(--st-secondary-background-color, #262730);
|
|
44
|
+
color: var(--st-text-color, #fafafa);
|
|
45
|
+
border: 1px solid var(--st-border-color, #444);
|
|
46
|
+
}
|
|
47
|
+
.cb-grid { display: grid; gap: 2px; align-items: center; position: relative; }
|
|
48
|
+
.cb-qlabel { font-size: 12px; opacity: 0.75; text-align: right; padding-right: 6px; }
|
|
49
|
+
.cb-cell {
|
|
50
|
+
width: 40px; height: 32px; border: 1px solid var(--st-border-color, #444);
|
|
51
|
+
border-radius: 4px; display: flex; align-items: center; justify-content: center;
|
|
52
|
+
font-size: 13px; cursor: pointer;
|
|
53
|
+
background: var(--st-background-color, #0e1117);
|
|
54
|
+
color: var(--st-text-color, #fafafa);
|
|
55
|
+
}
|
|
56
|
+
.cb-cell.cb-filled { background: var(--st-secondary-background-color, #262730); font-weight: bold; }
|
|
57
|
+
.cb-cell.cb-over { outline: 2px dashed #4af; }
|
|
58
|
+
.cb-count { font-size: 12px; opacity: 0.75; }
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
_HTML = """
|
|
62
|
+
<div class="cb-toolbar">
|
|
63
|
+
<span class="cb-count" data-role="count">0 gates placed</span>
|
|
64
|
+
<button data-role="clear">Clear grid</button>
|
|
65
|
+
</div>
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
_JS = r"""
|
|
69
|
+
export default function(component) {
|
|
70
|
+
const { data, setStateValue, parentElement } = component;
|
|
71
|
+
const nQubits = Math.max(1, data.n_qubits || 1);
|
|
72
|
+
const nCols = Math.max(1, data.n_columns || 12);
|
|
73
|
+
const palette = data.palette || [];
|
|
74
|
+
|
|
75
|
+
const grid = Array.from({ length: nQubits }, () => Array(nCols).fill(null));
|
|
76
|
+
const cellEls = Array.from({ length: nQubits }, () => Array(nCols).fill(null));
|
|
77
|
+
|
|
78
|
+
const root = document.createElement('div');
|
|
79
|
+
root.className = 'cb-root';
|
|
80
|
+
|
|
81
|
+
const paletteEl = document.createElement('div');
|
|
82
|
+
paletteEl.className = 'cb-palette';
|
|
83
|
+
palette.forEach((g) => {
|
|
84
|
+
const chip = document.createElement('div');
|
|
85
|
+
chip.className = 'cb-chip';
|
|
86
|
+
chip.draggable = true;
|
|
87
|
+
chip.textContent = g.label;
|
|
88
|
+
chip.dataset.kind = g.kind;
|
|
89
|
+
chip.title = g.kind === 'single' ? `Porta a 1 qubit: ${g.label}`
|
|
90
|
+
: g.kind === 'control' ? 'Controllo -- trascina un target nella stessa colonna'
|
|
91
|
+
: g.kind === 'target' ? `Target -- forma C${g.gate.toUpperCase()} con un controllo nella stessa colonna`
|
|
92
|
+
: 'SWAP -- trascina due marcatori nella stessa colonna';
|
|
93
|
+
chip.addEventListener('dragstart', (e) => {
|
|
94
|
+
e.dataTransfer.setData('text/plain', g.id);
|
|
95
|
+
e.dataTransfer.effectAllowed = 'copy';
|
|
96
|
+
});
|
|
97
|
+
paletteEl.appendChild(chip);
|
|
98
|
+
});
|
|
99
|
+
root.appendChild(paletteEl);
|
|
100
|
+
|
|
101
|
+
const gridEl = document.createElement('div');
|
|
102
|
+
gridEl.className = 'cb-grid';
|
|
103
|
+
gridEl.style.gridTemplateColumns = `36px repeat(${nCols}, 40px)`;
|
|
104
|
+
|
|
105
|
+
for (let r = 0; r < nQubits; r++) {
|
|
106
|
+
const label = document.createElement('div');
|
|
107
|
+
label.className = 'cb-qlabel';
|
|
108
|
+
label.textContent = `q${r}`;
|
|
109
|
+
gridEl.appendChild(label);
|
|
110
|
+
for (let c = 0; c < nCols; c++) {
|
|
111
|
+
const cell = document.createElement('div');
|
|
112
|
+
cell.className = 'cb-cell';
|
|
113
|
+
cell.addEventListener('dragover', (e) => { e.preventDefault(); cell.classList.add('cb-over'); });
|
|
114
|
+
cell.addEventListener('dragleave', () => cell.classList.remove('cb-over'));
|
|
115
|
+
cell.addEventListener('drop', (e) => {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
cell.classList.remove('cb-over');
|
|
118
|
+
const gateId = e.dataTransfer.getData('text/plain');
|
|
119
|
+
const g = palette.find((x) => x.id === gateId);
|
|
120
|
+
if (!g) return;
|
|
121
|
+
grid[r][c] = { kind: g.kind, gate: g.gate };
|
|
122
|
+
renderCell(r, c);
|
|
123
|
+
emit();
|
|
124
|
+
});
|
|
125
|
+
cell.addEventListener('click', () => {
|
|
126
|
+
if (grid[r][c]) {
|
|
127
|
+
grid[r][c] = null;
|
|
128
|
+
renderCell(r, c);
|
|
129
|
+
emit();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
gridEl.appendChild(cell);
|
|
133
|
+
cellEls[r][c] = cell;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
root.appendChild(gridEl);
|
|
137
|
+
parentElement.appendChild(root);
|
|
138
|
+
|
|
139
|
+
const countEl = parentElement.querySelector('[data-role="count"]');
|
|
140
|
+
parentElement.querySelector('[data-role="clear"]').addEventListener('click', () => {
|
|
141
|
+
for (let r = 0; r < nQubits; r++) {
|
|
142
|
+
for (let c = 0; c < nCols; c++) {
|
|
143
|
+
grid[r][c] = null;
|
|
144
|
+
renderCell(r, c);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
emit();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const SYMS = { control: '●', swap: '×' };
|
|
151
|
+
|
|
152
|
+
function renderCell(r, c) {
|
|
153
|
+
const cell = cellEls[r][c];
|
|
154
|
+
const v = grid[r][c];
|
|
155
|
+
if (!v) {
|
|
156
|
+
cell.textContent = '';
|
|
157
|
+
cell.classList.remove('cb-filled');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
cell.classList.add('cb-filled');
|
|
161
|
+
if (v.kind === 'single') cell.textContent = v.gate.toUpperCase();
|
|
162
|
+
else if (v.kind === 'target') cell.textContent = v.gate === 'x' ? '⊕' : v.gate.toUpperCase();
|
|
163
|
+
else cell.textContent = SYMS[v.kind] || '?';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function buildOps() {
|
|
167
|
+
const ops = [];
|
|
168
|
+
for (let c = 0; c < nCols; c++) {
|
|
169
|
+
let single = null;
|
|
170
|
+
const controls = [];
|
|
171
|
+
const targets = [];
|
|
172
|
+
const swaps = [];
|
|
173
|
+
for (let r = 0; r < nQubits; r++) {
|
|
174
|
+
const v = grid[r][c];
|
|
175
|
+
if (!v) continue;
|
|
176
|
+
if (v.kind === 'single') single = { row: r, gate: v.gate };
|
|
177
|
+
else if (v.kind === 'control') controls.push(r);
|
|
178
|
+
else if (v.kind === 'target') targets.push({ row: r, gate: v.gate });
|
|
179
|
+
else if (v.kind === 'swap') swaps.push(r);
|
|
180
|
+
}
|
|
181
|
+
const isEmpty = !single && controls.length === 0 && targets.length === 0 && swaps.length === 0;
|
|
182
|
+
if (isEmpty) continue;
|
|
183
|
+
if (single && controls.length === 0 && targets.length === 0 && swaps.length === 0) {
|
|
184
|
+
ops.push({ gate: single.gate, qubits: [single.row] });
|
|
185
|
+
} else if (!single && controls.length === 1 && targets.length === 1 && swaps.length === 0) {
|
|
186
|
+
ops.push({ gate: 'c' + targets[0].gate, qubits: [controls[0], targets[0].row] });
|
|
187
|
+
} else if (!single && controls.length === 0 && targets.length === 0 && swaps.length === 2) {
|
|
188
|
+
ops.push({ gate: 'swap', qubits: swaps });
|
|
189
|
+
}
|
|
190
|
+
// Any other combination in a column (e.g. a lone control with no
|
|
191
|
+
// target yet) is an incomplete gate -- shown on the grid but not
|
|
192
|
+
// emitted as an op until it's completed.
|
|
193
|
+
}
|
|
194
|
+
return ops;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function emit() {
|
|
198
|
+
const ops = buildOps();
|
|
199
|
+
countEl.textContent = `${ops.length} gates placed`;
|
|
200
|
+
setStateValue('circuit', ops);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
emit();
|
|
204
|
+
}
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
_COMPONENT_NAME = "dense_evolution_circuit_builder"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def mount_circuit_builder(n_qubits: int, n_columns: int = 12, key: str = "circuit_builder"):
|
|
211
|
+
"""Mount the drag-and-drop circuit builder and return its current ops
|
|
212
|
+
list (list[dict] with 'gate'/'qubits'), or [] if nothing is placed yet.
|
|
213
|
+
|
|
214
|
+
`key` should change whenever `n_qubits` changes (e.g. include it in the
|
|
215
|
+
caller's key) so Streamlit mounts a fresh grid instead of reusing a
|
|
216
|
+
stale one sized for a different qubit count.
|
|
217
|
+
|
|
218
|
+
Streamlit is imported here, not at module level: this component only
|
|
219
|
+
means anything inside a Streamlit app (app_dashboard.py) -- the
|
|
220
|
+
Composer kernel (local_site/app/server.py) imports dashboard_core too
|
|
221
|
+
but never calls this function, and shouldn't need streamlit installed
|
|
222
|
+
just to start.
|
|
223
|
+
"""
|
|
224
|
+
import streamlit as st
|
|
225
|
+
builder = st.components.v2.component(_COMPONENT_NAME, html=_HTML, css=_CSS, js=_JS)
|
|
226
|
+
result = builder(
|
|
227
|
+
key=key,
|
|
228
|
+
data={"n_qubits": n_qubits, "n_columns": n_columns, "palette": GATE_PALETTE},
|
|
229
|
+
default={"circuit": []},
|
|
230
|
+
on_circuit_change=lambda: None,
|
|
231
|
+
)
|
|
232
|
+
return result.circuit if result.circuit is not None else []
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Native circuit-diagram renderer -- pure matplotlib, never a Qiskit
|
|
3
|
+
QuantumCircuit. Replaces qiskit's circuit.draw(output='mpl') for exactly
|
|
4
|
+
the reason documented in dashboard_core/engine.py's module docstring:
|
|
5
|
+
qiskit.circuit.QuantumCircuit.__init__ itself segfaults (SIGSEGV) on
|
|
6
|
+
macOS CI runners, on the simplest possible call (QuantumCircuit(3) alone,
|
|
7
|
+
no QASM, no methods called on it) -- see tests/integration/test_interop.py::
|
|
8
|
+
TestQiskitInterop for the full reproduction story. There is no way to
|
|
9
|
+
keep using Qiskit's own drawer without constructing that object, so this
|
|
10
|
+
module draws directly from the same (name, *qubits[, param]) gate-tuple
|
|
11
|
+
format every other dense_evolution entry point already uses.
|
|
12
|
+
|
|
13
|
+
Gate vocabulary comes from dashboard_core._gate_tables (_ONE_QUBIT_STATIC /
|
|
14
|
+
_ONE_QUBIT_PARAM / _TWO_QUBIT_STATIC / _TWO_QUBIT_PARAM /
|
|
15
|
+
_THREE_QUBIT_STATIC), shared with qasm_library.py -- the same gate set
|
|
16
|
+
QASMParser can ever hand back, so nothing here can see a name it doesn't
|
|
17
|
+
recognize. Was a second, independently hand-copied set of these 5
|
|
18
|
+
constants until this shared import (prog.txt, dashboard_core audit point
|
|
19
|
+
2): this copy was missing the 'u1'/'phase' aliases the other one had,
|
|
20
|
+
so a circuit using either of those two real gate names crashed this
|
|
21
|
+
function with "unsupported gate for native circuit diagram" -- a real
|
|
22
|
+
bug the duplication caused, not just a divergence risk.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import matplotlib.pyplot as plt
|
|
26
|
+
from matplotlib.patches import Circle, Rectangle
|
|
27
|
+
|
|
28
|
+
from ._gate_tables import (
|
|
29
|
+
_ONE_QUBIT_STATIC, _ONE_QUBIT_PARAM, _TWO_QUBIT_STATIC,
|
|
30
|
+
_TWO_QUBIT_PARAM, _THREE_QUBIT_STATIC,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = ['draw_native_circuit_diagram']
|
|
34
|
+
|
|
35
|
+
_BOX_LABEL = {
|
|
36
|
+
"h": "H", "x": "X", "y": "Y", "z": "Z", "s": "S", "sdg": "S†",
|
|
37
|
+
"t": "T", "tdg": "T†", "sx": "√X", "id": "I",
|
|
38
|
+
"rx": "RX", "ry": "RY", "rz": "RZ", "p": "P",
|
|
39
|
+
# u1/phase are the same gate as 'p' (see _gate_tables.py) -- without
|
|
40
|
+
# an entry here they'd still draw (that bug is fixed), just as the
|
|
41
|
+
# raw uppercased name ("U1"/"PHASE") instead of the same "P" label
|
|
42
|
+
# 'p' gets, inconsistent with dense_evolution/utils/drawing.py's own
|
|
43
|
+
# _LABELS, which already maps all three to "P" (prog.txt, dashboard_core
|
|
44
|
+
# audit point 5e).
|
|
45
|
+
"u1": "P", "phase": "P",
|
|
46
|
+
"cy": "Y", "cp": "P", "crz": "RZ",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_WIRE_COLOR = "black"
|
|
50
|
+
_BOX_FACE = "#6FA8DC"
|
|
51
|
+
_BOX_EDGE = "black"
|
|
52
|
+
_DOT_COLOR = "black"
|
|
53
|
+
_MEASURE_FACE = "#DDDDDD"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _op_qubits(op, name):
|
|
57
|
+
"""Every op tuple is (name, *qubits, *params) -- qubit count is fixed
|
|
58
|
+
per gate name (never variadic), so this just slices the right span."""
|
|
59
|
+
if name in _ONE_QUBIT_STATIC:
|
|
60
|
+
return [op[1]]
|
|
61
|
+
if name in _ONE_QUBIT_PARAM:
|
|
62
|
+
return [op[1]]
|
|
63
|
+
if name in _TWO_QUBIT_STATIC:
|
|
64
|
+
return [op[1], op[2]]
|
|
65
|
+
if name in _TWO_QUBIT_PARAM:
|
|
66
|
+
return [op[1], op[2]]
|
|
67
|
+
if name in _THREE_QUBIT_STATIC:
|
|
68
|
+
return [op[1], op[2], op[3]]
|
|
69
|
+
raise ValueError(f"unsupported gate for native circuit diagram: {name!r}")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _schedule_columns(ops):
|
|
73
|
+
"""Greedy left-packing, same rule every real circuit drawer uses: an
|
|
74
|
+
op goes in the first column strictly after the last column any of its
|
|
75
|
+
qubits was already used in. Returns a list of (op, column) pairs and
|
|
76
|
+
the total column count.
|
|
77
|
+
|
|
78
|
+
A multi-qubit op's vertical connector is drawn spanning every row
|
|
79
|
+
between its lowest and highest qubit index (see _draw_vertical_link
|
|
80
|
+
call sites below), not just the qubits it actually touches -- e.g.
|
|
81
|
+
cy(2, 0) draws a line through q1's row even though q1 isn't part of
|
|
82
|
+
the gate. So every qubit in that span, not just the op's own qubits,
|
|
83
|
+
must be marked used at this column too, or an unrelated single-qubit
|
|
84
|
+
gate on the skipped-over qubit can land in the same column and get
|
|
85
|
+
visually cut through by the connector line."""
|
|
86
|
+
next_free_col = {}
|
|
87
|
+
scheduled = []
|
|
88
|
+
for op in ops:
|
|
89
|
+
name = op[0]
|
|
90
|
+
qubits = _op_qubits(op, name)
|
|
91
|
+
span = range(min(qubits), max(qubits) + 1)
|
|
92
|
+
col = max((next_free_col.get(q, 0) for q in span), default=0)
|
|
93
|
+
scheduled.append((op, col))
|
|
94
|
+
for q in span:
|
|
95
|
+
next_free_col[q] = col + 1
|
|
96
|
+
n_columns = max((col for _, col in scheduled), default=-1) + 1
|
|
97
|
+
return scheduled, n_columns
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _draw_one_qubit_box(ax, x, y, label):
|
|
101
|
+
box = Rectangle((x - 0.3, y - 0.3), 0.6, 0.6, facecolor=_BOX_FACE, edgecolor=_BOX_EDGE, zorder=3)
|
|
102
|
+
ax.add_patch(box)
|
|
103
|
+
ax.text(x, y, label, ha="center", va="center", fontsize=10, fontweight="bold", zorder=4)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _draw_control_dot(ax, x, y):
|
|
107
|
+
ax.add_patch(Circle((x, y), 0.08, facecolor=_DOT_COLOR, edgecolor=_DOT_COLOR, zorder=4))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _draw_target_plus(ax, x, y):
|
|
111
|
+
ax.add_patch(Circle((x, y), 0.28, facecolor="white", edgecolor=_DOT_COLOR, zorder=3))
|
|
112
|
+
ax.plot([x - 0.28, x + 0.28], [y, y], color=_DOT_COLOR, linewidth=1.4, zorder=4)
|
|
113
|
+
ax.plot([x, x], [y - 0.28, y + 0.28], color=_DOT_COLOR, linewidth=1.4, zorder=4)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _draw_swap_x(ax, x, y):
|
|
117
|
+
ax.plot([x - 0.15, x + 0.15], [y - 0.15, y + 0.15], color=_DOT_COLOR, linewidth=2, zorder=4)
|
|
118
|
+
ax.plot([x - 0.15, x + 0.15], [y + 0.15, y - 0.15], color=_DOT_COLOR, linewidth=2, zorder=4)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _draw_vertical_link(ax, x, y0, y1):
|
|
122
|
+
ax.plot([x, x], [y0, y1], color=_DOT_COLOR, linewidth=1.4, zorder=2)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _param_label(name, params):
|
|
126
|
+
base = _BOX_LABEL.get(name, name.upper())
|
|
127
|
+
if params:
|
|
128
|
+
return f"{base}({params[0]:.2f})"
|
|
129
|
+
return base
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def draw_native_circuit_diagram(ops, n_qubits: int, add_measure: bool = True):
|
|
133
|
+
"""Draws a circuit diagram figure directly from dense_evolution gate
|
|
134
|
+
tuples -- no Qiskit QuantumCircuit ever constructed. Qubit 0 is drawn
|
|
135
|
+
at the top, increasing downward, matching Qiskit's own drawer
|
|
136
|
+
convention so this is a drop-in replacement for
|
|
137
|
+
dashboard_core.visuals.draw_circuit_figure's panel.
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
matplotlib.figure.Figure
|
|
142
|
+
|
|
143
|
+
Examples
|
|
144
|
+
--------
|
|
145
|
+
>>> from dashboard_core.circuit_diagram import draw_native_circuit_diagram
|
|
146
|
+
>>> fig = draw_native_circuit_diagram([('h', 0), ('cx', 0, 1)], n_qubits=2)
|
|
147
|
+
>>> type(fig).__name__
|
|
148
|
+
'Figure'
|
|
149
|
+
>>> fig.savefig('bell_pair.png') # doctest: +SKIP
|
|
150
|
+
"""
|
|
151
|
+
scheduled, n_columns = _schedule_columns(ops)
|
|
152
|
+
n_display_columns = n_columns + (1 if add_measure else 0)
|
|
153
|
+
|
|
154
|
+
fig_width = max(3.0, 1.1 * (n_display_columns + 1))
|
|
155
|
+
fig_height = max(2.0, 0.9 * n_qubits + 0.5)
|
|
156
|
+
fig, ax = plt.subplots(figsize=(fig_width, fig_height))
|
|
157
|
+
|
|
158
|
+
def row_y(qubit):
|
|
159
|
+
return n_qubits - 1 - qubit
|
|
160
|
+
|
|
161
|
+
wire_x_end = n_display_columns + 0.7
|
|
162
|
+
for q in range(n_qubits):
|
|
163
|
+
y = row_y(q)
|
|
164
|
+
ax.plot([0, wire_x_end], [y, y], color=_WIRE_COLOR, linewidth=1.2, zorder=1)
|
|
165
|
+
ax.text(-0.35, y, f"q{q}", ha="right", va="center", fontsize=10)
|
|
166
|
+
|
|
167
|
+
for op, col in scheduled:
|
|
168
|
+
name = op[0]
|
|
169
|
+
x = col + 1.0
|
|
170
|
+
qubits = _op_qubits(op, name)
|
|
171
|
+
rows = [row_y(q) for q in qubits]
|
|
172
|
+
params = list(op[1 + len(qubits):])
|
|
173
|
+
|
|
174
|
+
if name in _ONE_QUBIT_STATIC:
|
|
175
|
+
_draw_one_qubit_box(ax, x, rows[0], _BOX_LABEL.get(name, name.upper()))
|
|
176
|
+
elif name in _ONE_QUBIT_PARAM:
|
|
177
|
+
_draw_one_qubit_box(ax, x, rows[0], _param_label(name, params))
|
|
178
|
+
elif name == "swap":
|
|
179
|
+
_draw_vertical_link(ax, x, rows[0], rows[1])
|
|
180
|
+
_draw_swap_x(ax, x, rows[0])
|
|
181
|
+
_draw_swap_x(ax, x, rows[1])
|
|
182
|
+
elif name in _TWO_QUBIT_STATIC:
|
|
183
|
+
# cx/cy/cz: qubits[0] is control, qubits[1] is target.
|
|
184
|
+
_draw_vertical_link(ax, x, rows[0], rows[1])
|
|
185
|
+
_draw_control_dot(ax, x, rows[0])
|
|
186
|
+
if name == "cx":
|
|
187
|
+
_draw_target_plus(ax, x, rows[1])
|
|
188
|
+
else:
|
|
189
|
+
_draw_one_qubit_box(ax, x, rows[1], _BOX_LABEL.get(name, name.upper()))
|
|
190
|
+
elif name in _TWO_QUBIT_PARAM:
|
|
191
|
+
_draw_vertical_link(ax, x, rows[0], rows[1])
|
|
192
|
+
_draw_control_dot(ax, x, rows[0])
|
|
193
|
+
_draw_one_qubit_box(ax, x, rows[1], _param_label(name, params))
|
|
194
|
+
elif name in _THREE_QUBIT_STATIC:
|
|
195
|
+
# ccx (Toffoli): qubits[0], qubits[1] control, qubits[2] target.
|
|
196
|
+
_draw_vertical_link(ax, x, min(rows), max(rows))
|
|
197
|
+
_draw_control_dot(ax, x, rows[0])
|
|
198
|
+
_draw_control_dot(ax, x, rows[1])
|
|
199
|
+
_draw_target_plus(ax, x, rows[2])
|
|
200
|
+
else:
|
|
201
|
+
raise ValueError(f"unsupported gate for native circuit diagram: {name!r}")
|
|
202
|
+
|
|
203
|
+
if add_measure:
|
|
204
|
+
x = n_columns + 1.0
|
|
205
|
+
for q in range(n_qubits):
|
|
206
|
+
y = row_y(q)
|
|
207
|
+
box = Rectangle((x - 0.3, y - 0.3), 0.6, 0.6, facecolor=_MEASURE_FACE, edgecolor=_BOX_EDGE, zorder=3)
|
|
208
|
+
ax.add_patch(box)
|
|
209
|
+
ax.text(x, y, "M", ha="center", va="center", fontsize=9, zorder=4)
|
|
210
|
+
|
|
211
|
+
ax.set_xlim(-1.2, wire_x_end + 0.3)
|
|
212
|
+
ax.set_ylim(-0.7, n_qubits - 0.3)
|
|
213
|
+
ax.set_aspect("equal")
|
|
214
|
+
ax.axis("off")
|
|
215
|
+
fig.tight_layout()
|
|
216
|
+
return fig
|