1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! Runtime resource control and progress; never part of numerical identity.
use super::{PathSearchProgress, PreparedRefeffStats};
use serde::{Deserialize, Serialize};
use std::sync::{
atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering},
Arc, Mutex,
};
/// Since 0.2.12: current prepared-scattering operation. A stage is not a percentage.
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
pub enum PreparedStage {
/// No calculation is currently in progress.
#[default]
Idle,
/// Enumerating directed scattering paths within the displacement envelope.
Paths,
/// Preparing fixed electronic potentials and phase tables.
Electronics,
/// Evaluating or reusing explicit scattering paths.
Scattering,
}
/// Since 0.2.12: inexpensive progress snapshot for another thread to display.
/// Counters can advance during an attempted move that is later rejected or fails.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct PreparedProgress {
/// Current operation, independent of optimizer attempts or generations.
pub stage: PreparedStage,
/// Zero-based absorber index during preparation; None for a calculation batch.
pub absorber: Option<usize>,
/// Geometric-search counters for the current absorber.
pub search: PathSearchProgress,
/// Catalogue entries visited in the current calculation batch, including hits.
pub paths_processed: u64,
/// Completed work and retained cache payload; excludes transient allocations.
pub stats: PreparedRefeffStats,
}
struct Inner {
observed: AtomicBool,
budget: AtomicUsize,
paths: AtomicU64,
progress: Mutex<PreparedProgress>,
}
/// Since 0.2.12: thread-safe progress and cache-budget handle. Read [`Self::snapshot`]
/// periodically; it never runs scattering. Set [`Self::set_cache_bytes`] to adjust
/// memory without changing spectra, random streams, or checkpoint identity.
#[derive(Clone)]
pub struct PreparedRefeffMonitor(Arc<Inner>);
impl PreparedRefeffMonitor {
pub(super) fn new(bytes: usize) -> Self {
Self(Arc::new(Inner {
observed: AtomicBool::new(false),
budget: AtomicUsize::new(bytes),
paths: AtomicU64::new(0),
progress: Mutex::new(PreparedProgress::default()),
}))
}
pub(super) fn observe(&self) {
self.0.observed.store(true, Ordering::Relaxed);
}
/// Current requested cache-payload limit in bytes. This is not a process
/// memory limit: electronic tables, catalogues and active work are separate.
pub fn cache_bytes(&self) -> usize {
self.0.budget.load(Ordering::Relaxed)
}
/// Change the cache-payload limit at the next absorber-batch boundary.
/// Decreasing it evicts the oldest snapshots; zero disables retention.
/// Increasing it permits later results to be cached but allocates nothing
/// immediately. Neither operation changes the calculator's stored identity.
/// The caller is responsible for reserving memory for the rest of the process.
pub fn set_cache_bytes(&self, bytes: usize) {
self.0.budget.store(bytes, Ordering::Relaxed);
}
/// Clone the most recent completed counters and the live stage/path count.
/// During a batch the retained-memory counters describe the previous boundary.
pub fn snapshot(&self) -> PreparedProgress {
let mut progress = self
.0
.progress
.lock()
.unwrap_or_else(|p| p.into_inner())
.clone();
progress.paths_processed = self.0.paths.load(Ordering::Relaxed);
progress.stats.cache_limit_bytes = self.cache_bytes();
progress
}
pub(super) fn stage(&self, stage: PreparedStage, absorber: Option<usize>) {
if self.0.observed.load(Ordering::Relaxed) {
self.0.paths.store(0, Ordering::Relaxed);
let mut p = self.0.progress.lock().unwrap_or_else(|p| p.into_inner());
p.stage = stage;
p.absorber = absorber;
p.search = PathSearchProgress::default();
}
}
pub(super) fn search(&self, search: PathSearchProgress) {
if self.0.observed.load(Ordering::Relaxed) {
self.0
.progress
.lock()
.unwrap_or_else(|p| p.into_inner())
.search = search;
}
}
pub(super) fn path(&self) {
if self.0.observed.load(Ordering::Relaxed) {
self.0.paths.fetch_add(1, Ordering::Relaxed);
}
}
pub(super) fn publish(&self, stats: PreparedRefeffStats) {
if self.0.observed.load(Ordering::Relaxed) {
self.0
.progress
.lock()
.unwrap_or_else(|p| p.into_inner())
.stats = stats;
}
}
}