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,215 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dense_evolution.healing -- predictive-healing primitives for noisy vector
|
|
3
|
+
sequences (VQE/MD telemetry, quantum state trajectories).
|
|
4
|
+
|
|
5
|
+
Built empirically, one formula at a time, not derived top-down from a
|
|
6
|
+
named theory -- worth being precise about, since one piece of it turns
|
|
7
|
+
out to have a real mathematical identity worth naming rather than
|
|
8
|
+
leaving implicit: calculate_vettore_dinamico's core term,
|
|
9
|
+
log(E_B / E_A), is a log-likelihood ratio -- the same elementary
|
|
10
|
+
quantity Kullback-Leibler divergence (relative entropy) is built from
|
|
11
|
+
(D_KL(A||B) = sum_x A(x) * log(A(x)/B(x)), a probability-weighted
|
|
12
|
+
average of exactly this log-ratio, which calculate_vettore_dinamico
|
|
13
|
+
does not compute -- it uses one un-weighted log-ratio between two
|
|
14
|
+
scalars, not a full KL divergence over a distribution). Equivalently,
|
|
15
|
+
if -log(E) is read as self-information ("surprisal"), log(E_B/E_A) is
|
|
16
|
+
the *difference in surprisal* between the two states.
|
|
17
|
+
|
|
18
|
+
Not every formula in this module carries the same reading -- calculate_
|
|
19
|
+
phi_ab's blend of cosine-alignment and Euclidean-distance terms is a
|
|
20
|
+
geometric similarity construction, not an information-theoretic one;
|
|
21
|
+
naming it as such would be the overclaiming this note is trying to
|
|
22
|
+
avoid, not fix.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import math
|
|
26
|
+
import warnings
|
|
27
|
+
|
|
28
|
+
import jax
|
|
29
|
+
import jax.numpy as jnp
|
|
30
|
+
import json
|
|
31
|
+
from datetime import datetime, timezone
|
|
32
|
+
from typing import Dict, Tuple, List, Any
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
GLOBAL_CONSTANTS = {
|
|
36
|
+
'TARGET_SIGMA_IDEALE': 10.0,
|
|
37
|
+
'THRESHOLD_DELTA_PREEMP': 0.2,
|
|
38
|
+
'SEMANTIC_LEARNING_RATE': 0.05,
|
|
39
|
+
'V_DINAMIC_K_COEFF': 5.0,
|
|
40
|
+
'V_STATIC_K_PRIME_COEFF': 1.0,
|
|
41
|
+
'V_DINAMIC_MIN_EFFECTIVE_VALUE': 0.01,
|
|
42
|
+
# Plain math.sqrt, not jnp.sqrt -- a bare jnp constant built at
|
|
43
|
+
# import time bakes in whatever precision jax_enable_x64 happens to
|
|
44
|
+
# be at that exact moment (permanently truncated to float32 if it's
|
|
45
|
+
# still False), the same class of bug config.py's ensure_x64()
|
|
46
|
+
# exists to avoid. This constant only ever divides a jnp array, so a
|
|
47
|
+
# plain Python float sidesteps the process-wide flag entirely.
|
|
48
|
+
'MAX_SEMANTIC_DISTANCE': math.sqrt(2.0),
|
|
49
|
+
'WEIGHT_SEMANTIC': 0.6,
|
|
50
|
+
'WEIGHT_COHERENCE': 0.4,
|
|
51
|
+
'NON_STATIC_THRESHOLD_A': 1e-2,
|
|
52
|
+
'DAMPING_BOOST_ON_STASIS': 0.1,
|
|
53
|
+
'EPSILON_DISSIPATION_BASE': 0.01
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# =====================================================================
|
|
57
|
+
# 📊 CORE LAYER
|
|
58
|
+
# =====================================================================
|
|
59
|
+
|
|
60
|
+
@jax.jit
|
|
61
|
+
def _calculate_advanced_sigma_core(kappa: jnp.ndarray, H: jnp.ndarray, Psi: jnp.ndarray, Omega_sync: jnp.ndarray, tau_K: jnp.ndarray) -> jnp.ndarray:
|
|
62
|
+
return kappa * H * Psi * Omega_sync * tau_K
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def calculate_advanced_sigma(kappa: jnp.ndarray, H: jnp.ndarray, Psi: jnp.ndarray, Omega_sync: jnp.ndarray, tau_K: jnp.ndarray) -> jnp.ndarray:
|
|
66
|
+
"""Deprecated: kappa*H*Psi*Omega_sync*tau_K, intended as the source of
|
|
67
|
+
zero_noise_extrapolation's sigma_at_base_noise (see this module's own
|
|
68
|
+
docs/api/healing.md), but its 5 inputs never had a defined provenance
|
|
69
|
+
in a ZNE context -- and Dense-Evolution-Discovery Experiment 35
|
|
70
|
+
(scripts/zne_healing_sigma_provenance.py) has since shown that even a
|
|
71
|
+
fully-designed input wouldn't matter: a permutation-test negative
|
|
72
|
+
control (real sigma vs. randomly shuffled sigma) performed
|
|
73
|
+
statistically identically, meaning the healing-adapted branch's
|
|
74
|
+
coefficient perturbation doesn't discriminate real signal from noise
|
|
75
|
+
at all. Kept for backward compatibility only (no known external
|
|
76
|
+
callers found in Dense-Evolution, Dense-Evolution-Discovery, or
|
|
77
|
+
Dense-Armor); will be removed in a future major version. This wrapper
|
|
78
|
+
is intentionally NOT @jax.jit-decorated (unlike the private core it
|
|
79
|
+
delegates to) so the warning fires on every call, not just once per
|
|
80
|
+
traced input shape."""
|
|
81
|
+
warnings.warn(
|
|
82
|
+
"calculate_advanced_sigma is deprecated: its output was never wired into "
|
|
83
|
+
"any real pipeline, and Dense-Evolution-Discovery Experiment 35 found the "
|
|
84
|
+
"one place it could have been used (zero_noise_extrapolation's healing "
|
|
85
|
+
"branch) does not discriminate real sigma from random noise anyway. "
|
|
86
|
+
"Will be removed in a future release.",
|
|
87
|
+
DeprecationWarning, stacklevel=2,
|
|
88
|
+
)
|
|
89
|
+
return _calculate_advanced_sigma_core(kappa, H, Psi, Omega_sync, tau_K)
|
|
90
|
+
|
|
91
|
+
@jax.jit
|
|
92
|
+
def calculate_phi_ab(state_A: jnp.ndarray, state_B: jnp.ndarray, ipg_vector: jnp.ndarray) -> jnp.ndarray:
|
|
93
|
+
"""Computes the Phi_AB spatial alignment and coherence factor."""
|
|
94
|
+
semantic_change = state_B - state_A
|
|
95
|
+
norm_change = jnp.linalg.norm(semantic_change)
|
|
96
|
+
norm_ipg = jnp.linalg.norm(ipg_vector)
|
|
97
|
+
|
|
98
|
+
alignment = jnp.where(
|
|
99
|
+
(norm_change > 1e-12) & (norm_ipg > 1e-12),
|
|
100
|
+
# jnp.dot on complex arrays is the bilinear (non-conjugated) product
|
|
101
|
+
# and stays complex, which used to blow up jnp.clip below with
|
|
102
|
+
# "ValueError: Clip received a complex value". jnp.real(jnp.vdot(..))
|
|
103
|
+
# is the correct Hermitian-inner-product real part -- for real
|
|
104
|
+
# inputs it reduces exactly to jnp.dot (no behavior change for
|
|
105
|
+
# existing real-valued callers), and for complex inputs (e.g. a
|
|
106
|
+
# genuine statevector) it gives the real alignment value this
|
|
107
|
+
# function needs. Re(vdot(a,b)) == Re(vdot(b,a)) always, even
|
|
108
|
+
# though vdot(a,b) != vdot(b,a) in general (they're conjugates) --
|
|
109
|
+
# argument order doesn't matter here only because we take the real part.
|
|
110
|
+
jnp.real(jnp.vdot(semantic_change, ipg_vector)) / (norm_change * norm_ipg),
|
|
111
|
+
0.0
|
|
112
|
+
)
|
|
113
|
+
semantic_alignment = (alignment + 1.0) / 2.0
|
|
114
|
+
|
|
115
|
+
distance_A_B = jnp.linalg.norm(state_A - state_B)
|
|
116
|
+
coherence_component = 1.0 - (distance_A_B / GLOBAL_CONSTANTS['MAX_SEMANTIC_DISTANCE'])
|
|
117
|
+
|
|
118
|
+
phi_ab = (semantic_alignment * GLOBAL_CONSTANTS['WEIGHT_SEMANTIC']) + (coherence_component * GLOBAL_CONSTANTS['WEIGHT_COHERENCE'])
|
|
119
|
+
return jnp.clip(phi_ab, 0.0, 1.0)
|
|
120
|
+
|
|
121
|
+
@jax.jit
|
|
122
|
+
def calculate_vettore_dinamico(E_A: jnp.ndarray, E_B: jnp.ndarray, Phi_AB: jnp.ndarray) -> jnp.ndarray:
|
|
123
|
+
"""Computes the Dynamic Vector (V_dinamic) as a differential logarithmic energy variation.
|
|
124
|
+
|
|
125
|
+
log(E_B / E_A) is a log-likelihood ratio -- the same elementary
|
|
126
|
+
quantity Kullback-Leibler divergence is built from (see this
|
|
127
|
+
module's docstring for the precise distinction: this is one
|
|
128
|
+
un-weighted log-ratio between two scalars, not a full KL divergence
|
|
129
|
+
over a probability distribution). Equivalently, the difference in
|
|
130
|
+
surprisal (-log E) between the two states."""
|
|
131
|
+
valid_inputs = (E_A > 1e-12) & (E_B > 1e-12)
|
|
132
|
+
ratio = jnp.where(valid_inputs, E_B / E_A, 1.0)
|
|
133
|
+
log_ratio_clamped = jnp.clip(jnp.log(ratio), -5.0, 5.0)
|
|
134
|
+
v_vita = GLOBAL_CONSTANTS['V_DINAMIC_K_COEFF'] * log_ratio_clamped * Phi_AB
|
|
135
|
+
return jnp.where(valid_inputs, v_vita, 0.0)
|
|
136
|
+
|
|
137
|
+
@jax.jit
|
|
138
|
+
def calculate_vettore_statico(v_dinamic_value: jnp.ndarray) -> jnp.ndarray:
|
|
139
|
+
"""Computes the Static Vector tensorial-stasis indicator."""
|
|
140
|
+
is_growing = v_dinamic_value > GLOBAL_CONSTANTS['V_DINAMIC_MIN_EFFECTIVE_VALUE']
|
|
141
|
+
return GLOBAL_CONSTANTS['V_STATIC_K_PRIME_COEFF'] * (1.0 - jnp.where(is_growing, 1.0, 0.0))
|
|
142
|
+
|
|
143
|
+
@jax.jit
|
|
144
|
+
def calculate_delta_preemp(current_sigma: jnp.ndarray, target_sigma_ideal: float = 10.0) -> jnp.ndarray:
|
|
145
|
+
"""Computes the predictive deviation Delta_Pre_emp normalized against the ideal eigenstate."""
|
|
146
|
+
safe_target = jnp.where(target_sigma_ideal <= 0.0, 1.0, target_sigma_ideal)
|
|
147
|
+
return jnp.abs(current_sigma - target_sigma_ideal) / safe_target
|
|
148
|
+
|
|
149
|
+
@jax.jit
|
|
150
|
+
def evaluate_phi_trigger(deterministic_dq_dt_a: jnp.ndarray) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]:
|
|
151
|
+
"""Evaluates the Phi-Trigger state by computing the conditional damping coefficients."""
|
|
152
|
+
magnitude_change_a = jnp.abs(deterministic_dq_dt_a)
|
|
153
|
+
trigger_active = magnitude_change_a > GLOBAL_CONSTANTS['NON_STATIC_THRESHOLD_A']
|
|
154
|
+
|
|
155
|
+
lambda_step = jnp.where(trigger_active, 0.05, 0.05 + GLOBAL_CONSTANTS['DAMPING_BOOST_ON_STASIS'])
|
|
156
|
+
epsilon_dissip = jnp.where(trigger_active, GLOBAL_CONSTANTS['EPSILON_DISSIPATION_BASE'],
|
|
157
|
+
GLOBAL_CONSTANTS['EPSILON_DISSIPATION_BASE'] + GLOBAL_CONSTANTS['DAMPING_BOOST_ON_STASIS'])
|
|
158
|
+
|
|
159
|
+
return jnp.where(trigger_active, 1.0, 0.0), lambda_step, epsilon_dissip
|
|
160
|
+
|
|
161
|
+
@jax.jit
|
|
162
|
+
def calculate_jax_reflection(coherence_values: jnp.ndarray, noise_levels: jnp.ndarray) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]:
|
|
163
|
+
"""Performs spectral statistical aggregation (Zero-Drift) on the XLA runtime."""
|
|
164
|
+
n_coh = coherence_values.shape[0]
|
|
165
|
+
avg_coherence = jnp.where(n_coh > 0, jnp.mean(coherence_values), 0.0)
|
|
166
|
+
var_coherence = jnp.where(n_coh > 0, jnp.var(coherence_values), 0.0)
|
|
167
|
+
|
|
168
|
+
n_noise = noise_levels.shape[0]
|
|
169
|
+
avg_noise = jnp.where(n_noise > 0, jnp.mean(noise_levels), 0.0)
|
|
170
|
+
|
|
171
|
+
return avg_coherence, var_coherence, avg_noise
|
|
172
|
+
|
|
173
|
+
# =====================================================================
|
|
174
|
+
# OPERATIONAL LAYER: LOGGING AND HISTORY TRACKING
|
|
175
|
+
# =====================================================================
|
|
176
|
+
|
|
177
|
+
class MemoryReflectionEngine:
|
|
178
|
+
def __init__(self):
|
|
179
|
+
self.memory: List[Dict[str, Any]] = []
|
|
180
|
+
|
|
181
|
+
def record_event(self, event_type: str, value: float, description: str = ""):
|
|
182
|
+
event = {
|
|
183
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
184
|
+
"type": event_type,
|
|
185
|
+
"value": float(value),
|
|
186
|
+
"description": description
|
|
187
|
+
}
|
|
188
|
+
self.memory.append(event)
|
|
189
|
+
|
|
190
|
+
def reflect(self) -> Dict[str, Any]:
|
|
191
|
+
coherence_list = [e['value'] for e in self.memory if e['type'] == 'coherence']
|
|
192
|
+
interventions = [e for e in self.memory if e['type'] == 'intervention']
|
|
193
|
+
noise_list = [e['value'] for e in self.memory if e['type'] == 'noise']
|
|
194
|
+
|
|
195
|
+
j_coherence = jnp.array(coherence_list, dtype=jnp.float64) if coherence_list else jnp.array([], dtype=jnp.float64)
|
|
196
|
+
j_noise = jnp.array(noise_list, dtype=jnp.float64) if noise_list else jnp.array([], dtype=jnp.float64)
|
|
197
|
+
|
|
198
|
+
avg_coh, var_coh, avg_noise = calculate_jax_reflection(j_coherence, j_noise)
|
|
199
|
+
|
|
200
|
+
report = {
|
|
201
|
+
'average_coherence': float(avg_coh) if coherence_list else None,
|
|
202
|
+
'variance_coherence': float(var_coh) if coherence_list else None,
|
|
203
|
+
'interventions_count': len(interventions),
|
|
204
|
+
'average_noise': float(avg_noise) if noise_list else None,
|
|
205
|
+
'total_events': len(self.memory)
|
|
206
|
+
}
|
|
207
|
+
return report
|
|
208
|
+
|
|
209
|
+
def export_memory(self, filepath: str):
|
|
210
|
+
with open(filepath, 'w') as f:
|
|
211
|
+
json.dump(self.memory, f, indent=2)
|
|
212
|
+
|
|
213
|
+
def load_memory(self, filepath: str):
|
|
214
|
+
with open(filepath, 'r') as f:
|
|
215
|
+
self.memory = json.load(f)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Classical Kullback-Leibler divergence between probability distributions
|
|
2
|
+
(Kullback, S. & Leibler, R.A., "On Information and Sufficiency", The
|
|
3
|
+
Annals of Mathematical Statistics, 22(1), 79-86, 1951).
|
|
4
|
+
|
|
5
|
+
D_KL(p||q) = sum_x p(x) * log2(p(x)/q(x)), the relative entropy of q from
|
|
6
|
+
p, in bits (log2, matching the rest of this subpackage's convention --
|
|
7
|
+
`sandwiched_renyi_divergence`/`magic_entropy` both use log2).
|
|
8
|
+
|
|
9
|
+
Checked against the paper's own text directly (Section 2, eq. 2.2-2.3),
|
|
10
|
+
not assumed from the textbook formula alone: what this module implements
|
|
11
|
+
is what Kullback & Leibler call I(1:2), "the mean information for
|
|
12
|
+
discrimination between H1 and H2" -- what the broader literature later
|
|
13
|
+
popularized as "the KL divergence". Their OWN word "divergence",
|
|
14
|
+
J(1,2) = I(1:2) + I(2:1) (eq. 2.9), names the symmetrized sum of both
|
|
15
|
+
directions instead -- deliberately not implemented here, since it would
|
|
16
|
+
duplicate the Jensen-Shannon divergence this codebase already uses
|
|
17
|
+
(`mps.py`'s bond-dimension search, `zne.py`'s predictive ZNE), which is
|
|
18
|
+
bounded and better-behaved at disjoint supports.
|
|
19
|
+
|
|
20
|
+
Distinct in kind from `sandwiched_renyi_divergence(rho, sigma, alpha=1.0)`,
|
|
21
|
+
which reduces to the QUANTUM relative entropy Tr[rho(log rho - log sigma)]
|
|
22
|
+
between density MATRICES via matrix logarithms -- this module implements
|
|
23
|
+
the simpler classical case directly over probability VECTORS (e.g. a
|
|
24
|
+
measurement-outcome distribution |psi|^2, or any other normalized
|
|
25
|
+
histogram), with no eigendecomposition needed.
|
|
26
|
+
|
|
27
|
+
Originated from an honest gap flagged in this subpackage's own healing.py
|
|
28
|
+
docstring: calculate_vettore_dinamico's core term, log(E_B/E_A), is a
|
|
29
|
+
single un-weighted log-likelihood ratio between two scalars -- the same
|
|
30
|
+
elementary quantity this divergence is built from, but not this
|
|
31
|
+
divergence itself. Built and validated in Dense-Evolution-Discovery,
|
|
32
|
+
Experiment 32
|
|
33
|
+
(https://tatopenn-cell.github.io/Dense-Evolution-Discovery/kullback_leibler_divergence/):
|
|
34
|
+
against an independent scipy.stats.entropy reference (1e-9 bits across 20
|
|
35
|
+
random trials), Gibbs' inequality (D_KL >= 0, 200 random pairs, never
|
|
36
|
+
negative), a genuine support-violation case (+inf, not a finite wrong
|
|
37
|
+
number), and a real measurement-distribution application confirming this
|
|
38
|
+
is not a trivial rescaling of healing.py's existing scalar signal.
|
|
39
|
+
|
|
40
|
+
Additive, not a replacement for the already-validated healing pipeline.
|
|
41
|
+
"""
|
|
42
|
+
import jax
|
|
43
|
+
import jax.numpy as jnp
|
|
44
|
+
|
|
45
|
+
__all__ = ["kl_divergence", "kl_divergence_jit"]
|
|
46
|
+
|
|
47
|
+
_EPS = 1e-12
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _kl_divergence_core(p: jnp.ndarray, q: jnp.ndarray) -> jnp.ndarray:
|
|
51
|
+
# 0 * log(0/q) = 0 by the standard x*log(x) -> 0 (x -> 0+) convention
|
|
52
|
+
# (Cover & Thomas, "Elements of Information Theory", 2nd ed., section
|
|
53
|
+
# 2.1) -- terms where p is (numerically) zero contribute nothing,
|
|
54
|
+
# regardless of q there.
|
|
55
|
+
p_is_zero = p < _EPS
|
|
56
|
+
|
|
57
|
+
# A term is only a genuine support violation when p(x) > 0 but
|
|
58
|
+
# q(x) == 0: p has mass where q has none, so no finite log-ratio can
|
|
59
|
+
# account for it -- D_KL(p||q) is +inf by definition, not a numerical
|
|
60
|
+
# artifact to clamp away (the same lesson already learned the hard way
|
|
61
|
+
# for sandwiched_renyi_divergence's alpha>1 case -- see renyi.py).
|
|
62
|
+
q_is_zero = q < _EPS
|
|
63
|
+
is_support_violation = jnp.any((~p_is_zero) & q_is_zero)
|
|
64
|
+
|
|
65
|
+
safe_p = jnp.where(p_is_zero, 1.0, p)
|
|
66
|
+
safe_q = jnp.where(q_is_zero, 1.0, q)
|
|
67
|
+
terms = jnp.where(p_is_zero, 0.0, safe_p * jnp.log2(safe_p / safe_q))
|
|
68
|
+
finite_result = jnp.sum(terms)
|
|
69
|
+
return jnp.where(is_support_violation, jnp.inf, finite_result)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def kl_divergence(p: jnp.ndarray, q: jnp.ndarray) -> float:
|
|
73
|
+
"""Classical Kullback-Leibler divergence D_KL(p||q), in bits.
|
|
74
|
+
|
|
75
|
+
`p`, `q` are 1-D real, non-negative probability vectors of the same
|
|
76
|
+
length, each summing to 1 (not validated here -- callers pass in a
|
|
77
|
+
normalized distribution, e.g. `jnp.abs(psi) ** 2` for a statevector's
|
|
78
|
+
measurement-outcome probabilities). Not symmetric: D_KL(p||q) !=
|
|
79
|
+
D_KL(q||p) in general.
|
|
80
|
+
|
|
81
|
+
Zero iff p == q (Gibbs' inequality: D_KL(p||q) >= 0 always, with
|
|
82
|
+
equality only at p == q). Returns `+inf` when p has support where q
|
|
83
|
+
does not (p(x) > 0, q(x) == 0 for some x) -- the correct value, not an
|
|
84
|
+
edge case to avoid; see the module docstring.
|
|
85
|
+
"""
|
|
86
|
+
p = jnp.asarray(p, dtype=jnp.float64)
|
|
87
|
+
q = jnp.asarray(q, dtype=jnp.float64)
|
|
88
|
+
return float(_kl_divergence_core(p, q))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
kl_divergence_jit = jax.jit(_kl_divergence_core)
|
|
92
|
+
"""`jax.jit`-compiled entry point for `kl_divergence`. `p`/`q` must already
|
|
93
|
+
be `float64` arrays. Returns a jnp scalar, not a Python `float`."""
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Magic entropy: a single-qubit density-matrix diagnostic built from the
|
|
2
|
+
3-fold self-convolution "Key Unitary" construction (Bu, Gu, Jaffe,
|
|
3
|
+
"Stabilizer testing and magic entropy", arXiv:2306.09292, Definitions 7-8).
|
|
4
|
+
|
|
5
|
+
Originated from a Colab proposal for a pairwise "Quantum Ruzsa Divergence"
|
|
6
|
+
(following Bu, Gu, Jaffe, "A convolutional quantum Ruzsa divergence and
|
|
7
|
+
its applications", arXiv:2401.14385) that turned out to have no valid
|
|
8
|
+
definition for qubits: that paper's pairwise convolution needs
|
|
9
|
+
s^2+t^2=1 mod d, which has no solution at d=2. The companion paper above
|
|
10
|
+
does not patch this with a qubit-specific pairwise formula -- it defines
|
|
11
|
+
a structurally different, minimum-3-input "Key Unitary" convolution (K
|
|
12
|
+
quantum registers, K must be ODD, K>=3; there is no K=2 case). For qubits
|
|
13
|
+
the smallest valid object is therefore the 3-fold SELF-convolution of one
|
|
14
|
+
state with itself, boxtimes_3(rho,rho,rho), and the entropy of its
|
|
15
|
+
reduced output register is what the paper calls "magic entropy": zero
|
|
16
|
+
for stabilizer states, positive for non-stabilizer ("magic") states (the
|
|
17
|
+
paper's Examples 32/33).
|
|
18
|
+
|
|
19
|
+
Validated end-to-end in Dense-Evolution-Discovery, Experiment 30
|
|
20
|
+
(https://tatopenn-cell.github.io/Dense-Evolution-Discovery/quantum_ruzsa_magic_entropy/):
|
|
21
|
+
the Key Unitary circuit was checked basis-state by basis-state against
|
|
22
|
+
the paper's own Lemma 9 combinatorial identity; magic_entropy was
|
|
23
|
+
checked against all six single-qubit stabilizer states (~0, max observed
|
|
24
|
+
4e-11) and the two standard magic states T and H (0.811 bits, matching
|
|
25
|
+
each other exactly as expected by symmetry); and it was used as a noise
|
|
26
|
+
diagnostic that is qualitatively distinct from uhlmann_fidelity and the
|
|
27
|
+
sandwiched Renyi divergence (see renyi.py in this same subpackage) --
|
|
28
|
+
under amplitude damping it is non-monotonic (rises then returns to
|
|
29
|
+
exactly 0, since the p=1 fixed point |0> is a stabilizer state), unlike
|
|
30
|
+
either of those two, which change monotonically over the same sweep.
|
|
31
|
+
|
|
32
|
+
Restricted to SINGLE-QUBIT density matrices (2x2) -- the Key Unitary here
|
|
33
|
+
is built for n=1-qubit registers specifically; a multi-qubit
|
|
34
|
+
generalization would need a larger Key Unitary circuit (n-qubit
|
|
35
|
+
registers, Definition 7 in general) not implemented here.
|
|
36
|
+
|
|
37
|
+
A shadow-measurement-based estimator for this same quantity, using
|
|
38
|
+
randomized measurement snapshots instead of the exact density matrix, is
|
|
39
|
+
promoted alongside this module in magic_entropy_shadows.py -- see that
|
|
40
|
+
module for why it has its own API shape (measurement snapshots in, not a
|
|
41
|
+
density matrix) rather than a function alongside this one.
|
|
42
|
+
"""
|
|
43
|
+
import functools
|
|
44
|
+
|
|
45
|
+
import jax
|
|
46
|
+
import jax.numpy as jnp
|
|
47
|
+
import numpy as np
|
|
48
|
+
|
|
49
|
+
__all__ = ["magic_entropy", "magic_entropy_jit"]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _cnot_matrix(control: int, target: int, n: int = 3) -> np.ndarray:
|
|
53
|
+
"""Permutation matrix for a CNOT(control->target) gate on n qubits,
|
|
54
|
+
computational basis ordered as |q0 q1 ... q(n-1)> (this package's
|
|
55
|
+
MSB-first convention, matching dense_evolution.physics.entropy)."""
|
|
56
|
+
dim = 2 ** n
|
|
57
|
+
mat = np.zeros((dim, dim))
|
|
58
|
+
for i in range(dim):
|
|
59
|
+
bits = [(i >> (n - 1 - k)) & 1 for k in range(n)]
|
|
60
|
+
if bits[control]:
|
|
61
|
+
bits[target] ^= 1
|
|
62
|
+
j = 0
|
|
63
|
+
for bit in bits:
|
|
64
|
+
j = (j << 1) | bit
|
|
65
|
+
mat[j, i] = 1.0
|
|
66
|
+
return mat
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _build_key_unitary_k3() -> jnp.ndarray:
|
|
70
|
+
"""Definition 7 (Bu, Gu, Jaffe, arXiv:2306.09292, p.6), specialized to
|
|
71
|
+
K=3 registers of n=1 qubit each -- the minimum valid case (K must be
|
|
72
|
+
odd). U = (CNOT_{2->1} CNOT_{3->1}) (CNOT_{1->2} CNOT_{1->3}): layer 1
|
|
73
|
+
fans register 1 into registers 2,3; layer 2 XORs registers 2,3 (their
|
|
74
|
+
post-layer-1 values) back into register 1. Verified basis-state by
|
|
75
|
+
basis-state against the paper's own Lemma 9 identity in
|
|
76
|
+
Dense-Evolution-Discovery's tests/test_quantum_ruzsa_magic_entropy.py."""
|
|
77
|
+
layer1 = _cnot_matrix(0, 2) @ _cnot_matrix(0, 1)
|
|
78
|
+
layer2 = _cnot_matrix(2, 0) @ _cnot_matrix(1, 0)
|
|
79
|
+
return jnp.array(layer2 @ layer1, dtype=jnp.complex128)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@functools.lru_cache(maxsize=1)
|
|
83
|
+
def _key_unitary_k3() -> jnp.ndarray:
|
|
84
|
+
"""Cached, lazily-built K3 Key Unitary -- NOT a bare module-level
|
|
85
|
+
constant (that used to be `_KEY_UNITARY_K3 = _build_key_unitary_k3()`,
|
|
86
|
+
evaluated at import time). A bare module-level `jnp.array(...,
|
|
87
|
+
dtype=jnp.complex128)` runs before anything has had a chance to call
|
|
88
|
+
`dense_evolution.config.ensure_x64()` (only DenseSVSimulator,
|
|
89
|
+
QuantumHardwareRegistry and circuit_to_energy_fn do that, and only
|
|
90
|
+
the first time one of them is constructed) -- so on a bare `import
|
|
91
|
+
dense_evolution`, jax_enable_x64 is still False at this point, and
|
|
92
|
+
JAX both silently truncates the requested complex128 to complex64
|
|
93
|
+
AND raises a UserWarning about it, on every fresh interpreter
|
|
94
|
+
(verified directly: 3-4 near-identical warnings fire on a plain
|
|
95
|
+
`import dense_evolution as de`, one per such module-level constant
|
|
96
|
+
across this file and magic_entropy_shadows.py, before this fix).
|
|
97
|
+
Wrapping it in a cached lazy getter defers construction to first
|
|
98
|
+
actual use (typically after some simulator has already called
|
|
99
|
+
ensure_x64()), which both silences the spurious warning in the
|
|
100
|
+
common case and avoids permanently baking in whatever precision
|
|
101
|
+
happened to be active at import time -- exactly the "lazy, not an
|
|
102
|
+
import-time side effect" policy dense_evolution.config already
|
|
103
|
+
documents for the rest of the package, just not yet applied here."""
|
|
104
|
+
return _build_key_unitary_k3()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _self_convolve_3_core(rho: jnp.ndarray) -> jnp.ndarray:
|
|
108
|
+
"""boxtimes_3(rho,rho,rho) = Tr_{2,3}[V (rho (x) rho (x) rho) V^dagger]
|
|
109
|
+
(Definition 8). `rho` may be pure or mixed. Not built on
|
|
110
|
+
`dense_evolution.physics.entropy.partial_trace` -- that helper is
|
|
111
|
+
pure-statevector-only, and this needs to trace out a general (possibly
|
|
112
|
+
mixed) 3-qubit density matrix built from a possibly-mixed input."""
|
|
113
|
+
key_unitary = _key_unitary_k3()
|
|
114
|
+
rho_full = jnp.kron(jnp.kron(rho, rho), rho)
|
|
115
|
+
evolved = key_unitary @ rho_full @ jnp.conj(key_unitary).T
|
|
116
|
+
tensor = evolved.reshape(2, 2, 2, 2, 2, 2)
|
|
117
|
+
return jnp.einsum("ijkljk->il", tensor)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _magic_entropy_core(rho: jnp.ndarray, eps: float = 1e-12) -> jnp.ndarray:
|
|
121
|
+
reduced = _self_convolve_3_core(rho)
|
|
122
|
+
ev = jnp.linalg.eigvalsh(reduced)
|
|
123
|
+
# Clip then renormalize (same order magic_entropy_from_shadows uses),
|
|
124
|
+
# not clip alone: an unclipped-then-unnormalized eigenvalue sum that
|
|
125
|
+
# has drifted off 1 (numerical trace error in `reduced`) would
|
|
126
|
+
# otherwise get silently truncated at the eps/1.0 clip bounds instead
|
|
127
|
+
# of corrected -- masking the drift rather than fixing it.
|
|
128
|
+
safe_ev = jnp.clip(jnp.real(ev), eps, None)
|
|
129
|
+
safe_ev = safe_ev / jnp.sum(safe_ev)
|
|
130
|
+
return -jnp.sum(safe_ev * jnp.log2(safe_ev))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def magic_entropy(rho: jnp.ndarray) -> float:
|
|
134
|
+
"""Magic entropy of a single-qubit density matrix `rho` (2x2), in bits
|
|
135
|
+
(log2) -- NOTE this differs from
|
|
136
|
+
`dense_evolution.physics.entropy.von_neumann_entropy`'s natural-log
|
|
137
|
+
(nats) convention; kept as log2 here to match both the source paper's
|
|
138
|
+
own convention and the values already published in
|
|
139
|
+
Dense-Evolution-Discovery's Experiment 30.
|
|
140
|
+
|
|
141
|
+
Zero for every single-qubit stabilizer state (|0>, |1>, |+>, |->,
|
|
142
|
+
|+i>, |-i>), positive for non-stabilizer ("magic") states -- e.g. the
|
|
143
|
+
T-state and H-state both give 0.811 bits.
|
|
144
|
+
|
|
145
|
+
Differentiable through `jax.grad`, including at stabilizer states
|
|
146
|
+
where the reduced matrix's eigenvalues are exactly degenerate (e.g.
|
|
147
|
+
the fully mixed state I/2 gives eigenvalues [0.5, 0.5]): unlike
|
|
148
|
+
`uhlmann_fidelity`, which needs a custom `_eigh_degenerate_safe` JVP
|
|
149
|
+
rule because it reconstructs eigenVECTORS (ill-defined in a
|
|
150
|
+
degenerate eigenspace), this function only ever needs eigenVALUES
|
|
151
|
+
(`jnp.linalg.eigvalsh`, no eigenvectors), whose gradient is
|
|
152
|
+
well-defined even at exact degeneracies -- confirmed directly:
|
|
153
|
+
`jax.grad(magic_entropy)` is finite (no NaN) at both the fully mixed
|
|
154
|
+
state and a magic state.
|
|
155
|
+
"""
|
|
156
|
+
rho = jnp.asarray(rho, dtype=jnp.complex128)
|
|
157
|
+
return float(_magic_entropy_core(rho))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
magic_entropy_jit = jax.jit(_magic_entropy_core)
|
|
161
|
+
"""`jax.jit`-compiled entry point for `magic_entropy`. `rho` must already
|
|
162
|
+
be `complex128`. Returns a jnp scalar, not a Python `float` -- call
|
|
163
|
+
`float(...)` yourself if you need one outside a jitted context."""
|