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,95 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import jax.numpy as jnp
|
|
5
|
+
|
|
6
|
+
from .guard import _device_memory_budget_bytes, HAS_JAX
|
|
7
|
+
|
|
8
|
+
__all__ = ["MemoryChunker", "get_dynamic_chunk"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_dynamic_chunk(dtype_target) -> int:
|
|
12
|
+
available_bytes, _source = _device_memory_budget_bytes()
|
|
13
|
+
safe_bytes = available_bytes * 0.85
|
|
14
|
+
if HAS_JAX and dtype_target is jnp.complex128:
|
|
15
|
+
bpe = 16
|
|
16
|
+
elif dtype_target is np.complex128:
|
|
17
|
+
bpe = 16
|
|
18
|
+
else:
|
|
19
|
+
bpe = 8
|
|
20
|
+
max_elements = safe_bytes / bpe
|
|
21
|
+
max_bits = int(np.floor(np.log2(max(max_elements, 2.0))))
|
|
22
|
+
# Upper bound raised 27 -> 30 (prog.txt / user request): the real
|
|
23
|
+
# constraint is already _device_memory_budget_bytes's own VRAM/RAM
|
|
24
|
+
# reading (a 27-bit chunk is only 2.1GB regardless of dtype -- any
|
|
25
|
+
# GPU with more than ~2.5GB free was being capped well below what it
|
|
26
|
+
# could actually hold). 30 keeps a ceiling at all (avoids ever
|
|
27
|
+
# returning a chunk_size_bits so large a single chunk's own indexing
|
|
28
|
+
# arithmetic elsewhere risks int32 overflow) without arbitrarily
|
|
29
|
+
# discarding real headroom on 16GB+ GPUs (a Kaggle T4/P100 at
|
|
30
|
+
# ~0.85*16GB/16B per complex128 element already computes max_bits
|
|
31
|
+
# near 30 on its own -- this line was the thing throwing that away).
|
|
32
|
+
return max(16, min(max_bits, 30))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _dtype_for_qubits(n_qubits: int):
|
|
36
|
+
xp = jnp if HAS_JAX else np
|
|
37
|
+
return xp.complex64 if n_qubits > 26 else xp.complex128
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ─────────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
# MemoryChunker (chunk1)
|
|
42
|
+
# ─────────────────────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
class MemoryChunker:
|
|
45
|
+
"""
|
|
46
|
+
Geometry calculator for chunked simulation.
|
|
47
|
+
|
|
48
|
+
Attributes
|
|
49
|
+
----------
|
|
50
|
+
n_qubits int — requested logical qubit count
|
|
51
|
+
dtype — numpy/jax dtype for the statevector
|
|
52
|
+
chunk_size_bits int — safe qubit-width that fits in RAM
|
|
53
|
+
num_chunks int — number of statevector chunks required
|
|
54
|
+
chunk_dim int — 2 ** chunk_size_bits
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, n_qubits: int):
|
|
58
|
+
# Looked up through the package's own top-level attribute, not
|
|
59
|
+
# this module's local `get_dynamic_chunk` name directly -- tests
|
|
60
|
+
# (and any caller) override chunk sizing via
|
|
61
|
+
# `dense_evolution.chunk.get_dynamic_chunk = ...`, the package's
|
|
62
|
+
# public attribute; a deferred `from . import` re-reads that
|
|
63
|
+
# attribute at call time instead of capturing this module's own
|
|
64
|
+
# function object once at import time, so an override actually
|
|
65
|
+
# takes effect on the next MemoryChunker() constructed.
|
|
66
|
+
from . import get_dynamic_chunk as _get_dynamic_chunk
|
|
67
|
+
self.n_qubits = n_qubits
|
|
68
|
+
self.dtype = _dtype_for_qubits(n_qubits)
|
|
69
|
+
self.chunk_size_bits = _get_dynamic_chunk(self.dtype)
|
|
70
|
+
|
|
71
|
+
if self.n_qubits <= self.chunk_size_bits:
|
|
72
|
+
self.num_chunks = 1
|
|
73
|
+
self.chunk_dim = 2 ** self.n_qubits
|
|
74
|
+
else:
|
|
75
|
+
self.num_chunks = 2 ** (self.n_qubits - self.chunk_size_bits)
|
|
76
|
+
self.chunk_dim = 2 ** self.chunk_size_bits
|
|
77
|
+
|
|
78
|
+
def geometry(self) -> Tuple[int, int, int]:
|
|
79
|
+
"""(num_chunks, chunk_dim, chunk_size_bits)"""
|
|
80
|
+
return self.num_chunks, self.chunk_dim, self.chunk_size_bits
|
|
81
|
+
|
|
82
|
+
def memory_mb(self) -> float:
|
|
83
|
+
"""Estimated RAM per chunk in MB."""
|
|
84
|
+
bpe = np.dtype(self.dtype).itemsize
|
|
85
|
+
return (self.chunk_dim * bpe) / (1024 * 1024)
|
|
86
|
+
|
|
87
|
+
def __repr__(self) -> str:
|
|
88
|
+
return (
|
|
89
|
+
f"MemoryChunker(n_qubits={self.n_qubits}, "
|
|
90
|
+
f"num_chunks={self.num_chunks}, "
|
|
91
|
+
f"chunk_dim={self.chunk_dim}, "
|
|
92
|
+
f"chunk_size_bits={self.chunk_size_bits}, "
|
|
93
|
+
f"dtype={self.dtype}, "
|
|
94
|
+
f"mem_per_chunk={self.memory_mb():.2f} MB)"
|
|
95
|
+
)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import gc
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
|
|
4
|
+
import psutil
|
|
5
|
+
|
|
6
|
+
# JAX is now a mandatory dependency (see registry.py) -- kept as an
|
|
7
|
+
# import-time flag for the dual-path code below, which stays as-is.
|
|
8
|
+
import jax
|
|
9
|
+
HAS_JAX = True
|
|
10
|
+
|
|
11
|
+
__all__ = ["MemoryPressureError", "SafeMemoryGuard"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _device_memory_budget_bytes(device=None) -> Tuple[float, str]:
|
|
15
|
+
"""Real, dynamic memory budget -- reads the ACTIVE compute device's
|
|
16
|
+
own memory via JAX's device.memory_stats() (bytes_limit -
|
|
17
|
+
bytes_in_use) when available, falling back to host RAM via
|
|
18
|
+
psutil.virtual_memory() otherwise (some backends, e.g. plain CPU,
|
|
19
|
+
return None from memory_stats() -- confirmed directly, so on CPU
|
|
20
|
+
this is identical to the old psutil-only behavior). get_dynamic_chunk
|
|
21
|
+
and SafeMemoryGuard used to read psutil.virtual_memory()
|
|
22
|
+
UNCONDITIONALLY, which is only correct when the compute device IS the
|
|
23
|
+
host (true on CPU, false on GPU/TPU) -- sizing chunks off host RAM
|
|
24
|
+
while the data actually lives in smaller GPU VRAM is exactly why
|
|
25
|
+
Chunk's own README benchmark ("~2GB constant RAM regardless of qubit
|
|
26
|
+
count") holds on CPU but silently mis-sized chunks on GPU, causing
|
|
27
|
+
MemoryPressureError/real OOM well before the GPU's own VRAM was
|
|
28
|
+
actually exhausted. Verified on a real Colab T4 GPU: before this fix,
|
|
29
|
+
chunk_size_bits was computed from host RAM (often >11GB free on
|
|
30
|
+
Colab) while the data lived in the T4's 11-15GB VRAM pool -- after
|
|
31
|
+
this fix, device.memory_stats() correctly reports VRAM instead.
|
|
32
|
+
|
|
33
|
+
Returns (available_bytes, source_description) -- the raw number, not
|
|
34
|
+
a safety-margined one; callers apply their own margin."""
|
|
35
|
+
if device is None:
|
|
36
|
+
device = jax.devices()[0] if HAS_JAX else None
|
|
37
|
+
stats = device.memory_stats() if device is not None and hasattr(device, "memory_stats") else None
|
|
38
|
+
if stats and stats.get("bytes_limit"):
|
|
39
|
+
available = stats["bytes_limit"] - stats.get("bytes_in_use", 0)
|
|
40
|
+
return float(available), f"device.memory_stats() on {device}"
|
|
41
|
+
vm = psutil.virtual_memory()
|
|
42
|
+
return float(vm.available), "psutil (host RAM) -- device.memory_stats() unavailable"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _device_total_bytes(device=None) -> float:
|
|
46
|
+
"""Companion to _device_memory_budget_bytes for the TOTAL (not
|
|
47
|
+
available) capacity -- used by SafeMemoryGuard to compute free_pct
|
|
48
|
+
against the right pool (device VRAM on GPU/TPU, host RAM on CPU)."""
|
|
49
|
+
if device is None:
|
|
50
|
+
device = jax.devices()[0] if HAS_JAX else None
|
|
51
|
+
stats = device.memory_stats() if device is not None and hasattr(device, "memory_stats") else None
|
|
52
|
+
if stats and stats.get("bytes_limit"):
|
|
53
|
+
return float(stats["bytes_limit"])
|
|
54
|
+
return float(psutil.virtual_memory().total)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ─────────────────────────────────────────────────────────────────────────────────
|
|
58
|
+
# SafeMemoryGuard — Anti-OOM block
|
|
59
|
+
# ─────────────────────────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
class MemoryPressureError(RuntimeError):
|
|
62
|
+
"""
|
|
63
|
+
Raised when available system RAM drops below the configured safety threshold.
|
|
64
|
+
Catches the condition *before* the allocator attempts and crashes with
|
|
65
|
+
jaxlib.xla_extension.XlaRuntimeError: RESOURCE_EXHAUSTED.
|
|
66
|
+
"""
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class SafeMemoryGuard:
|
|
71
|
+
"""
|
|
72
|
+
Monitors system RAM before every high-memory operation and blocks execution
|
|
73
|
+
if free RAM falls below ``threshold_pct`` of total physical memory.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
_WARN_MULTIPLIER = 2.0
|
|
77
|
+
# Below this, check_allocation's own margin-after-allocation math
|
|
78
|
+
# reduces to "is ambient free RAM currently below threshold_pct?",
|
|
79
|
+
# independent of required_mb -- for a request this small that's not
|
|
80
|
+
# protecting against anything, since real machines don't run out of
|
|
81
|
+
# memory over a request this size regardless of ambient load. Sized
|
|
82
|
+
# to comfortably cover a dense statevector up to 24 qubits at
|
|
83
|
+
# complex128 (2**24 * 16 bytes = 268.4 MB) with a real margin -- the
|
|
84
|
+
# same 24-qubit figure dashboard_core.system_limits already treats as
|
|
85
|
+
# MPS's own RAM-independent ceiling, and well inside the 16-27 qubit
|
|
86
|
+
# range dense_evolution.chunk.get_dynamic_chunk considers safe on any
|
|
87
|
+
# machine (27 qubits / 2 GB is that function's own practical ceiling
|
|
88
|
+
# for a single dense block). An 8 GB minimum-spec machine can always
|
|
89
|
+
# absorb an allocation this size without real risk, even while
|
|
90
|
+
# running low on RAM from unrelated processes. A 2-qubit statevector
|
|
91
|
+
# (64 bytes) used to be refused on a machine sitting at 8% free RAM
|
|
92
|
+
# from unrelated processes, which was not a real anti-OOM save, just
|
|
93
|
+
# a false positive from a threshold designed for actual multi-GB
|
|
94
|
+
# allocations. Only bypasses when available_mb is itself comfortably
|
|
95
|
+
# above this floor too (see check_allocation below) -- a machine
|
|
96
|
+
# truly down to its last few hundred MB still gets the real check.
|
|
97
|
+
_NEGLIGIBLE_MB = 512.0
|
|
98
|
+
|
|
99
|
+
def __init__(self, threshold_pct: float = 0.15, gc_before_check: bool = True):
|
|
100
|
+
if not 0.0 < threshold_pct < 1.0:
|
|
101
|
+
raise ValueError(f"threshold_pct must be in (0, 1), got {threshold_pct}")
|
|
102
|
+
self.threshold_pct = threshold_pct
|
|
103
|
+
self.gc_before_check = gc_before_check
|
|
104
|
+
# Device VRAM total on GPU/TPU, host RAM total on CPU (identical
|
|
105
|
+
# to the old psutil-only value there) -- see
|
|
106
|
+
# _device_memory_budget_bytes's docstring for why this matters.
|
|
107
|
+
self._total_mb = _device_total_bytes() / (1024 * 1024)
|
|
108
|
+
|
|
109
|
+
def status(self) -> dict:
|
|
110
|
+
available_bytes, _source = _device_memory_budget_bytes()
|
|
111
|
+
available_mb = available_bytes / (1024 * 1024)
|
|
112
|
+
free_pct = available_mb / self._total_mb if self._total_mb > 0 else 0.0
|
|
113
|
+
return {
|
|
114
|
+
"total_mb" : self._total_mb,
|
|
115
|
+
"available_mb": available_mb,
|
|
116
|
+
"used_pct" : (1.0 - free_pct) * 100.0,
|
|
117
|
+
"free_pct" : free_pct * 100.0,
|
|
118
|
+
"safe" : free_pct >= self.threshold_pct,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
def check(self, context: str = "") -> None:
|
|
122
|
+
if self.gc_before_check:
|
|
123
|
+
gc.collect()
|
|
124
|
+
|
|
125
|
+
s = self.status()
|
|
126
|
+
tag = f"[{context}] " if context else ""
|
|
127
|
+
free_frac = s["free_pct"] / 100.0
|
|
128
|
+
|
|
129
|
+
if not s["safe"]:
|
|
130
|
+
raise MemoryPressureError(
|
|
131
|
+
f"\n{'─'*60}\n"
|
|
132
|
+
f" {tag}MEMORIA CRITICA — simulazione bloccata\n"
|
|
133
|
+
f" Disponibile : {s['available_mb']:.0f} MB "
|
|
134
|
+
f"({s['free_pct']:.1f}% libera)\n"
|
|
135
|
+
f" Soglia : {self.threshold_pct * 100:.0f}% "
|
|
136
|
+
f"({self._total_mb * self.threshold_pct:.0f} MB)\n"
|
|
137
|
+
f" Azione : liberare RAM o ridurre n_qubits / chunk_size.\n"
|
|
138
|
+
f"{'─'*60}"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
warn_threshold = self.threshold_pct * self._WARN_MULTIPLIER
|
|
142
|
+
if free_frac < warn_threshold:
|
|
143
|
+
print(
|
|
144
|
+
f" [WARN] {tag}RAM bassa: {s['available_mb']:.0f} MB liberi "
|
|
145
|
+
f"({s['free_pct']:.1f}%) — soglia critica al "
|
|
146
|
+
f"{self.threshold_pct * 100:.0f}%."
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def check_allocation(self, required_mb: float, context: str = "") -> None:
|
|
150
|
+
"""Like check(), but sized: verifies that *required_mb* can be
|
|
151
|
+
allocated while still leaving threshold_pct free afterwards —
|
|
152
|
+
check() alone only looks at RAM free *right now*, independent of
|
|
153
|
+
what's about to be allocated (needed for Chunk's multi-chunk path,
|
|
154
|
+
where several chunk-sized simulators are held in RAM at once)."""
|
|
155
|
+
if self.gc_before_check:
|
|
156
|
+
gc.collect()
|
|
157
|
+
|
|
158
|
+
s = self.status()
|
|
159
|
+
if required_mb < self._NEGLIGIBLE_MB and s["available_mb"] > self._NEGLIGIBLE_MB:
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
tag = f"[{context}] " if context else ""
|
|
163
|
+
available_after_mb = s["available_mb"] - required_mb
|
|
164
|
+
free_frac_after = available_after_mb / self._total_mb if self._total_mb > 0 else 0.0
|
|
165
|
+
|
|
166
|
+
if available_after_mb < 0 or free_frac_after < self.threshold_pct:
|
|
167
|
+
import socket
|
|
168
|
+
raise MemoryPressureError(
|
|
169
|
+
f"\n{'─'*60}\n"
|
|
170
|
+
f" {tag}MEMORIA INSUFFICIENTE per l'allocazione richiesta\n"
|
|
171
|
+
f" Macchina : {socket.gethostname()} ({self._total_mb / 1024:.1f} GB RAM totali)\n"
|
|
172
|
+
f" Richiesti : {required_mb:.0f} MB\n"
|
|
173
|
+
f" Disponibile : {s['available_mb']:.0f} MB ({s['free_pct']:.1f}% libera)\n"
|
|
174
|
+
f" Dopo alloc. : {available_after_mb:.0f} MB ({free_frac_after * 100:.1f}% libera)\n"
|
|
175
|
+
f" Soglia : {self.threshold_pct * 100:.0f}% libera dopo l'allocazione\n"
|
|
176
|
+
f" Azione : ridurre n_qubits, liberare RAM, oppure passare\n"
|
|
177
|
+
f" allow_disk_overflow=True a Chunk() per continuare\n"
|
|
178
|
+
f" oltre la RAM disponibile usando lo storage su disco\n"
|
|
179
|
+
f" (vedi disk_overflow.py).\n"
|
|
180
|
+
f"{'─'*60}"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def __repr__(self) -> str:
|
|
184
|
+
s = self.status()
|
|
185
|
+
return (
|
|
186
|
+
f"SafeMemoryGuard("
|
|
187
|
+
f"threshold={self.threshold_pct*100:.0f}%, "
|
|
188
|
+
f"available={s['available_mb']:.0f} MB / {s['free_pct']:.1f}% free, "
|
|
189
|
+
f"safe={s['safe']})"
|
|
190
|
+
)
|