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,262 @@
|
|
|
1
|
+
"""Classical-shadows-based estimator for `magic_entropy` (see
|
|
2
|
+
`magic_entropy.py` in this same subpackage) -- estimates the same
|
|
3
|
+
single-qubit non-stabilizerness quantity from randomized measurement
|
|
4
|
+
snapshots instead of requiring the exact density matrix.
|
|
5
|
+
|
|
6
|
+
Originated from a Colab proposal for a `dense_evolution/circuits/shadows.py`
|
|
7
|
+
module (following Huang, Kueng, Preskill 2020, "Predicting Many Properties
|
|
8
|
+
of a Quantum System from Very Few Measurements") with a real bug in its
|
|
9
|
+
purity estimator (a missing transpose in a U-statistic einsum contraction,
|
|
10
|
+
silent whenever every snapshot happened to be real-valued). Fixed, then
|
|
11
|
+
extended -- using the same multi-copy U-statistic trick the paper says
|
|
12
|
+
"readily generalizes to higher order polynomials" -- to estimate
|
|
13
|
+
`magic_entropy`'s reduced convolution matrix from shadow snapshots instead
|
|
14
|
+
of the exact rho. Matured across Dense-Evolution-Discovery Experiment 31
|
|
15
|
+
(three real gaps found and closed there before promotion: the purity
|
|
16
|
+
estimator bug, missing median-of-means robustness, and no
|
|
17
|
+
sample-complexity guidance):
|
|
18
|
+
https://tatopenn-cell.github.io/Dense-Evolution-Discovery/quantum_shadows_magic_entropy/
|
|
19
|
+
|
|
20
|
+
API SHAPE differs from every other function in this subpackage: sampling
|
|
21
|
+
(`sample_classical_shadow`) and estimation (`magic_entropy_from_shadows`)
|
|
22
|
+
are separate steps, matching how classical shadows work in general -- the
|
|
23
|
+
snapshot data can come from this simulator (`sample_classical_shadow` uses
|
|
24
|
+
oracle access to `rho`'s exact Born-rule probabilities, something only a
|
|
25
|
+
simulator has) or, in principle, from real hardware measurement outcomes
|
|
26
|
+
reconstructed the same way (`rho_hat = 3 U^dagger |b><b| U - I` per
|
|
27
|
+
snapshot, from a recorded basis+outcome).
|
|
28
|
+
|
|
29
|
+
Restricted to SINGLE-QUBIT density matrices, matching `magic_entropy`'s
|
|
30
|
+
own scope.
|
|
31
|
+
"""
|
|
32
|
+
import functools
|
|
33
|
+
|
|
34
|
+
import jax
|
|
35
|
+
import jax.numpy as jnp
|
|
36
|
+
import numpy as np
|
|
37
|
+
|
|
38
|
+
from .magic_entropy import _key_unitary_k3
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"sample_classical_shadow", "magic_entropy_from_shadows",
|
|
42
|
+
"approx_shadow_std", "fit_shadow_sample_complexity",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
# Single-qubit random-Pauli classical shadow protocol (Huang, Kueng,
|
|
46
|
+
# Preskill 2020, eq. 2-3): the three diagonalizing unitaries for X, Y, Z.
|
|
47
|
+
@functools.lru_cache(maxsize=1)
|
|
48
|
+
def _basis_u() -> jnp.ndarray:
|
|
49
|
+
"""Cached, lazily-built stack of the 3 diagonalizing unitaries -- same
|
|
50
|
+
"defer past the import-time x64 warning" fix as magic_entropy.py's
|
|
51
|
+
_key_unitary_k3(); see that function's docstring for why a bare
|
|
52
|
+
module-level `jnp.array(..., dtype=jnp.complex128)` here used to fire
|
|
53
|
+
a spurious UserWarning on every `import dense_evolution`."""
|
|
54
|
+
h = jnp.array([[1.0, 1.0], [1.0, -1.0]], dtype=jnp.complex128) / jnp.sqrt(2.0)
|
|
55
|
+
sdag = jnp.array([[1.0, 0.0], [0.0, -1j]], dtype=jnp.complex128)
|
|
56
|
+
return jnp.stack([h, h @ sdag, jnp.eye(2, dtype=jnp.complex128)])
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def sample_classical_shadow(rho: jnp.ndarray, n_snapshots: int, seed: int = 0) -> jnp.ndarray:
|
|
60
|
+
"""Simulates the real single-qubit random-Pauli classical-shadow
|
|
61
|
+
measurement protocol against a known `rho` (2x2, pure or mixed): for
|
|
62
|
+
each of `n_snapshots` independent draws, picks a random Pauli basis
|
|
63
|
+
uniformly, samples a computational-basis outcome from the true
|
|
64
|
+
Born-rule probability under that basis (this simulator has oracle
|
|
65
|
+
access to `rho`, unlike real hardware), then reconstructs the
|
|
66
|
+
classical snapshot `rho_hat = 3 U^dagger |b><b| U - I`.
|
|
67
|
+
|
|
68
|
+
Returns an `(n_snapshots, 2, 2)` complex128 array -- feed this
|
|
69
|
+
directly into `magic_entropy_from_shadows`.
|
|
70
|
+
|
|
71
|
+
Each individual `rho_hat` is NOT a valid density matrix on its own
|
|
72
|
+
(can have negative eigenvalues) -- only the average over many
|
|
73
|
+
snapshots converges to the true `rho`. Verified directly in
|
|
74
|
+
Dense-Evolution-Discovery Experiment 31: the empirical mean over
|
|
75
|
+
200,000 snapshots matched the true state to within 0.004.
|
|
76
|
+
"""
|
|
77
|
+
rho = jnp.asarray(rho, dtype=jnp.complex128)
|
|
78
|
+
key = jax.random.PRNGKey(seed)
|
|
79
|
+
key_basis, key_bit = jax.random.split(key)
|
|
80
|
+
bases = jax.random.randint(key_basis, (n_snapshots,), 0, 3)
|
|
81
|
+
|
|
82
|
+
basis_u = _basis_u()
|
|
83
|
+
|
|
84
|
+
def prob0(basis_idx):
|
|
85
|
+
u = basis_u[basis_idx]
|
|
86
|
+
rotated = u @ rho @ jnp.conj(u).T
|
|
87
|
+
return jnp.clip(jnp.real(rotated[0, 0]), 0.0, 1.0)
|
|
88
|
+
|
|
89
|
+
probs0 = jax.vmap(prob0)(bases)
|
|
90
|
+
uniforms = jax.random.uniform(key_bit, (n_snapshots,))
|
|
91
|
+
bits = (uniforms > probs0).astype(jnp.int32)
|
|
92
|
+
|
|
93
|
+
def snapshot_matrix(basis_idx, bit):
|
|
94
|
+
u = basis_u[basis_idx]
|
|
95
|
+
b_ket = jnp.array([1.0, 0.0], dtype=jnp.complex128) * (1 - bit) + \
|
|
96
|
+
jnp.array([0.0, 1.0], dtype=jnp.complex128) * bit
|
|
97
|
+
proj = jnp.outer(b_ket, jnp.conj(b_ket))
|
|
98
|
+
return 3.0 * (jnp.conj(u).T @ proj @ u) - jnp.eye(2, dtype=jnp.complex128)
|
|
99
|
+
|
|
100
|
+
return jax.vmap(snapshot_matrix)(bases, bits)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@functools.lru_cache(maxsize=1)
|
|
104
|
+
def _o_operators():
|
|
105
|
+
"""O_ab = V^dagger (|b><a| (x) I_4) V such that R_ab = Tr[O_ab . rho^{(x)3}]
|
|
106
|
+
-- the same construction `magic_entropy.py`'s `_self_convolve_3_core`
|
|
107
|
+
applies to exact `rho`, here turned into fixed operators so each entry
|
|
108
|
+
is a LINEAR functional of `rho^{(x)3}` (the shape a shadow-snapshot
|
|
109
|
+
U-statistic estimator needs). Reuses this package's own
|
|
110
|
+
`_key_unitary_k3()` directly rather than duplicating it (unlike
|
|
111
|
+
Dense-Evolution-Discovery's per-script self-containment convention,
|
|
112
|
+
this library's internal modules import from each other freely). The
|
|
113
|
+
`|b><a|` (not `|a><b|`) projector is deliberate: a direct index-expansion
|
|
114
|
+
check (not just the cyclic-trace derivation, which looks right on paper
|
|
115
|
+
but hides the swap) showed `Tr[(|a><b| (x) I) M] = R_ba`, not `R_ab` --
|
|
116
|
+
caught in Discovery Experiment 31 by a unit test checking matrix
|
|
117
|
+
entries directly, not just the downstream entropy.
|
|
118
|
+
|
|
119
|
+
Cached/lazy (functools.lru_cache), not a bare module-level constant
|
|
120
|
+
-- same "defer past the import-time x64 warning" fix as
|
|
121
|
+
magic_entropy.py's _key_unitary_k3() and this module's _basis_u()."""
|
|
122
|
+
i4 = jnp.eye(4, dtype=jnp.complex128)
|
|
123
|
+
v = _key_unitary_k3()
|
|
124
|
+
ops = {}
|
|
125
|
+
for a in range(2):
|
|
126
|
+
for b in range(2):
|
|
127
|
+
proj_ba = jnp.zeros((2, 2), dtype=jnp.complex128).at[b, a].set(1.0)
|
|
128
|
+
ops[(a, b)] = jnp.conj(v).T @ jnp.kron(proj_ba, i4) @ v
|
|
129
|
+
return ops
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _median_of_means(values: np.ndarray, n_groups: int) -> float:
|
|
133
|
+
"""Split real-valued `values` into `n_groups` contiguous batches,
|
|
134
|
+
average each batch, then return the median of those batch means --
|
|
135
|
+
Huang et al.'s standard robustification for shadow-estimator
|
|
136
|
+
U-statistics. Unlike a single overall mean, the median tolerates up
|
|
137
|
+
to `n_groups // 2` entirely corrupted/outlier batches (e.g. a
|
|
138
|
+
systematic calibration fault affecting one contiguous stretch of a
|
|
139
|
+
measurement run) without being dragged toward them -- verified
|
|
140
|
+
directly in Dense-Evolution-Discovery Experiment 31: stays within 0.5
|
|
141
|
+
of the true value at 40% of samples corrupted, while a plain mean is
|
|
142
|
+
dragged from 1.0 to -19.4 under the same corruption."""
|
|
143
|
+
values = np.asarray(values, dtype=float)
|
|
144
|
+
n = len(values)
|
|
145
|
+
n_groups = max(1, min(n_groups, n))
|
|
146
|
+
group_size = n // n_groups
|
|
147
|
+
trimmed = values[: group_size * n_groups]
|
|
148
|
+
group_means = trimmed.reshape(n_groups, group_size).mean(axis=1)
|
|
149
|
+
return float(np.median(group_means))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def magic_entropy_from_shadows(shadow_snapshots: jnp.ndarray, n_groups: int = 20) -> float:
|
|
153
|
+
"""Estimates `magic_entropy(rho)` from classical shadow snapshots of
|
|
154
|
+
`rho` (from `sample_classical_shadow`, or real hardware measurement
|
|
155
|
+
data reconstructed the same way) instead of the exact density matrix.
|
|
156
|
+
|
|
157
|
+
Groups the snapshots into disjoint triples, estimates each entry of
|
|
158
|
+
the 3-copy self-convolution's reduced matrix `R` via median-of-means
|
|
159
|
+
over `Tr[O_ab . (rho_hat_i (x) rho_hat_j (x) rho_hat_k)]` (unbiased,
|
|
160
|
+
since each triple's three snapshots are independent unbiased
|
|
161
|
+
estimators of `rho`; real and imaginary parts of each entry are
|
|
162
|
+
median-of-means'd separately, the standard practical choice since a
|
|
163
|
+
complex median has no single definition), then computes the von
|
|
164
|
+
Neumann entropy of the (Hermitized, eigenvalue-clipped, trace-
|
|
165
|
+
renormalized) ESTIMATED `R` classically -- entropy itself is never
|
|
166
|
+
shadow-estimated directly, matching how Huang et al. handle their own
|
|
167
|
+
Renyi-2 entanglement entropy example.
|
|
168
|
+
|
|
169
|
+
Not `jax.jit`-compatible (unlike every other function in this
|
|
170
|
+
subpackage): median-of-means uses `numpy.median`, which has no
|
|
171
|
+
equivalent JAX primitive at this scale.
|
|
172
|
+
|
|
173
|
+
See `approx_shadow_std`/`fit_shadow_sample_complexity` for how many
|
|
174
|
+
snapshots this needs for a given error tolerance.
|
|
175
|
+
"""
|
|
176
|
+
n = shadow_snapshots.shape[0]
|
|
177
|
+
n_triples = n // 3
|
|
178
|
+
if n_triples < 1:
|
|
179
|
+
raise ValueError(f"need at least 3 shadow snapshots to form one triple, got {n}")
|
|
180
|
+
triples = shadow_snapshots[: n_triples * 3].reshape(n_triples, 3, 2, 2)
|
|
181
|
+
|
|
182
|
+
def triple_kron(t):
|
|
183
|
+
return jnp.kron(jnp.kron(t[0], t[1]), t[2])
|
|
184
|
+
|
|
185
|
+
rho3_batch = jax.vmap(triple_kron)(triples)
|
|
186
|
+
|
|
187
|
+
r_hat = jnp.zeros((2, 2), dtype=jnp.complex128)
|
|
188
|
+
for (a, b), o_ab in _o_operators().items():
|
|
189
|
+
vals = np.array(jnp.einsum("ij,tji->t", o_ab, rho3_batch))
|
|
190
|
+
real_part = _median_of_means(vals.real, n_groups)
|
|
191
|
+
imag_part = _median_of_means(vals.imag, n_groups)
|
|
192
|
+
r_hat = r_hat.at[a, b].set(real_part + 1j * imag_part)
|
|
193
|
+
|
|
194
|
+
r_hat = 0.5 * (r_hat + jnp.conj(r_hat).T) # enforce Hermiticity
|
|
195
|
+
ev = jnp.linalg.eigvalsh(r_hat)
|
|
196
|
+
safe_ev = jnp.clip(ev.real, 1e-9, None)
|
|
197
|
+
safe_ev = safe_ev / jnp.sum(safe_ev) # renormalize (estimation noise can shift trace off 1)
|
|
198
|
+
return float(-jnp.sum(safe_ev * jnp.log2(safe_ev)))
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# Empirically fitted in Dense-Evolution-Discovery Experiment 31 (20 trials
|
|
202
|
+
# per snapshot count on a |T> state, n_groups=20): std(n_snapshots) ~
|
|
203
|
+
# _FIT_C / n_snapshots ** _FIT_P. The T-state is a standard maximally-magic
|
|
204
|
+
# single-qubit state, a reasonable but NOT rigorously proven proxy for a
|
|
205
|
+
# "hard" case -- this is a rough built-in guide, not a formal guarantee for
|
|
206
|
+
# an arbitrary rho.
|
|
207
|
+
_FIT_C = 11.751
|
|
208
|
+
_FIT_P = 0.546
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def approx_shadow_std(n_snapshots: int) -> float:
|
|
212
|
+
"""Rough approximate standard deviation (bits) of
|
|
213
|
+
`magic_entropy_from_shadows`'s estimate at a given snapshot count,
|
|
214
|
+
from an empirical fit (not a formal theorem) calibrated on a `|T>`
|
|
215
|
+
state in Dense-Evolution-Discovery Experiment 31: 20 independent
|
|
216
|
+
trials at each of 4 snapshot counts (3,000-100,000), a log-log linear
|
|
217
|
+
regression gave `std(n) ~ 11.75 / n^0.546` -- the fitted exponent
|
|
218
|
+
(0.546) is close to the ~0.5 ("error shrinks like 1/sqrt(n)") standard
|
|
219
|
+
shadow/median-of-means theory predicts.
|
|
220
|
+
|
|
221
|
+
This is a quick sanity-check fallback, not a guarantee for an
|
|
222
|
+
arbitrary state -- call `fit_shadow_sample_complexity` on YOUR
|
|
223
|
+
specific state if you need a real, state-calibrated error bound.
|
|
224
|
+
"""
|
|
225
|
+
return _FIT_C / float(n_snapshots) ** _FIT_P
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def fit_shadow_sample_complexity(rho: jnp.ndarray, exact_value: float, n_snapshots_list, n_trials: int, seed_base: int = 0):
|
|
229
|
+
"""Empirically measures `magic_entropy_from_shadows`'s standard
|
|
230
|
+
deviation across `n_trials` independent shadow samplings at each
|
|
231
|
+
snapshot count in `n_snapshots_list`, for the SPECIFIC state `rho`
|
|
232
|
+
(rather than trusting `approx_shadow_std`'s built-in T-state-derived
|
|
233
|
+
fallback), then fits `std(n) ~ C / n^p` via log-log linear regression
|
|
234
|
+
-- the same method used to derive `approx_shadow_std`'s constants in
|
|
235
|
+
the first place (Dense-Evolution-Discovery Experiment 31).
|
|
236
|
+
|
|
237
|
+
`exact_value` should be `magic_entropy(rho)` -- used only to also
|
|
238
|
+
report each snapshot count's mean estimation bias alongside the
|
|
239
|
+
fitted curve, not part of the fit itself.
|
|
240
|
+
|
|
241
|
+
Returns `(rows, fit_c, fit_p)`: `rows` is a list of per-snapshot-count
|
|
242
|
+
dicts (`n_snapshots`, `mean_estimate`, `std_estimate`,
|
|
243
|
+
`mean_abs_error`); `fit_c`/`fit_p` are the fitted constants for
|
|
244
|
+
`C / n^p`, usable the same way as `approx_shadow_std` (or pass them to
|
|
245
|
+
`approx_shadow_std`'s formula directly: `fit_c / n ** fit_p`).
|
|
246
|
+
"""
|
|
247
|
+
rows = []
|
|
248
|
+
for n_snap in n_snapshots_list:
|
|
249
|
+
estimates = []
|
|
250
|
+
for trial in range(n_trials):
|
|
251
|
+
snaps = sample_classical_shadow(rho, n_snap, seed=seed_base + trial)
|
|
252
|
+
estimates.append(magic_entropy_from_shadows(snaps))
|
|
253
|
+
estimates = np.array(estimates)
|
|
254
|
+
rows.append({
|
|
255
|
+
"n_snapshots": n_snap, "n_trials": n_trials,
|
|
256
|
+
"mean_estimate": float(estimates.mean()), "std_estimate": float(estimates.std()),
|
|
257
|
+
"mean_abs_error": float(abs(estimates.mean() - exact_value)),
|
|
258
|
+
})
|
|
259
|
+
log_n = np.log([r["n_snapshots"] for r in rows])
|
|
260
|
+
log_std = np.log([r["std_estimate"] for r in rows])
|
|
261
|
+
slope, intercept = np.polyfit(log_n, log_std, 1)
|
|
262
|
+
return rows, float(np.exp(intercept)), float(-slope)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Sandwiched Quantum Renyi Divergence for full density-matrix diagnostics
|
|
2
|
+
(Muller-Lennert, Reeb, Wolf, Wilde, "On quantum Renyi entropies: a new
|
|
3
|
+
generalization and some applications", arXiv:1306.3142, Definition 1).
|
|
4
|
+
|
|
5
|
+
D_alpha(rho||sigma) = 1/(alpha-1) * log2 Tr[(sigma^e rho sigma^e)^alpha],
|
|
6
|
+
e = (1-alpha)/(2*alpha), with the alpha->1 limit reducing to the standard
|
|
7
|
+
quantum relative entropy and alpha=1/2 reducing to a fidelity-based form.
|
|
8
|
+
|
|
9
|
+
Originated from a Colab proposal with a real bug in its case_general
|
|
10
|
+
branch: `tr_inner = jnp.maximum(tr_inner, 1.0)` floors the inner trace at
|
|
11
|
+
1.0 even when the true value is < 1 (the normal case for non-commuting
|
|
12
|
+
rho, sigma), silently forcing every result to log2(1)=0 -- confirmed
|
|
13
|
+
directly in the Colab's own printed output (alpha=1.5 gave exactly
|
|
14
|
+
0.000000 across an entire rotation sweep). A second, deeper bug survived
|
|
15
|
+
the floor-value fix alone: for alpha > 1, a trace below 1 is not a
|
|
16
|
+
numerical artifact to clamp away, it is the genuine signature of a
|
|
17
|
+
support mismatch (supp(rho) not contained in supp(sigma)), which the
|
|
18
|
+
divergence must report as +inf, not a finite (and wrong-signed) number --
|
|
19
|
+
verified by hand on two different pure states: Tr[Q^1.5] = 0.6759,
|
|
20
|
+
matching the closed-form prediction (|<sigma|rho>|^2)^alpha exactly, and
|
|
21
|
+
plugging that into the naive formula gives a finite NEGATIVE divergence,
|
|
22
|
+
worse than the original bug's silent zero since it looks plausible
|
|
23
|
+
instead of visibly wrong.
|
|
24
|
+
|
|
25
|
+
Fixed and validated in Dense-Evolution-Discovery, Experiment 29
|
|
26
|
+
(https://tatopenn-cell.github.io/Dense-Evolution-Discovery/sandwiched_renyi_density_matrix/):
|
|
27
|
+
against the alpha->1 relative-entropy limit (matches an independent numpy
|
|
28
|
+
reference to 4 decimal places), the commuting/diagonal case (reduces
|
|
29
|
+
exactly to the classical Renyi divergence), and the support-violation
|
|
30
|
+
+inf case (verified at alpha>1, confirmed the branch does not fire
|
|
31
|
+
spuriously at alpha<1).
|
|
32
|
+
|
|
33
|
+
Its originally proposed use case -- replacing the JSD-based truncation
|
|
34
|
+
criterion in dense_evolution.mps's bond-dimension search -- was
|
|
35
|
+
independently disproven: on the diagonal singular-value spectrum used
|
|
36
|
+
there, rho and sigma commute, so a non-commuting-aware divergence induces
|
|
37
|
+
the exact same truncation ordering as JSD (5 benchmark configurations,
|
|
38
|
+
byte-identical chi_used and truncation error every time) -- nothing for
|
|
39
|
+
it to add in that setting. Promoted here instead for the genuinely
|
|
40
|
+
non-commuting full-density-matrix diagnostic use case it WAS validated
|
|
41
|
+
against: alongside uhlmann_fidelity, tracking a Bell state degraded by
|
|
42
|
+
amplitude damping, where the two metrics' noise-sensitivity curves
|
|
43
|
+
visibly diverge from each other.
|
|
44
|
+
"""
|
|
45
|
+
import jax
|
|
46
|
+
import jax.numpy as jnp
|
|
47
|
+
|
|
48
|
+
__all__ = ["sandwiched_renyi_divergence", "sandwiched_renyi_divergence_jit"]
|
|
49
|
+
|
|
50
|
+
_EPS = 1e-12
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _case_half(rho, sigma):
|
|
54
|
+
ev_r, ec_r = jnp.linalg.eigh(rho)
|
|
55
|
+
safe_ev_r = jnp.where(ev_r > _EPS, ev_r, 0.0)
|
|
56
|
+
sqrt_rho = (ec_r * jnp.sqrt(safe_ev_r)) @ jnp.conj(ec_r).T
|
|
57
|
+
uhlmann_mat = sqrt_rho @ sigma @ sqrt_rho
|
|
58
|
+
ev_u = jnp.linalg.eigvalsh(uhlmann_mat)
|
|
59
|
+
safe_ev_u = jnp.where(ev_u > _EPS, ev_u, 0.0)
|
|
60
|
+
fidelity = jnp.clip(jnp.sum(jnp.sqrt(safe_ev_u)), 0.0, 1.0)
|
|
61
|
+
return -2.0 * jnp.log2(jnp.maximum(fidelity, _EPS))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _case_one(rho, sigma):
|
|
65
|
+
ev_r, ec_r = jnp.linalg.eigh(rho)
|
|
66
|
+
safe_ev_r = jnp.where(ev_r > _EPS, ev_r, 1.0)
|
|
67
|
+
log_rho = (ec_r * jnp.where(ev_r > _EPS, jnp.log2(safe_ev_r), 0.0)) @ jnp.conj(ec_r).T
|
|
68
|
+
ev_s, ec_s = jnp.linalg.eigh(sigma)
|
|
69
|
+
safe_ev_s = jnp.where(ev_s > _EPS, ev_s, 1.0)
|
|
70
|
+
log_sigma = (ec_s * jnp.where(ev_s > _EPS, jnp.log2(safe_ev_s), 0.0)) @ jnp.conj(ec_s).T
|
|
71
|
+
return jnp.trace(rho @ (log_rho - log_sigma)).real
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _case_general(rho, sigma, alpha):
|
|
75
|
+
exponent = (1.0 - alpha) / (2.0 * alpha)
|
|
76
|
+
ev_s, ec_s = jnp.linalg.eigh(sigma)
|
|
77
|
+
mask_s = ev_s > _EPS
|
|
78
|
+
safe_ev_s = jnp.where(mask_s, ev_s, 1.0)
|
|
79
|
+
pow_ev_s = jnp.where(mask_s, safe_ev_s ** exponent, 0.0)
|
|
80
|
+
sigma_pow = (ec_s * pow_ev_s) @ jnp.conj(ec_s).T
|
|
81
|
+
|
|
82
|
+
int_m = sigma_pow @ rho @ sigma_pow
|
|
83
|
+
v_int = jnp.linalg.eigvalsh(int_m)
|
|
84
|
+
mask_int = v_int > _EPS
|
|
85
|
+
safe_v_int = jnp.where(mask_int, v_int, 1.0)
|
|
86
|
+
pow_v_int = jnp.where(mask_int, safe_v_int ** alpha, 0.0)
|
|
87
|
+
|
|
88
|
+
tr_inner = jnp.sum(pow_v_int)
|
|
89
|
+
|
|
90
|
+
# For alpha > 1, D_alpha is finite only when supp(rho) subset supp(sigma);
|
|
91
|
+
# tr_inner < 1 there is the genuine signature of a support mismatch, not
|
|
92
|
+
# a numerical artifact -- see the module docstring's bug-history note.
|
|
93
|
+
is_support_violation = (alpha > 1.0) & (tr_inner < 1.0 - 1e-9)
|
|
94
|
+
tr_inner_safe = jnp.maximum(tr_inner, _EPS)
|
|
95
|
+
finite_result = ((1.0 / (alpha - 1.0)) * jnp.log2(tr_inner_safe)).real
|
|
96
|
+
return jnp.where(is_support_violation, jnp.inf, finite_result)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _sandwiched_renyi_divergence_core(rho: jnp.ndarray, sigma: jnp.ndarray, alpha: float = 1.5) -> jnp.ndarray:
|
|
100
|
+
# alpha must be a jnp value, not a raw Python float, before it reaches
|
|
101
|
+
# _case_general: jax.lax.cond traces BOTH branches regardless of which
|
|
102
|
+
# one runs, and `1.0 / (alpha - 1.0)` at alpha=1.0 raises a Python
|
|
103
|
+
# ZeroDivisionError if alpha is still a plain float at that point (the
|
|
104
|
+
# case_one branch's own guard never gets a chance to skip it) -- under
|
|
105
|
+
# jax.jit this conversion happens automatically (all arguments get
|
|
106
|
+
# traced), which is why the original un-split Discovery version never
|
|
107
|
+
# hit this; the eager (non-jit) core path here needs it explicitly.
|
|
108
|
+
alpha = jnp.asarray(alpha, dtype=jnp.float64)
|
|
109
|
+
is_half = jnp.isclose(alpha, 0.5)
|
|
110
|
+
is_one = jnp.isclose(alpha, 1.0)
|
|
111
|
+
return jax.lax.cond(
|
|
112
|
+
is_half, lambda: _case_half(rho, sigma),
|
|
113
|
+
lambda: jax.lax.cond(is_one, lambda: _case_one(rho, sigma), lambda: _case_general(rho, sigma, alpha)),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def sandwiched_renyi_divergence(rho: jnp.ndarray, sigma: jnp.ndarray, alpha: float = 1.5) -> float:
|
|
118
|
+
"""Sandwiched quantum Renyi divergence D_alpha(rho||sigma), in bits
|
|
119
|
+
(log2). `rho`, `sigma` are density matrices of the same dimension;
|
|
120
|
+
`alpha` selects the order (0.5 -> fidelity-based, 1.0 -> standard
|
|
121
|
+
relative entropy, both handled as exact closed-form limits rather
|
|
122
|
+
than through the general formula's own alpha->0.5/1 numerical
|
|
123
|
+
instability).
|
|
124
|
+
|
|
125
|
+
Zero when rho == sigma at every alpha (verified). For alpha > 1,
|
|
126
|
+
returns `+inf` when supp(rho) is not contained in supp(sigma) --
|
|
127
|
+
e.g. two different pure (rank-1) states -- rather than a finite
|
|
128
|
+
number; see the module docstring for why this is the mathematically
|
|
129
|
+
correct behavior, not an edge-case failure.
|
|
130
|
+
|
|
131
|
+
KNOWN LIMITATION at exactly alpha=1.0: the same support-violation
|
|
132
|
+
check is NOT applied to the alpha=1 (relative-entropy) branch, which
|
|
133
|
+
instead clips log(0)-type contributions to 0 rather than diverging --
|
|
134
|
+
e.g. D_1(rho||sigma) for two different pure states returns 0.0, not
|
|
135
|
+
+inf, even though the true relative entropy diverges there too.
|
|
136
|
+
Experiment 29 validated the alpha=1 branch only against full-rank
|
|
137
|
+
(depolarized) inputs specifically to sidestep this exactly-singular
|
|
138
|
+
case (`scipy.linalg.logm` itself raises `LogmExactlySingularWarning`
|
|
139
|
+
on singular inputs -- an inherent ill-conditioning of relative
|
|
140
|
+
entropy near degenerate support, not unique to this implementation).
|
|
141
|
+
Do not rely on alpha=1 to correctly flag a support mismatch; use
|
|
142
|
+
alpha slightly above 1 (e.g. 1.001) if that matters for your use case.
|
|
143
|
+
|
|
144
|
+
Validation-only, like `uhlmann_fidelity`: meant to grade a correction
|
|
145
|
+
against a known reference state, not to feed into one (see
|
|
146
|
+
`uhlmann_fidelity`'s docstring for the full "ideal state as oracle"
|
|
147
|
+
argument, which applies here identically).
|
|
148
|
+
|
|
149
|
+
Examples
|
|
150
|
+
--------
|
|
151
|
+
>>> import numpy as np
|
|
152
|
+
>>> from dense_evolution.mitigation.renyi import sandwiched_renyi_divergence
|
|
153
|
+
>>> rho = np.array([[1, 0], [0, 0]], dtype=complex)
|
|
154
|
+
>>> round(float(sandwiched_renyi_divergence(rho, rho, alpha=1.5)), 4)
|
|
155
|
+
0.0
|
|
156
|
+
>>> sigma = np.array([[0.5, 0], [0, 0.5]], dtype=complex)
|
|
157
|
+
>>> round(float(sandwiched_renyi_divergence(rho, sigma, alpha=1.5)), 4)
|
|
158
|
+
1.0
|
|
159
|
+
"""
|
|
160
|
+
rho = jnp.asarray(rho, dtype=jnp.complex128)
|
|
161
|
+
sigma = jnp.asarray(sigma, dtype=jnp.complex128)
|
|
162
|
+
return float(_sandwiched_renyi_divergence_core(rho, sigma, alpha))
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
sandwiched_renyi_divergence_jit = jax.jit(_sandwiched_renyi_divergence_core)
|
|
166
|
+
"""`jax.jit`-compiled entry point for `sandwiched_renyi_divergence`. `rho`/
|
|
167
|
+
`sigma` must already be `complex128`. Returns a jnp scalar, not a Python
|
|
168
|
+
`float`."""
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Stabilizer Renyi Entropy (SRE): a per-STATE nonstabilizerness ("magic")
|
|
2
|
+
monotone (Leone, Oliviero, Hamma, "Stabilizer Renyi Entropy",
|
|
3
|
+
arXiv:2106.12587, Phys. Rev. Lett. 128, 050402 (2022), Eq. 5-8 there,
|
|
4
|
+
labeled Eq. 14/18 in the paper that motivated promoting this).
|
|
5
|
+
|
|
6
|
+
NOT the same quantity as `dense_evolution.mitigation.magic_entropy`
|
|
7
|
+
(Bu-Gu-Jaffe's 3-fold self-convolution "Key Unitary" construction,
|
|
8
|
+
single-qubit only) or `sandwiched_renyi_divergence` (Muller-Lennert et
|
|
9
|
+
al., a DIVERGENCE between TWO density matrices -- "how different are rho
|
|
10
|
+
and sigma", answering a different question entirely; the shared "Renyi"
|
|
11
|
+
in both names is a coincidence of both being alpha-generalizations of
|
|
12
|
+
entropy applied to different objects, not overlapping math). This SRE is
|
|
13
|
+
a genuinely different, MULTI-qubit, SINGLE-state magic monotone: zero for
|
|
14
|
+
every stabilizer state, positive otherwise.
|
|
15
|
+
|
|
16
|
+
M_2(psi) = -log2[ (1/d) * sum_a sum_b |WHT[c_a](b)|^4 ], where
|
|
17
|
+
c_a(x) = conj(psi(x)) * psi(x XOR a) and WHT is the length-d Walsh-Hadamard
|
|
18
|
+
transform (signmat[b,x] = (-1)^popcount(b AND x)), d = 2**n_qubits.
|
|
19
|
+
|
|
20
|
+
Verified against known values: every computational-basis/stabilizer state
|
|
21
|
+
gives exactly 0; a single T state ((cos(pi/8), sin(pi/8)) in the
|
|
22
|
+
computational basis) gives -log2(0.75) = 0.415037 bits, matching the
|
|
23
|
+
closed-form derivation of Eq. 5 by hand (not a value copied from
|
|
24
|
+
elsewhere).
|
|
25
|
+
|
|
26
|
+
Promoted from Dense-Evolution-Discovery's wormhole_magic_entropy.py
|
|
27
|
+
(2026-08-29), where it was implemented fresh because the existing
|
|
28
|
+
magic_entropy is single-qubit only -- this quantity has no dependency on
|
|
29
|
+
that use case (wormhole teleportation), so it belongs here as a general
|
|
30
|
+
multi-qubit magic diagnostic.
|
|
31
|
+
|
|
32
|
+
VECTORIZATION NOTE: the Discovery script's original version had an
|
|
33
|
+
explicit Python `for a in range(d)` loop, each iteration doing its own
|
|
34
|
+
(d,d)@(d,) matrix-vector product -- d sequential small matmuls. This
|
|
35
|
+
promoted version instead builds the (d,d) matrix of every c_a(x) pair at
|
|
36
|
+
once (broadcasting over x and a together) and applies the Walsh-Hadamard
|
|
37
|
+
transform as ONE (d,d)@(d,d) matmul -- same O(d^3) total FLOP count, but
|
|
38
|
+
expressed as a single large matmul JAX/XLA can execute efficiently
|
|
39
|
+
(GPU/TPU-friendly, no per-iteration Python dispatch overhead), matching
|
|
40
|
+
this package's JAX-by-default convention. Still O(d^3), not the paper's
|
|
41
|
+
own asymptotically-better O(4^n * n) Walsh-Hadamard butterfly algorithm
|
|
42
|
+
(n = log2(d) qubits) -- unimplemented here, same as the Discovery
|
|
43
|
+
original; fine for d up to a few thousand (n up to ~11-12 qubits), the
|
|
44
|
+
sizes this package's exact-statevector backends already target.
|
|
45
|
+
"""
|
|
46
|
+
import jax
|
|
47
|
+
import jax.numpy as jnp
|
|
48
|
+
|
|
49
|
+
__all__ = ["stabilizer_renyi_entropy", "stabilizer_renyi_entropy_jit"]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _stabilizer_renyi_entropy_core(psi):
|
|
53
|
+
d = psi.shape[0]
|
|
54
|
+
idx = jnp.arange(d)
|
|
55
|
+
# Static (trace-time) construction, same style as magic_entropy.py's
|
|
56
|
+
# _cnot_matrix -- one Python loop per distinct d, cached by jax.jit.
|
|
57
|
+
popcount = jnp.array([bin(i).count("1") for i in range(d)])
|
|
58
|
+
|
|
59
|
+
bitwise_and = idx[:, None] & idx[None, :] # [b, x] = b & x
|
|
60
|
+
signmat = (-1.0) ** popcount[bitwise_and] # Walsh-Hadamard sign matrix
|
|
61
|
+
|
|
62
|
+
xor_table = idx[:, None] ^ idx[None, :] # [x, a] = x ^ a
|
|
63
|
+
c = jnp.conj(psi)[:, None] * psi[xor_table] # [x, a] = c_a(x)
|
|
64
|
+
|
|
65
|
+
wht = signmat @ c # [b, a] = WHT[c_a](b)
|
|
66
|
+
total = jnp.sum(jnp.abs(wht) ** 4)
|
|
67
|
+
return -jnp.log2(total / d)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def stabilizer_renyi_entropy(psi):
|
|
71
|
+
"""Stabilizer Renyi Entropy of a pure state `psi` (length 2**n_qubits),
|
|
72
|
+
in bits (log2) -- the paper's own convention.
|
|
73
|
+
|
|
74
|
+
Zero for every stabilizer state, positive for non-stabilizer ("magic")
|
|
75
|
+
states -- e.g. a single T state gives 0.415037 bits.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
psi : array-like, shape (2**n_qubits,)
|
|
80
|
+
A normalized pure statevector.
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
float
|
|
85
|
+
|
|
86
|
+
Examples
|
|
87
|
+
--------
|
|
88
|
+
>>> import numpy as np
|
|
89
|
+
>>> psi0 = np.zeros(8, dtype=complex); psi0[0] = 1.0
|
|
90
|
+
>>> round(stabilizer_renyi_entropy(psi0), 6) # computational basis state: stabilizer, expect 0
|
|
91
|
+
0.0
|
|
92
|
+
"""
|
|
93
|
+
psi = jnp.asarray(psi, dtype=jnp.complex128)
|
|
94
|
+
d = psi.shape[0]
|
|
95
|
+
if d < 1 or (d & (d - 1)) != 0:
|
|
96
|
+
raise ValueError(f"psi length {d} is not a power of 2 (must be 2**n_qubits)")
|
|
97
|
+
return float(_stabilizer_renyi_entropy_core(psi))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
stabilizer_renyi_entropy_jit = jax.jit(_stabilizer_renyi_entropy_core)
|
|
101
|
+
"""`jax.jit`-compiled entry point for `stabilizer_renyi_entropy`. `psi`
|
|
102
|
+
must already be `complex128`. Returns a jnp scalar, not a Python `float`
|
|
103
|
+
-- call `float(...)` yourself if you need one outside a jitted context."""
|