dense-evolution 8.1.76__tar.gz → 8.1.78__tar.gz
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.
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/PKG-INFO +1 -1
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/__init__.py +1 -1
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/mps.py +470 -30
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/native_hf/bridge.py +18 -2
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution.egg-info/PKG-INFO +1 -1
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/pyproject.toml +1 -1
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/README.md +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/autodiff.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/chunk/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/chunk/_engine_imports.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/chunk/circuit_chunker.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/chunk/core.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/chunk/disk_overflow.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/chunk/geometry.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/chunk/guard.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/chunk/kernels.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/statevector.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/chunk.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/circuits/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/circuits/compiler.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/circuits/diagram.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/circuits/gates.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/circuits/parser.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/circuits/qft.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/circuits/random_circuit.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/circuits/registry.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/circuits/topology.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/circuits/trotter.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/circuits/uccsd.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/cli.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/compiler.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/config.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/drawing.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/entropy.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/fermions.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/gates.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/harrison_tb.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/healing.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/interop/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/interop/qiskit_pennylane.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/measurement.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/mitigation/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/mitigation/healing.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/mitigation/kl_divergence.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/mitigation/magic_entropy.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/mitigation/magic_entropy_shadows.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/mitigation/renyi.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/mitigation/stabilizer_renyi_entropy.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/mitigation/zne.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/mps.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/native_hf/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/native_hf/assembly.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/native_hf/basis.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/native_hf/boys.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/native_hf/cartesian.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/native_hf/coulomb.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/native_hf/gaussians.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/native_hf/kinetic.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/native_hf/libcint_bridge.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/native_hf/overlap.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/native_hf/scf.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/coherent_attack.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/cosmic_ray.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/density_matrix_channels.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/differentiable.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/kraus/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/kraus/amplitude_damping.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/kraus/bitflip.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/kraus/combined.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/kraus/depolarizing.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/kraus/ideal.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/kraus/phaseflip.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/kraus_channels.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/oscillating.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/observables.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/parser.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/physics/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/physics/entropy.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/physics/fermions.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/physics/observables.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/physics/qec.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/physics/states.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/qec.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/qft.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/random_circuit.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/registry.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/simulator.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/solvers/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/solvers/autodiff.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/solvers/harrison_tb.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/solvers/vhd_tb.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/states.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/topology.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/trotter.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/utils/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/utils/drawing.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/utils/measurement.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/vhd_tb.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution.egg-info/SOURCES.txt +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution.egg-info/dependency_links.txt +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution.egg-info/entry_points.txt +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution.egg-info/requires.txt +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution.egg-info/top_level.txt +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/license.md +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/research/local_site/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/research/local_site/app/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/research/local_site/app/server.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/setup.cfg +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/circuit_builder_component.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/circuit_diagram.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/engine.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/graphical_builder.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/hamiltonians.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/mitigation.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/noise_tools.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/qasm_library.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/qmmm.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/state_visuals.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/system_limits.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/vector_healing.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/visuals.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/vqe.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/wormhole.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/ia_utils/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/ia_utils/adversarial_vector_attack.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/ia_utils/vector_healing.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/client.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/config.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/models.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/molecules.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/server.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/tools/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/tools/chemistry_tools.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/tools/circuit_tools.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/tools/mitigation_tools.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/tools/noise_tools.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/tools/system_tools.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/tools/wormhole_tools.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/utils/__init__.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/utils/cache.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/utils/images.py +0 -0
- {dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/utils/truncation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dense-evolution
|
|
3
|
-
Version: 8.1.
|
|
3
|
+
Version: 8.1.78
|
|
4
4
|
Summary: High-performance quantum simulation toolkit -- Statevector/MPS engines with JIT compilation, noise modeling, VQE, QEC, quantum chemistry, and agent-native tooling
|
|
5
5
|
Author-email: Salvatore Pennacchio <jtatopenn@libero.it>
|
|
6
6
|
License: Business Source License 1.1
|
|
@@ -57,7 +57,7 @@ from .physics.qec import (pauli_commutes, compute_syndrome, erasure_aware_decode
|
|
|
57
57
|
blind_minimum_weight_decode, decode_with_erasure_fallback,
|
|
58
58
|
counts_in_intervals_dimension, nearest_coset_decode)
|
|
59
59
|
|
|
60
|
-
__version__ = "8.1.
|
|
60
|
+
__version__ = "8.1.78"
|
|
61
61
|
|
|
62
62
|
__all__ = [
|
|
63
63
|
"__version__",
|
|
@@ -50,7 +50,9 @@ DenseSVSimulator has -- it is not a universal replacement, it is
|
|
|
50
50
|
complementary.
|
|
51
51
|
"""
|
|
52
52
|
|
|
53
|
+
import dataclasses
|
|
53
54
|
import warnings
|
|
55
|
+
from functools import partial
|
|
54
56
|
from typing import List, Optional, Tuple
|
|
55
57
|
|
|
56
58
|
import jax
|
|
@@ -120,6 +122,18 @@ def _vectorized_chi_search_jax(S: jnp.ndarray, eps: float, jsd_budget: float, ma
|
|
|
120
122
|
|
|
121
123
|
norm_full = jnp.sum(S ** 2) + 1e-15
|
|
122
124
|
p_full = (S ** 2) / norm_full
|
|
125
|
+
# Real bug (found via a real-machine bisection down to 5e34a1e, not
|
|
126
|
+
# guessed): under jax.jit, _jsd_vectors_jax's own internal eps=1e-12
|
|
127
|
+
# mask does not reliably zero JSD contributions from p_full's
|
|
128
|
+
# already-negligible tail (~1e-18, from squaring SVD noise-floor
|
|
129
|
+
# singular values ~1e-9) when batched via jax.vmap across multiple
|
|
130
|
+
# candidates at once -- eager execution of the exact same formula on
|
|
131
|
+
# the exact same array gives the correct (zero) JSD there, jit does
|
|
132
|
+
# not. Zeroing p_full's own negligible tail here, before it ever
|
|
133
|
+
# reaches the vmapped JSD computation, verified to fix this under
|
|
134
|
+
# jit (confirmed both eager and jit now agree) without changing any
|
|
135
|
+
# value large enough to matter physically.
|
|
136
|
+
p_full = jnp.where(p_full > 1e-12, p_full, 0.0)
|
|
123
137
|
|
|
124
138
|
candidates = jnp.arange(1, max_possible + 1)
|
|
125
139
|
idx = jnp.arange(n)
|
|
@@ -298,6 +312,27 @@ def _pad_lambda(lam: jnp.ndarray, max_bond: int) -> jnp.ndarray:
|
|
|
298
312
|
return out.at[:n].set(lam)
|
|
299
313
|
|
|
300
314
|
|
|
315
|
+
@partial(jax.jit, static_argnums=(1, 2))
|
|
316
|
+
def _pad_all_gammas(gammas, max_bond: int, dtype) -> jnp.ndarray:
|
|
317
|
+
"""Batches the whole per-gamma padding+stacking loop into one
|
|
318
|
+
compiled call instead of n_qubits separate eager _pad_gamma
|
|
319
|
+
dispatches -- profiling run_circuit_jit directly found this eager
|
|
320
|
+
loop (50-51 separate JAX dispatches for N=50) cost 52-55% of total
|
|
321
|
+
run_circuit_jit time, present equally on the fuse_gates=True and
|
|
322
|
+
default paths, diluting the real speedup ratio between them.
|
|
323
|
+
Persistent-compiled-cache wrapper, keyed by the tuple of gamma
|
|
324
|
+
shapes + max_bond + dtype -- valid across the whole process
|
|
325
|
+
lifetime, same principle as _jit_mps_1q_matrix/_jit_mps_2q_matrix."""
|
|
326
|
+
return jnp.stack([_pad_gamma(g, max_bond).astype(dtype) for g in gammas])
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@partial(jax.jit, static_argnums=(1, 2))
|
|
330
|
+
def _pad_all_lambdas(lambdas, max_bond: int, dtype) -> jnp.ndarray:
|
|
331
|
+
"""Same persistent-compiled-cache fix as _pad_all_gammas, for
|
|
332
|
+
lambdas."""
|
|
333
|
+
return jnp.stack([_pad_lambda(l, max_bond).astype(dtype) for l in lambdas])
|
|
334
|
+
|
|
335
|
+
|
|
301
336
|
def _compile_mps_ops(ops, n_qubits: int) -> List[List[float]]:
|
|
302
337
|
"""Pre-compile-time (pure Python, outside any jit region) translation
|
|
303
338
|
of a circuit -- list of (name, *args) tuples/lists, same convention as
|
|
@@ -370,6 +405,161 @@ def _compile_mps_ops(ops, n_qubits: int) -> List[List[float]]:
|
|
|
370
405
|
return rows
|
|
371
406
|
|
|
372
407
|
|
|
408
|
+
def _embed_1q_matrix(mat1: np.ndarray, qubit: int, pair: Tuple[int, int]) -> np.ndarray:
|
|
409
|
+
"""Embeds a 2x2 single-qubit matrix into the 4x4 space of a 2-qubit
|
|
410
|
+
pair (a, b), acting as identity on the other qubit -- kron(mat1, I2)
|
|
411
|
+
if qubit is the pair's first (more significant) qubit, kron(I2, mat1)
|
|
412
|
+
otherwise, matching _mps_2q_matrix's own convention (its (4,4)
|
|
413
|
+
matrices are built with qubit0 as the more significant index before
|
|
414
|
+
reshaping to (2,2,2,2))."""
|
|
415
|
+
eye2 = np.eye(2, dtype=mat1.dtype)
|
|
416
|
+
a, _ = pair
|
|
417
|
+
return np.kron(mat1, eye2) if qubit == a else np.kron(eye2, mat1)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@partial(jax.jit, static_argnums=(2,))
|
|
421
|
+
def _jit_mps_1q_matrix(g_id, param, dtype):
|
|
422
|
+
"""Persistent-compiled-cache wrapper around _mps_1q_matrix, keyed by
|
|
423
|
+
JAX on (g_id, param, dtype) across the whole process lifetime, not
|
|
424
|
+
just within one _fuse_compiled_rows call -- without this, each
|
|
425
|
+
separate run_circuit_jit(fuse_gates=True) call pays a real XLA
|
|
426
|
+
compile cost again for the same gate/parameter combination, since
|
|
427
|
+
_mps_1q_matrix's own jax.lax.switch is never wrapped in @jax.jit."""
|
|
428
|
+
return _mps_1q_matrix(g_id, param, dtype)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@partial(jax.jit, static_argnums=(2,))
|
|
432
|
+
def _jit_mps_2q_matrix(g_id, param, dtype):
|
|
433
|
+
"""Same persistent-compiled-cache fix as _jit_mps_1q_matrix, for
|
|
434
|
+
_mps_2q_matrix."""
|
|
435
|
+
return _mps_2q_matrix(g_id, param, dtype)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _fuse_compiled_rows(rows: List[List[float]], dtype) -> List[tuple]:
|
|
439
|
+
"""Host-side (pure Python/NumPy, outside any jit region), exact --
|
|
440
|
+
fuses a chain of consecutive _compile_mps_ops rows into one matrix
|
|
441
|
+
via matrix multiplication, growing the active qubit pair as needed:
|
|
442
|
+
a 1-qubit gate on a qubit already inside the current chain's pair
|
|
443
|
+
gets embedded into that same 4x4 space (kron with identity on the
|
|
444
|
+
other qubit) rather than breaking the chain -- this is what actually
|
|
445
|
+
fuses a CX-RZ-CX triple (RZ acts on only one of CX's two qubits, so a
|
|
446
|
+
naive "exact qubit set match" rule never fuses anything). A chain
|
|
447
|
+
starting on a single qubit that is later joined by a 2-qubit gate
|
|
448
|
+
touching it also grows into the 2-qubit space the same way. Any gate
|
|
449
|
+
touching a qubit OUTSIDE the current chain's pair ends the chain.
|
|
450
|
+
|
|
451
|
+
Runs on _compile_mps_ops's OWN output rows, so CCX decomposition and
|
|
452
|
+
SWAP-chain expansion for non-adjacent gates have already happened --
|
|
453
|
+
fusion composes correctly with both by construction (a SWAP is just
|
|
454
|
+
another 2-qubit gate, fusable like any other), verified directly
|
|
455
|
+
against the eager reference on both a non-adjacent-gate circuit and
|
|
456
|
+
a CCX circuit, not merely assumed.
|
|
457
|
+
|
|
458
|
+
Returns a list of ('1q', q, matrix_2x2) / ('2q', q1, q2, matrix_4x4)
|
|
459
|
+
entries. transpose_flag is resolved into the matrix itself here, so
|
|
460
|
+
the fused representation never needs it downstream.
|
|
461
|
+
|
|
462
|
+
Memoizes row_matrix by (g_id, param, transpose_flag) -- most rows in
|
|
463
|
+
a real circuit share the same small set of distinct gate/parameter
|
|
464
|
+
combinations (e.g. one N=50 TFIM circuit has 985 rows but only 3
|
|
465
|
+
distinct ones), and each cache miss dispatches a real JAX call
|
|
466
|
+
(_mps_1q_matrix/_mps_2q_matrix) that is not free to skip. Without
|
|
467
|
+
this, a real (not tiny-example) circuit pays a full eager JAX
|
|
468
|
+
dispatch per row on every single run_circuit_jit(fuse_gates=True)
|
|
469
|
+
call, not just the first -- measured directly: an unmemoized version
|
|
470
|
+
of this function made "warm" (post-compile) calls nearly as slow as
|
|
471
|
+
"cold" ones (10.7s vs 12.9s on a 74-gate circuit), because the
|
|
472
|
+
matrix-reconstruction cost, not recompilation, dominated."""
|
|
473
|
+
matrix_cache: dict = {}
|
|
474
|
+
|
|
475
|
+
def row_matrix(row):
|
|
476
|
+
g_id, q1, q2, param, transpose_flag = row
|
|
477
|
+
key = (g_id, param, transpose_flag)
|
|
478
|
+
cached = matrix_cache.get(key)
|
|
479
|
+
if cached is not None:
|
|
480
|
+
return cached, (int(q1), int(q2)) if g_id >= 20 else (int(q1),)
|
|
481
|
+
g_id_arr = jnp.asarray(g_id).astype(jnp.int32)
|
|
482
|
+
if g_id >= 20:
|
|
483
|
+
mat4 = np.asarray(_jit_mps_2q_matrix(g_id_arr, jnp.asarray(param), dtype))
|
|
484
|
+
if transpose_flag > 0.5:
|
|
485
|
+
mat4 = np.transpose(mat4, (1, 0, 3, 2))
|
|
486
|
+
mat = mat4.reshape(4, 4)
|
|
487
|
+
else:
|
|
488
|
+
mat = np.asarray(_jit_mps_1q_matrix(g_id_arr, jnp.asarray(param), dtype))
|
|
489
|
+
matrix_cache[key] = mat
|
|
490
|
+
return mat, (int(q1), int(q2)) if g_id >= 20 else (int(q1),)
|
|
491
|
+
|
|
492
|
+
fused = []
|
|
493
|
+
i = 0
|
|
494
|
+
n = len(rows)
|
|
495
|
+
while i < n:
|
|
496
|
+
mat, qubits = row_matrix(rows[i])
|
|
497
|
+
pair = qubits if len(qubits) == 2 else None
|
|
498
|
+
active_qubit = qubits[0]
|
|
499
|
+
j = i + 1
|
|
500
|
+
while j < n:
|
|
501
|
+
nmat, nqubits = row_matrix(rows[j])
|
|
502
|
+
n_is_2q = len(nqubits) == 2
|
|
503
|
+
if pair is None:
|
|
504
|
+
if not n_is_2q:
|
|
505
|
+
if nqubits[0] != active_qubit:
|
|
506
|
+
break
|
|
507
|
+
mat = nmat @ mat
|
|
508
|
+
j += 1
|
|
509
|
+
continue
|
|
510
|
+
if active_qubit not in nqubits:
|
|
511
|
+
break
|
|
512
|
+
pair = nqubits
|
|
513
|
+
mat = _embed_1q_matrix(mat, active_qubit, pair)
|
|
514
|
+
mat = nmat @ mat
|
|
515
|
+
j += 1
|
|
516
|
+
continue
|
|
517
|
+
if n_is_2q:
|
|
518
|
+
if nqubits != pair:
|
|
519
|
+
break
|
|
520
|
+
else:
|
|
521
|
+
if nqubits[0] not in pair:
|
|
522
|
+
break
|
|
523
|
+
nmat = _embed_1q_matrix(nmat, nqubits[0], pair)
|
|
524
|
+
mat = nmat @ mat
|
|
525
|
+
j += 1
|
|
526
|
+
if pair is not None:
|
|
527
|
+
fused.append(('2q', pair[0], pair[1], mat.reshape(2, 2, 2, 2)))
|
|
528
|
+
else:
|
|
529
|
+
fused.append(('1q', active_qubit, mat))
|
|
530
|
+
i = j
|
|
531
|
+
return fused
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def _fused_entries_to_arrays(fused: List[tuple], dtype):
|
|
535
|
+
"""Turns _fuse_compiled_rows's output into the parallel arrays
|
|
536
|
+
jax.lax.scan needs -- every step carries BOTH a 1q and a 2q matrix
|
|
537
|
+
slot (one is an unused identity placeholder) so shapes stay uniform
|
|
538
|
+
across the whole scanned sequence, same principle _mps_1q_matrix/
|
|
539
|
+
_mps_2q_matrix's own switch already relies on (all branches traced,
|
|
540
|
+
only one path's real work executed)."""
|
|
541
|
+
is_2q, q1s, q2s, mats2q, mats1q = [], [], [], [], []
|
|
542
|
+
eye2 = np.eye(2, dtype=dtype)
|
|
543
|
+
eye4 = np.eye(4, dtype=dtype).reshape(2, 2, 2, 2)
|
|
544
|
+
for entry in fused:
|
|
545
|
+
if entry[0] == '2q':
|
|
546
|
+
_, a, b, mat = entry
|
|
547
|
+
is_2q.append(True)
|
|
548
|
+
q1s.append(a)
|
|
549
|
+
q2s.append(b)
|
|
550
|
+
mats2q.append(mat)
|
|
551
|
+
mats1q.append(eye2)
|
|
552
|
+
else:
|
|
553
|
+
_, a, mat = entry
|
|
554
|
+
is_2q.append(False)
|
|
555
|
+
q1s.append(a)
|
|
556
|
+
q2s.append(0)
|
|
557
|
+
mats2q.append(eye4)
|
|
558
|
+
mats1q.append(mat)
|
|
559
|
+
return (jnp.asarray(is_2q), jnp.asarray(q1s, dtype=jnp.int32), jnp.asarray(q2s, dtype=jnp.int32),
|
|
560
|
+
jnp.asarray(np.stack(mats2q)), jnp.asarray(np.stack(mats1q)))
|
|
561
|
+
|
|
562
|
+
|
|
373
563
|
_SVD_BUCKETS = (2, 4, 8, 16, 32, 64, 128, 256, 512, 1024)
|
|
374
564
|
|
|
375
565
|
|
|
@@ -542,6 +732,125 @@ def _build_mps_runner(n_qubits: int, max_bond: int, eps: float, jsd_budget: floa
|
|
|
542
732
|
return run
|
|
543
733
|
|
|
544
734
|
|
|
735
|
+
def _build_fused_mps_runner(n_qubits: int, max_bond: int, eps: float, jsd_budget: float):
|
|
736
|
+
"""Factory for run_circuit_jit(ops, fuse_gates=True) -- same bucketed-
|
|
737
|
+
SVD dispatch as _build_mps_runner, except every step's gate matrix is
|
|
738
|
+
taken directly from a pre-fused matrix stream (built host-side by
|
|
739
|
+
_fuse_compiled_rows/_fused_entries_to_arrays from _compile_mps_ops's
|
|
740
|
+
own output) instead of being reconstructed inside the JIT via
|
|
741
|
+
_mps_1q_matrix/_mps_2q_matrix -- gate-ID dispatch is removed from the
|
|
742
|
+
traced program entirely, not just extended. Fusing consecutive
|
|
743
|
+
same-qubit-pair gates into one matrix means fewer, larger scan steps:
|
|
744
|
+
a real, measured ~2x GPU speedup on top of the bucketed dispatch
|
|
745
|
+
alone (see Dense-Evolution-Discovery's mps_gate_blocking_redesign_v2
|
|
746
|
+
experiment), on top of jax.lax.scan's own per-step GPU dispatch
|
|
747
|
+
overhead being the real bottleneck the bucketed dispatch alone
|
|
748
|
+
couldn't remove.
|
|
749
|
+
|
|
750
|
+
Returns the same (chi_new, jsd_val, trunc_err, entanglement_entropy)
|
|
751
|
+
diagnostic tuple _build_mps_runner does, one entry per FUSED step
|
|
752
|
+
rather than per original gate -- run_circuit_jit populates
|
|
753
|
+
self._bond_history/jsd_per_bond/truncation_errors/entanglement_entropy
|
|
754
|
+
from this at that coarser granularity when fuse_gates=True, an
|
|
755
|
+
explicit, documented trade-off (see MPSSimulator.run_circuit_jit's
|
|
756
|
+
own docstring), not a silent behavior change."""
|
|
757
|
+
buckets = _bucket_sizes(max_bond)
|
|
758
|
+
bucket_arr = jnp.array(buckets)
|
|
759
|
+
|
|
760
|
+
def step(carry, xs):
|
|
761
|
+
gammas, lambdas, real_chi = carry
|
|
762
|
+
dtype = gammas.dtype
|
|
763
|
+
is_2q, q1, q2, mat2q, mat1q = xs
|
|
764
|
+
q1 = q1.astype(jnp.int32)
|
|
765
|
+
q2 = q2.astype(jnp.int32)
|
|
766
|
+
real_dtype = _real_dtype_for(dtype)
|
|
767
|
+
|
|
768
|
+
def branch_1q(c):
|
|
769
|
+
gammas_, lambdas_, real_chi_ = c
|
|
770
|
+
new_g = jnp.einsum('ij,ljr->lir', mat1q, gammas_[q1])
|
|
771
|
+
new_carry = (gammas_.at[q1].set(new_g), lambdas_, real_chi_)
|
|
772
|
+
diag = (jnp.asarray(0, dtype=jnp.int32), jnp.asarray(0.0, dtype=real_dtype),
|
|
773
|
+
jnp.asarray(0.0, dtype=real_dtype), jnp.asarray(0.0, dtype=real_dtype))
|
|
774
|
+
return new_carry, diag
|
|
775
|
+
|
|
776
|
+
def branch_2q(c):
|
|
777
|
+
gammas_, lambdas_, real_chi_ = c
|
|
778
|
+
gate_2q = mat2q
|
|
779
|
+
|
|
780
|
+
chi_l_real = real_chi_[q1]
|
|
781
|
+
chi_m_real = real_chi_[q2]
|
|
782
|
+
chi_r_real = real_chi_[q2 + 1]
|
|
783
|
+
input_min = jnp.maximum(jnp.maximum(chi_l_real, chi_r_real), chi_m_real)
|
|
784
|
+
output_bound = jnp.minimum(chi_l_real * 2, chi_r_real * 2)
|
|
785
|
+
bound = jnp.maximum(input_min, output_bound)
|
|
786
|
+
ge_mask = bucket_arr >= bound
|
|
787
|
+
bucket_idx = jnp.where(jnp.any(ge_mask), jnp.argmax(ge_mask), len(buckets) - 1)
|
|
788
|
+
|
|
789
|
+
def make_branch(B):
|
|
790
|
+
def branch_fn(_):
|
|
791
|
+
g1 = gammas_[q1][:B, :, :B]
|
|
792
|
+
g2 = gammas_[q2][:B, :, :B]
|
|
793
|
+
lam_l = lambdas_[q1][:B]
|
|
794
|
+
lam_m = lambdas_[q2][:B]
|
|
795
|
+
lam_r = lambdas_[q2 + 1][:B]
|
|
796
|
+
theta = jnp.einsum('l,lik,k,kjr,r->lijr', lam_l, g1, lam_m, g2, lam_r)
|
|
797
|
+
theta_new = jnp.einsum('abcd,ecdf->eabf', gate_2q, theta)
|
|
798
|
+
theta_mat = theta_new.reshape(B * 2, 2 * B)
|
|
799
|
+
|
|
800
|
+
U, S, Vh = jnp.linalg.svd(theta_mat, full_matrices=False)
|
|
801
|
+
chi_new, jsd_val = _vectorized_chi_search_jax(S, eps, jsd_budget, min(B, max_bond))
|
|
802
|
+
col_mask = jnp.arange(B) < chi_new
|
|
803
|
+
|
|
804
|
+
norm_full = jnp.sqrt(jnp.sum(S ** 2) + 1e-30)
|
|
805
|
+
S_norm_full = S / (norm_full + 1e-30)
|
|
806
|
+
trunc_err = jnp.sqrt(jnp.sum(jnp.where(jnp.arange(2 * B) >= chi_new, S_norm_full ** 2, 0.0)))
|
|
807
|
+
|
|
808
|
+
S_kept_masked = jnp.where(col_mask, S[:B], 0.0)
|
|
809
|
+
kept_norm = jnp.sqrt(jnp.sum(S_kept_masked ** 2) + 1e-30)
|
|
810
|
+
S_fixed = jnp.where(col_mask, S_kept_masked / (kept_norm + 1e-30), 0.0)
|
|
811
|
+
|
|
812
|
+
lam_l_inv = jnp.where(lam_l > eps, 1.0 / lam_l, 0.0)
|
|
813
|
+
lam_r_inv = jnp.where(lam_r > eps, 1.0 / lam_r, 0.0)
|
|
814
|
+
|
|
815
|
+
U_masked = jnp.where(col_mask[None, :], U[:, :B], 0.0)
|
|
816
|
+
Vh_masked = jnp.where(col_mask[:, None], Vh[:B, :], 0.0)
|
|
817
|
+
|
|
818
|
+
new_g1 = jnp.einsum('l,lir->lir', lam_l_inv, U_masked.reshape(B, 2, B))
|
|
819
|
+
new_g2 = jnp.einsum('ljr,r->ljr', Vh_masked.reshape(B, 2, B), lam_r_inv)
|
|
820
|
+
|
|
821
|
+
p_dist = S_fixed ** 2
|
|
822
|
+
ee = -jnp.sum(jnp.where(p_dist > 1e-20, p_dist * jnp.log2(jnp.where(p_dist > 1e-20, p_dist, 1.0)), 0.0))
|
|
823
|
+
|
|
824
|
+
return (_pad_gamma(new_g1, max_bond), _pad_gamma(new_g2, max_bond),
|
|
825
|
+
_pad_lambda(S_fixed, max_bond), chi_new.astype(jnp.int32),
|
|
826
|
+
jsd_val.astype(real_dtype), trunc_err.astype(real_dtype), ee.astype(real_dtype))
|
|
827
|
+
|
|
828
|
+
return branch_fn
|
|
829
|
+
|
|
830
|
+
branches = [make_branch(B) for B in buckets]
|
|
831
|
+
new_g1_p, new_g2_p, S_fixed_p, chi_new, jsd_val, trunc_err, ee = jax.lax.switch(
|
|
832
|
+
bucket_idx, branches, operand=None)
|
|
833
|
+
|
|
834
|
+
new_gammas = gammas_.at[q1].set(new_g1_p).at[q2].set(new_g2_p)
|
|
835
|
+
new_lambdas = lambdas_.at[q2].set(S_fixed_p)
|
|
836
|
+
new_real_chi = real_chi_.at[q2].set(chi_new)
|
|
837
|
+
|
|
838
|
+
new_carry = (new_gammas, new_lambdas, new_real_chi)
|
|
839
|
+
diag = (chi_new, jsd_val, trunc_err, ee)
|
|
840
|
+
return new_carry, diag
|
|
841
|
+
|
|
842
|
+
new_carry, diag = jax.lax.cond(is_2q, branch_2q, branch_1q, carry)
|
|
843
|
+
return new_carry, diag
|
|
844
|
+
|
|
845
|
+
@jax.jit
|
|
846
|
+
def run(gammas, lambdas, real_chi, xs):
|
|
847
|
+
(final_gammas, final_lambdas, final_real_chi), diag = jax.lax.scan(
|
|
848
|
+
step, (gammas, lambdas, real_chi), xs)
|
|
849
|
+
return final_gammas, final_lambdas, final_real_chi, diag
|
|
850
|
+
|
|
851
|
+
return run
|
|
852
|
+
|
|
853
|
+
|
|
545
854
|
class MPSSimulator:
|
|
546
855
|
"""
|
|
547
856
|
Matrix Product State simulator with adaptive SVD-truncated bond
|
|
@@ -634,6 +943,7 @@ class MPSSimulator:
|
|
|
634
943
|
# for this instance's lifetime), never rebuilt per call. Same
|
|
635
944
|
# caching pattern as Chunk.__init__'s self._multi_chunk_runner.
|
|
636
945
|
self._mps_runner = None
|
|
946
|
+
self._fused_mps_runner = None
|
|
637
947
|
|
|
638
948
|
for _ in range(n_qubits):
|
|
639
949
|
g = jnp.zeros((1, 2, 1), dtype=dtype)
|
|
@@ -947,7 +1257,37 @@ class MPSSimulator:
|
|
|
947
1257
|
)
|
|
948
1258
|
|
|
949
1259
|
# ── JIT-fused whole-circuit execution ─────────────────────────────
|
|
950
|
-
def
|
|
1260
|
+
def _record_diag_bookkeeping(self, diag, q1_ids: np.ndarray, is_2q_mask: np.ndarray) -> None:
|
|
1261
|
+
"""Shared by both run_circuit_jit paths: jax.lax.scan's stacked
|
|
1262
|
+
per-step diagnostics (diag) replace the eager path's Python
|
|
1263
|
+
list.append()s inside the loop -- same final content, populated
|
|
1264
|
+
differently. Only 2-qubit steps count (is_2q_mask), same as
|
|
1265
|
+
_bond_history/jsd_per_bond only ever growing on 2-qubit gates in
|
|
1266
|
+
the eager path."""
|
|
1267
|
+
chi_history, jsd_history, trunc_err_history, entropy_history = (
|
|
1268
|
+
np.asarray(diag[0]), np.asarray(diag[1]), np.asarray(diag[2]), np.asarray(diag[3]))
|
|
1269
|
+
for i in np.nonzero(is_2q_mask)[0]:
|
|
1270
|
+
chi_new = int(chi_history[i])
|
|
1271
|
+
jsd_val = float(jsd_history[i])
|
|
1272
|
+
self._bond_history.append(chi_new)
|
|
1273
|
+
self.jsd_per_bond.append(jsd_val)
|
|
1274
|
+
self.truncation_errors.append(float(trunc_err_history[i]))
|
|
1275
|
+
q1 = q1_ids[i]
|
|
1276
|
+
if q1 < len(self.entanglement_entropy):
|
|
1277
|
+
self.entanglement_entropy[q1] = float(entropy_history[i])
|
|
1278
|
+
if jsd_val > self.jsd_budget:
|
|
1279
|
+
if self.budget_violations == 0:
|
|
1280
|
+
warnings.warn(
|
|
1281
|
+
f"MPSSimulator: bond dimension capped at max_bond={self.chi}, "
|
|
1282
|
+
f"jsd_budget={self.jsd_budget:.1e} not honored "
|
|
1283
|
+
f"(jsd={jsd_val:.2e}) -- results may be unreliable, "
|
|
1284
|
+
f"consider raising max_bond.",
|
|
1285
|
+
UserWarning,
|
|
1286
|
+
stacklevel=2,
|
|
1287
|
+
)
|
|
1288
|
+
self.budget_violations += 1
|
|
1289
|
+
|
|
1290
|
+
def run_circuit_jit(self, ops: List, fuse_gates: bool = False) -> None:
|
|
951
1291
|
"""Runs an entire circuit through a single jax.lax.scan-fused,
|
|
952
1292
|
@jax.jit-compiled kernel instead of one eager Python call per gate
|
|
953
1293
|
-- the eager path (apply_gate_1q/apply_gate_2q/_apply_nonlocal_2q,
|
|
@@ -972,10 +1312,50 @@ class MPSSimulator:
|
|
|
972
1312
|
-- list of (name, *args) tuples/lists. Unlike that method, SWAP is
|
|
973
1313
|
never decomposed into 3xCX (kept as one real gate, see
|
|
974
1314
|
_compile_mps_ops's docstring for why that matters here).
|
|
1315
|
+
|
|
1316
|
+
fuse_gates: opt-in, default False. When True, consecutive gates
|
|
1317
|
+
acting on the same (or a growing) qubit pair are fused into one
|
|
1318
|
+
matrix on the host before compiling (exact -- matrix
|
|
1319
|
+
multiplication, no approximation), cutting the number of scan
|
|
1320
|
+
steps and measurably faster on GPU (~2x on top of the bucketed
|
|
1321
|
+
SVD dispatch alone, see Dense-Evolution-Discovery's
|
|
1322
|
+
mps_gate_blocking_redesign_v2 experiment for the full validation,
|
|
1323
|
+
including verification against non-adjacent-gate and CCX
|
|
1324
|
+
circuits). The trade-off: self._bond_history/jsd_per_bond/
|
|
1325
|
+
truncation_errors/entanglement_entropy get one entry per FUSED
|
|
1326
|
+
step instead of per original gate -- real diagnostics, just
|
|
1327
|
+
coarser-grained. Defaults to False so existing behavior and
|
|
1328
|
+
per-gate bookkeeping granularity are unchanged unless requested.
|
|
975
1329
|
"""
|
|
976
|
-
compiled_rows = _compile_mps_ops(ops, self.n)
|
|
977
1330
|
dtype = self.gammas[0].dtype
|
|
978
1331
|
lambda_dtype = self.lambdas[0].dtype
|
|
1332
|
+
|
|
1333
|
+
if fuse_gates:
|
|
1334
|
+
compiled_rows = _compile_mps_ops(ops, self.n)
|
|
1335
|
+
fused = _fuse_compiled_rows(compiled_rows, dtype) if compiled_rows else []
|
|
1336
|
+
|
|
1337
|
+
if self._fused_mps_runner is None:
|
|
1338
|
+
self._fused_mps_runner = _build_fused_mps_runner(self.n, self.chi, self.eps, self.jsd_budget)
|
|
1339
|
+
|
|
1340
|
+
gammas_padded = _pad_all_gammas(tuple(self.gammas), self.chi, dtype)
|
|
1341
|
+
lambdas_padded = _pad_all_lambdas(tuple(self.lambdas), self.chi, lambda_dtype)
|
|
1342
|
+
real_chi_initial = jnp.asarray(self._real_chi, dtype=jnp.int32)
|
|
1343
|
+
|
|
1344
|
+
if fused:
|
|
1345
|
+
xs = _fused_entries_to_arrays(fused, dtype)
|
|
1346
|
+
final_gammas, final_lambdas, final_real_chi, diag = self._fused_mps_runner(
|
|
1347
|
+
gammas_padded, lambdas_padded, real_chi_initial, xs)
|
|
1348
|
+
|
|
1349
|
+
self.gammas = [final_gammas[i] for i in range(self.n)]
|
|
1350
|
+
self.lambdas = [final_lambdas[i] for i in range(self.n + 1)]
|
|
1351
|
+
self._real_chi = np.asarray(final_real_chi)
|
|
1352
|
+
|
|
1353
|
+
q1_ids = np.asarray([entry[1] for entry in fused])
|
|
1354
|
+
is_2q_mask = np.asarray([entry[0] == '2q' for entry in fused])
|
|
1355
|
+
self._record_diag_bookkeeping(diag, q1_ids, is_2q_mask)
|
|
1356
|
+
return
|
|
1357
|
+
|
|
1358
|
+
compiled_rows = _compile_mps_ops(ops, self.n)
|
|
979
1359
|
ops_dtype = _real_dtype_for(dtype)
|
|
980
1360
|
|
|
981
1361
|
if compiled_rows:
|
|
@@ -997,38 +1377,11 @@ class MPSSimulator:
|
|
|
997
1377
|
self.lambdas = [final_lambdas[i] for i in range(self.n + 1)]
|
|
998
1378
|
self._real_chi = np.asarray(final_real_chi)
|
|
999
1379
|
|
|
1000
|
-
# Bookkeeping parity: jax.lax.scan's stacked per-step diagnostics
|
|
1001
|
-
# (diag) replace the eager path's Python list.append()s inside the
|
|
1002
|
-
# loop -- same final content, populated differently. Only 2-qubit
|
|
1003
|
-
# steps (g_id >= 20, includes SWAP -- the eager _apply_nonlocal_2q
|
|
1004
|
-
# path routes its SWAPs through apply_gate_2q too, so its history
|
|
1005
|
-
# lists grow on those as well, not just the "real" gate) count.
|
|
1006
1380
|
if compiled_rows:
|
|
1007
|
-
chi_history, jsd_history, trunc_err_history, entropy_history = (
|
|
1008
|
-
np.asarray(diag[0]), np.asarray(diag[1]), np.asarray(diag[2]), np.asarray(diag[3]))
|
|
1009
1381
|
g_ids = np.asarray([row[0] for row in compiled_rows])
|
|
1010
1382
|
q1_ids = np.asarray([int(row[1]) for row in compiled_rows])
|
|
1011
1383
|
is_2q_mask = g_ids >= 20
|
|
1012
|
-
|
|
1013
|
-
chi_new = int(chi_history[i])
|
|
1014
|
-
jsd_val = float(jsd_history[i])
|
|
1015
|
-
self._bond_history.append(chi_new)
|
|
1016
|
-
self.jsd_per_bond.append(jsd_val)
|
|
1017
|
-
self.truncation_errors.append(float(trunc_err_history[i]))
|
|
1018
|
-
q1 = q1_ids[i]
|
|
1019
|
-
if q1 < len(self.entanglement_entropy):
|
|
1020
|
-
self.entanglement_entropy[q1] = float(entropy_history[i])
|
|
1021
|
-
if jsd_val > self.jsd_budget:
|
|
1022
|
-
if self.budget_violations == 0:
|
|
1023
|
-
warnings.warn(
|
|
1024
|
-
f"MPSSimulator: bond dimension capped at max_bond={self.chi}, "
|
|
1025
|
-
f"jsd_budget={self.jsd_budget:.1e} not honored "
|
|
1026
|
-
f"(jsd={jsd_val:.2e}) -- results may be unreliable, "
|
|
1027
|
-
f"consider raising max_bond.",
|
|
1028
|
-
UserWarning,
|
|
1029
|
-
stacklevel=2,
|
|
1030
|
-
)
|
|
1031
|
-
self.budget_violations += 1
|
|
1384
|
+
self._record_diag_bookkeeping(diag, q1_ids, is_2q_mask)
|
|
1032
1385
|
|
|
1033
1386
|
|
|
1034
1387
|
_PAULI_MATRICES = {
|
|
@@ -1119,3 +1472,90 @@ def mps_pauli_sum_expectation(mps: "MPSSimulator", terms) -> complex:
|
|
|
1119
1472
|
for coeff, pauli_terms in terms
|
|
1120
1473
|
)
|
|
1121
1474
|
return raw_sum / norm_sq
|
|
1475
|
+
|
|
1476
|
+
|
|
1477
|
+
@dataclasses.dataclass
|
|
1478
|
+
class BondConvergenceResult:
|
|
1479
|
+
bonds: List[int]
|
|
1480
|
+
chi_used: List[int]
|
|
1481
|
+
avg_jsd: List[float]
|
|
1482
|
+
budget_violations: List[int]
|
|
1483
|
+
values: List[List[complex]]
|
|
1484
|
+
diffs: List[List[float]]
|
|
1485
|
+
verdicts: List[str]
|
|
1486
|
+
|
|
1487
|
+
|
|
1488
|
+
def bond_convergence(
|
|
1489
|
+
ops: List, n_qubits: int, observables: list, bonds: List[int],
|
|
1490
|
+
tol: float = 1e-3, **mps_kwargs,
|
|
1491
|
+
) -> BondConvergenceResult:
|
|
1492
|
+
"""Runs the same circuit at every value in `bonds` (increasing) and
|
|
1493
|
+
checks whether the reported observables have actually converged with
|
|
1494
|
+
respect to bond dimension, instead of trusting a single run's own
|
|
1495
|
+
internal diagnostics.
|
|
1496
|
+
|
|
1497
|
+
Requires len(bonds) >= 3. Two bonds give exactly one discrepancy,
|
|
1498
|
+
which is a single number with no way to tell whether it is still
|
|
1499
|
+
shrinking toward `tol` or has already stalled -- measured on a
|
|
1500
|
+
40-qubit, 4-layer brickwall circuit, chi=4->8->32 gave |<Z0>|
|
|
1501
|
+
discrepancies of ~4.7e-2 then ~1.2e-2 (chi_used never hit its own
|
|
1502
|
+
cap, so this is a real not_converged, not an artifact of running out
|
|
1503
|
+
of bond dimension): a two-bond check (chi=4 vs 8) would see only the
|
|
1504
|
+
first number and have no basis to call it anything, while three
|
|
1505
|
+
bonds show a trend that is decreasing but still two orders of
|
|
1506
|
+
magnitude above any reasonable `tol`.
|
|
1507
|
+
|
|
1508
|
+
A verdict of "converged" additionally requires the successive
|
|
1509
|
+
discrepancies to be monotonically non-increasing, not just that the
|
|
1510
|
+
last one is below `tol` -- a single small discrepancy proves nothing
|
|
1511
|
+
about the trend on its own, which is the same failure mode as the
|
|
1512
|
+
two-bond case above, one level up. (Ties count as non-increasing: an
|
|
1513
|
+
exactly-converged observable, e.g. a GHZ chain whose bond dimension
|
|
1514
|
+
never needs to grow, produces identical values -- and therefore
|
|
1515
|
+
zero discrepancies -- at every bond, which must count as converged.)
|
|
1516
|
+
|
|
1517
|
+
avg_jsd and budget_violations (from the underlying MPSSimulator runs)
|
|
1518
|
+
are reported per bond for context only, never used to decide the
|
|
1519
|
+
verdict -- a low average JSD is computed per truncation step and says
|
|
1520
|
+
nothing about whether the specific observable being tracked has
|
|
1521
|
+
settled down as `max_bond` grows.
|
|
1522
|
+
|
|
1523
|
+
If max_bond_used() at the highest bond still equals that bond's cap,
|
|
1524
|
+
the truncation never had headroom below max_bond at any cut, so no
|
|
1525
|
+
tolerance can be certified from this data: every observable's verdict
|
|
1526
|
+
becomes "undecidable" regardless of its own discrepancies.
|
|
1527
|
+
"""
|
|
1528
|
+
if len(bonds) < 3:
|
|
1529
|
+
raise ValueError(f"bond_convergence needs at least 3 bonds to detect a trend, got {len(bonds)}")
|
|
1530
|
+
|
|
1531
|
+
chi_used, avg_jsd, budget_violations = [], [], []
|
|
1532
|
+
values = [[] for _ in observables]
|
|
1533
|
+
for bond in bonds:
|
|
1534
|
+
mps = MPSSimulator(n_qubits=n_qubits, max_bond=bond, **mps_kwargs)
|
|
1535
|
+
mps.run_circuit_jit(ops)
|
|
1536
|
+
chi_used.append(mps.max_bond_used())
|
|
1537
|
+
avg_jsd.append(mps.avg_jsd())
|
|
1538
|
+
budget_violations.append(mps.budget_violations)
|
|
1539
|
+
for obs_idx, obs in enumerate(observables):
|
|
1540
|
+
values[obs_idx].append(mps_pauli_expectation(mps, obs))
|
|
1541
|
+
|
|
1542
|
+
diffs = [
|
|
1543
|
+
[abs(vals[i + 1] - vals[i]) for i in range(len(vals) - 1)]
|
|
1544
|
+
for vals in values
|
|
1545
|
+
]
|
|
1546
|
+
|
|
1547
|
+
undecidable = chi_used[-1] >= bonds[-1]
|
|
1548
|
+
verdicts = []
|
|
1549
|
+
for d in diffs:
|
|
1550
|
+
if undecidable:
|
|
1551
|
+
verdicts.append("undecidable")
|
|
1552
|
+
elif all(d[i + 1] <= d[i] for i in range(len(d) - 1)) and d[-1] < tol:
|
|
1553
|
+
verdicts.append("converged")
|
|
1554
|
+
else:
|
|
1555
|
+
verdicts.append("not_converged")
|
|
1556
|
+
|
|
1557
|
+
return BondConvergenceResult(
|
|
1558
|
+
bonds=list(bonds), chi_used=chi_used, avg_jsd=avg_jsd,
|
|
1559
|
+
budget_violations=budget_violations, values=values, diffs=diffs,
|
|
1560
|
+
verdicts=verdicts,
|
|
1561
|
+
)
|
|
@@ -9,13 +9,23 @@ instead of reimplementing them.
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import numpy as np
|
|
12
|
-
import pennylane as qml
|
|
13
|
-
import pennylane.qchem.observable_hf as _pl_observable
|
|
14
12
|
|
|
15
13
|
from dense_evolution.native_hf.basis import build_molecule_shells
|
|
16
14
|
from dense_evolution.native_hf.assembly import build_overlap_matrix, build_core_hamiltonian, build_repulsion_tensor
|
|
17
15
|
from dense_evolution.native_hf.scf import run_scf, HFResult
|
|
18
16
|
|
|
17
|
+
# pennylane is installed in this test suite's own environment, so the
|
|
18
|
+
# except branch below (and the `if qml is None` check further down) is
|
|
19
|
+
# only exercised by tests/unit/test_imports.py, which blocks pennylane
|
|
20
|
+
# in a separate subprocess -- invisible to same-process coverage, so
|
|
21
|
+
# excluded rather than chased with an artificial same-process test.
|
|
22
|
+
try:
|
|
23
|
+
import pennylane as qml
|
|
24
|
+
import pennylane.qchem.observable_hf as _pl_observable
|
|
25
|
+
except ModuleNotFoundError: # pragma: no cover
|
|
26
|
+
qml = None
|
|
27
|
+
_pl_observable = None
|
|
28
|
+
|
|
19
29
|
_BOHR_PER_ANGSTROM = 1.0 / 0.52917721067
|
|
20
30
|
|
|
21
31
|
|
|
@@ -64,6 +74,12 @@ def build_qubit_hamiltonian(
|
|
|
64
74
|
latter useful for e.g. reporting the SCF energy alongside the
|
|
65
75
|
post-mapping ground-state energy.
|
|
66
76
|
"""
|
|
77
|
+
if qml is None: # pragma: no cover -- see the try/except above
|
|
78
|
+
raise ModuleNotFoundError(
|
|
79
|
+
"build_qubit_hamiltonian requires pennylane. "
|
|
80
|
+
"Install it with: pip install dense-evolution[pennylane]"
|
|
81
|
+
)
|
|
82
|
+
|
|
67
83
|
geometry_bohr = np.asarray(geometry_angstrom) * _BOHR_PER_ANGSTROM
|
|
68
84
|
shells = build_molecule_shells(atomic_numbers, geometry_bohr, basis_name)
|
|
69
85
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dense-evolution
|
|
3
|
-
Version: 8.1.
|
|
3
|
+
Version: 8.1.78
|
|
4
4
|
Summary: High-performance quantum simulation toolkit -- Statevector/MPS engines with JIT compilation, noise modeling, VQE, QEC, quantum chemistry, and agent-native tooling
|
|
5
5
|
Author-email: Salvatore Pennacchio <jtatopenn@libero.it>
|
|
6
6
|
License: Business Source License 1.1
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dense-evolution"
|
|
7
|
-
version = "8.1.
|
|
7
|
+
version = "8.1.78"
|
|
8
8
|
description = "High-performance quantum simulation toolkit -- Statevector/MPS engines with JIT compilation, noise modeling, VQE, QEC, quantum chemistry, and agent-native tooling"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/chunk/__init__.py
RENAMED
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/chunk/_engine_imports.py
RENAMED
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/chunk/circuit_chunker.py
RENAMED
|
File without changes
|
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/chunk/disk_overflow.py
RENAMED
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/backends/chunk/geometry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/circuits/random_circuit.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/interop/qiskit_pennylane.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/mitigation/kl_divergence.py
RENAMED
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/mitigation/magic_entropy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/native_hf/libcint_bridge.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/density_matrix_channels.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/kraus/amplitude_damping.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution/noise/kraus/depolarizing.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/dense_evolution.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/dashboard/core/circuit_builder_component.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/ia_utils/adversarial_vector_attack.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dense_evolution-8.1.76 → dense_evolution-8.1.78}/tools/mcp_server/tools/mitigation_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|