dense-evolution 8.3.0__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. dashboard_core/__init__.py +115 -0
  2. dashboard_core/_gate_tables.py +30 -0
  3. dashboard_core/band_structure.py +71 -0
  4. dashboard_core/circuit_builder_component.py +232 -0
  5. dashboard_core/circuit_diagram.py +216 -0
  6. dashboard_core/crypto_protocols.py +77 -0
  7. dashboard_core/engine.py +326 -0
  8. dashboard_core/graphical_builder.py +114 -0
  9. dashboard_core/hamiltonians.py +593 -0
  10. dashboard_core/mass_decomposition_tool.py +47 -0
  11. dashboard_core/mitigation.py +343 -0
  12. dashboard_core/native_hf_diagnostics.py +62 -0
  13. dashboard_core/noise_tools.py +125 -0
  14. dashboard_core/qasm_library.py +233 -0
  15. dashboard_core/qmmm.py +16 -0
  16. dashboard_core/rag_tool.py +45 -0
  17. dashboard_core/state_visuals.py +288 -0
  18. dashboard_core/system_limits.py +60 -0
  19. dashboard_core/vector_healing.py +102 -0
  20. dashboard_core/visuals.py +158 -0
  21. dashboard_core/vqe.py +533 -0
  22. dashboard_core/wormhole.py +580 -0
  23. dense_evolution/__init__.py +114 -0
  24. dense_evolution/autodiff.py +10 -0
  25. dense_evolution/backends/__init__.py +5 -0
  26. dense_evolution/backends/chunk/__init__.py +37 -0
  27. dense_evolution/backends/chunk/_engine_imports.py +57 -0
  28. dense_evolution/backends/chunk/circuit_chunker.py +55 -0
  29. dense_evolution/backends/chunk/core.py +432 -0
  30. dense_evolution/backends/chunk/disk_overflow.py +232 -0
  31. dense_evolution/backends/chunk/geometry.py +95 -0
  32. dense_evolution/backends/chunk/guard.py +190 -0
  33. dense_evolution/backends/chunk/kernels.py +531 -0
  34. dense_evolution/backends/mps.py +1569 -0
  35. dense_evolution/backends/statevector.py +616 -0
  36. dense_evolution/chunk.py +25 -0
  37. dense_evolution/circuits/__init__.py +20 -0
  38. dense_evolution/circuits/compiler.py +488 -0
  39. dense_evolution/circuits/diagram.py +94 -0
  40. dense_evolution/circuits/gates.py +91 -0
  41. dense_evolution/circuits/parser.py +632 -0
  42. dense_evolution/circuits/qft.py +66 -0
  43. dense_evolution/circuits/random_circuit.py +85 -0
  44. dense_evolution/circuits/registry.py +74 -0
  45. dense_evolution/circuits/topology.py +79 -0
  46. dense_evolution/circuits/trotter.py +265 -0
  47. dense_evolution/circuits/uccsd.py +275 -0
  48. dense_evolution/cli.py +199 -0
  49. dense_evolution/compiler.py +9 -0
  50. dense_evolution/config.py +49 -0
  51. dense_evolution/drawing.py +10 -0
  52. dense_evolution/entropy.py +9 -0
  53. dense_evolution/fermions.py +9 -0
  54. dense_evolution/gates.py +9 -0
  55. dense_evolution/harrison_tb.py +16 -0
  56. dense_evolution/healing.py +18 -0
  57. dense_evolution/interop/__init__.py +18 -0
  58. dense_evolution/interop/qiskit_pennylane.py +406 -0
  59. dense_evolution/measurement.py +10 -0
  60. dense_evolution/mitigation/__init__.py +54 -0
  61. dense_evolution/mitigation/healing.py +215 -0
  62. dense_evolution/mitigation/kl_divergence.py +93 -0
  63. dense_evolution/mitigation/magic_entropy.py +163 -0
  64. dense_evolution/mitigation/magic_entropy_shadows.py +262 -0
  65. dense_evolution/mitigation/renyi.py +168 -0
  66. dense_evolution/mitigation/stabilizer_renyi_entropy.py +103 -0
  67. dense_evolution/mitigation/zne.py +990 -0
  68. dense_evolution/mps.py +9 -0
  69. dense_evolution/native_hf/__init__.py +26 -0
  70. dense_evolution/native_hf/_libcint/LICENSE-libcint +10 -0
  71. dense_evolution/native_hf/_libcint/libdecint.dll +0 -0
  72. dense_evolution/native_hf/assembly.py +304 -0
  73. dense_evolution/native_hf/basis.py +117 -0
  74. dense_evolution/native_hf/boys.py +35 -0
  75. dense_evolution/native_hf/bridge.py +112 -0
  76. dense_evolution/native_hf/cartesian.py +64 -0
  77. dense_evolution/native_hf/coulomb.py +196 -0
  78. dense_evolution/native_hf/differentiable.py +53 -0
  79. dense_evolution/native_hf/gaussians.py +79 -0
  80. dense_evolution/native_hf/kinetic.py +52 -0
  81. dense_evolution/native_hf/libcint_bridge.py +167 -0
  82. dense_evolution/native_hf/overlap.py +91 -0
  83. dense_evolution/native_hf/scf.py +404 -0
  84. dense_evolution/noise/__init__.py +79 -0
  85. dense_evolution/noise/coherent_attack.py +264 -0
  86. dense_evolution/noise/cosmic_ray.py +61 -0
  87. dense_evolution/noise/density_matrix_channels.py +78 -0
  88. dense_evolution/noise/differentiable.py +66 -0
  89. dense_evolution/noise/kraus/__init__.py +6 -0
  90. dense_evolution/noise/kraus/amplitude_damping.py +47 -0
  91. dense_evolution/noise/kraus/bitflip.py +22 -0
  92. dense_evolution/noise/kraus/combined.py +16 -0
  93. dense_evolution/noise/kraus/depolarizing.py +47 -0
  94. dense_evolution/noise/kraus/ideal.py +10 -0
  95. dense_evolution/noise/kraus/phaseflip.py +21 -0
  96. dense_evolution/noise/kraus_channels.py +285 -0
  97. dense_evolution/noise/oscillating.py +32 -0
  98. dense_evolution/noise/pink.py +80 -0
  99. dense_evolution/observables.py +11 -0
  100. dense_evolution/parser.py +9 -0
  101. dense_evolution/physics/__init__.py +27 -0
  102. dense_evolution/physics/entropy.py +161 -0
  103. dense_evolution/physics/fermions.py +322 -0
  104. dense_evolution/physics/observables.py +523 -0
  105. dense_evolution/physics/qec.py +1113 -0
  106. dense_evolution/physics/spectral.py +143 -0
  107. dense_evolution/physics/states.py +43 -0
  108. dense_evolution/protocols/__init__.py +27 -0
  109. dense_evolution/protocols/bb84.py +133 -0
  110. dense_evolution/protocols/di_qkd_ghz.py +199 -0
  111. dense_evolution/protocols/dicka_protocol2.py +124 -0
  112. dense_evolution/qec.py +20 -0
  113. dense_evolution/qft.py +9 -0
  114. dense_evolution/qmmm/__init__.py +13 -0
  115. dense_evolution/qmmm/ase_bridge.py +97 -0
  116. dense_evolution/qmmm/forces.py +388 -0
  117. dense_evolution/qmmm/propagation.py +80 -0
  118. dense_evolution/qmmm/region.py +137 -0
  119. dense_evolution/random_circuit.py +15 -0
  120. dense_evolution/registry.py +9 -0
  121. dense_evolution/simulator.py +10 -0
  122. dense_evolution/solvers/__init__.py +19 -0
  123. dense_evolution/solvers/autodiff.py +169 -0
  124. dense_evolution/solvers/harrison_tb.py +189 -0
  125. dense_evolution/solvers/vhd_tb.py +187 -0
  126. dense_evolution/states.py +9 -0
  127. dense_evolution/topology.py +9 -0
  128. dense_evolution/trotter.py +9 -0
  129. dense_evolution/utils/__init__.py +13 -0
  130. dense_evolution/utils/drawing.py +101 -0
  131. dense_evolution/utils/mass_decomposition.py +246 -0
  132. dense_evolution/utils/measurement.py +94 -0
  133. dense_evolution/vhd_tb.py +16 -0
  134. dense_evolution-8.3.0.dist-info/METADATA +366 -0
  135. dense_evolution-8.3.0.dist-info/RECORD +165 -0
  136. dense_evolution-8.3.0.dist-info/WHEEL +5 -0
  137. dense_evolution-8.3.0.dist-info/entry_points.txt +2 -0
  138. dense_evolution-8.3.0.dist-info/licenses/license.md +58 -0
  139. dense_evolution-8.3.0.dist-info/top_level.txt +5 -0
  140. ia_utils/__init__.py +0 -0
  141. ia_utils/adversarial_vector_attack.py +196 -0
  142. ia_utils/rag.py +288 -0
  143. ia_utils/vector_healing.py +399 -0
  144. local_site/__init__.py +0 -0
  145. local_site/app/__init__.py +0 -0
  146. local_site/app/server.py +1009 -0
  147. mcp_server/__init__.py +0 -0
  148. mcp_server/client.py +324 -0
  149. mcp_server/config.py +32 -0
  150. mcp_server/models.py +347 -0
  151. mcp_server/molecules.py +71 -0
  152. mcp_server/server.py +119 -0
  153. mcp_server/tools/__init__.py +0 -0
  154. mcp_server/tools/chemistry_tools.py +225 -0
  155. mcp_server/tools/circuit_tools.py +83 -0
  156. mcp_server/tools/crypto_tools.py +66 -0
  157. mcp_server/tools/mitigation_tools.py +81 -0
  158. mcp_server/tools/noise_tools.py +60 -0
  159. mcp_server/tools/retrieval_tools.py +44 -0
  160. mcp_server/tools/system_tools.py +149 -0
  161. mcp_server/tools/wormhole_tools.py +142 -0
  162. mcp_server/utils/__init__.py +0 -0
  163. mcp_server/utils/cache.py +55 -0
  164. mcp_server/utils/images.py +67 -0
  165. mcp_server/utils/truncation.py +38 -0
@@ -0,0 +1,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."""