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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
//! [`State`], [`HilbertState`], and [`Discrete`] traits — 3-layer vector
//! interface for Chernoff iteration (Wave 3, ADR-0043, v2.0 MAJOR).
//!
//! ## Layer 1 — [`State<F>`]
//!
//! Zero-allocation Banach-vector primitives sufficient for forward Chernoff
//! iteration (Theorem 6, Remizov 2025). **`Clone` is NOT a supertrait** — see
//! ADR-0043 for rationale. Concrete `GridFn{1,2,3}D<F>` retain `Clone` via
//! `#[derive(Clone)]` and also retain the v1.x inherent methods (`axpy`,
//! `scale`, `zeroed_like`) for source-level backward compatibility.
//!
//! ## Layer 2 — [`HilbertState<F>`]
//!
//! Inner-product extension for L²-flavoured error estimators (Wave 4 adaptive
//! PI controller foreshadow) and adjoint-Chernoff schemes. Counting-measure ℓ²:
//! `dot(a,b) = Σᵢ aᵢ·bᵢ`. Default impls for `norm_sq` and `norm_l2`.
//!
//! ## Layer 3 — [`Discrete<F>`]
//!
//! Graph/manifold/lattice extension. Uses GATs (stable Rust 1.65, MSRV 1.78)
//! to eliminate `Box<dyn Iterator>` per the v0.14.0 spike finding. Tensor
//! grid types (`GridFn{1,2,3}D`) do **not** implement `Discrete<F>`.
//!
//! ## Macro
//!
//! [`crate::impl_state_for_gridfn!`] generates identical `State<F>` + `HilbertState<F>`
//! impls for all three tensor grid-fn types without duplicating code.
use num_traits::Float;
use crate::float::SemiflowFloat;
// ---------------------------------------------------------------------------
// Layer 1 — State<F>
// ---------------------------------------------------------------------------
/// Minimal zero-allocation Banach-vector interface for Chernoff iteration.
///
/// **Breaking change vs v1.x**: `Clone` is no longer a supertrait. Concrete
/// `GridFn{1,2,3}D<F>` retain `Clone` via `#[derive(Clone)]` and provide v1.x
/// inherent shims (`axpy`, `scale`, `zeroed_like`). Generic code that previously
/// relied on `T: State<F>` implying `T: Clone` must add an explicit `+ Clone`
/// bound.
///
/// ## Algebraic laws (modulo f64 rounding)
///
/// - `axpy_into(F::zero(), &x)` is a no-op on `self`.
/// - After `copy_from(&src)`: `self` is node-wise equal to `src`; `len()` unchanged.
/// - After `zero_into()`: `norm_sup() == F::zero()`; `len()` unchanged.
/// - `len()` is invariant under `axpy_into`, `copy_from`, `zero_into`.
/// - `norm_sup()` returns `NaN` iff `self` contains any `NaN`.
///
/// ## When to implement
///
/// Implement `State<F>` for any function-on-a-grid type you want to evolve
/// through a [`crate::ChernoffFunction`]. See [`HilbertState`] and [`Discrete`]
/// for optional extensions.
///
/// ## Example (v2.0 style)
///
/// ```rust
/// use semiflow::{Grid1D, GridFn1D, State};
/// let grid = Grid1D::new(-1.0, 1.0, 8).unwrap();
/// let mut u = GridFn1D::from_fn(grid, |x| x);
/// let v = GridFn1D::from_fn(grid, |x| x * x);
/// // Wave 3: axpy_into (zero-alloc)
/// <GridFn1D<f64> as State<f64>>::axpy_into(&mut u, 2.0, &v);
/// assert!(u.norm_sup() > 0.0);
/// // v1.x source-compat inherent method still works:
/// u.axpy(1.0, &v);
/// ```
pub trait State<F: SemiflowFloat = f64> {
/// Number of degrees of freedom (grid nodes, graph nodes, etc.).
fn len(&self) -> usize;
/// Returns `true` if this state has no nodes.
#[inline]
fn is_empty(&self) -> bool {
self.len() == 0
}
/// In-place AXPY: `self ← self + alpha · src`. No allocation.
///
/// # Panics (debug)
/// `debug_assert_eq!(self.len(), src.len())` — shape mismatch.
fn axpy_into(&mut self, alpha: F, src: &Self);
/// Node-wise copy: `self ← src`. No allocation.
///
/// # Panics (debug)
/// `debug_assert_eq!(self.len(), src.len())` — shape mismatch.
fn copy_from(&mut self, src: &Self);
/// Zero all nodes: `self[i] ← 0` for all `i`. `len()` unchanged.
fn zero_into(&mut self);
/// Sup-norm `‖self‖_∞ = max_i |self[i]|`.
///
/// Returns `NaN` if any node is `NaN`.
fn norm_sup(&self) -> F;
/// In-place scale: `self ← k · self`.
///
/// **Must be overridden.** The default panics with `unimplemented!`.
/// All concrete types in `semiflow` override this. Generic code should
/// use concrete types' inherent `scale(k)` or override this method.
fn scale_into(&mut self, k: F) {
let _ = k;
unimplemented!(
"State::scale_into: implementors must override this method. \
Concrete GridFnXD<F> provide an inherent scale(k) method."
)
}
}
// ---------------------------------------------------------------------------
// Layer 2 — HilbertState<F>
// ---------------------------------------------------------------------------
/// Hilbert-space extension: inner product and L²-norm.
///
/// Extends [`State<F>`] for adjoint-Chernoff and L²-error estimators.
/// Wave 4 will use `dot` + `norm_sq` in the zero-alloc Richardson error
/// estimator and H211b adaptive step controller.
///
/// **Counting-measure ℓ²**: `dot(a,b) = Σᵢ aᵢ·bᵢ`. For weighted L²-norms,
/// callers multiply by `√(dx·dy·dz)` outside the trait.
///
/// ## Algebraic laws
///
/// - `dot(a, b) == dot(b, a)` (symmetric, real-valued).
/// - `dot(a, a) == norm_sq(a)`.
/// - `norm_sq(a) ≥ 0.0` for finite `a`.
/// - After `zero_into()`: `dot(s, anything) == 0.0`.
pub trait HilbertState<F: SemiflowFloat = f64>: State<F> {
/// Inner product `⟨self, other⟩ = Σᵢ self[i] · other[i]`.
///
/// # Panics (debug)
/// `debug_assert_eq!(self.len(), other.len())`.
fn dot(&self, other: &Self) -> F;
/// `‖self‖² = ⟨self, self⟩`. Default impl provided.
#[inline]
fn norm_sq(&self) -> F {
self.dot(self)
}
/// `‖self‖₂ = √‖self‖²`. Provided convenience method.
#[inline]
fn norm_l2(&self) -> F
where
F: Float,
{
self.norm_sq().sqrt()
}
}
// ---------------------------------------------------------------------------
// Layer 3 — Discrete<F>
// ---------------------------------------------------------------------------
/// Discrete-domain state for graph, manifold, and lattice geometries.
///
/// Extends [`State<F>`] with indexed node access and GAT-based neighbour
/// iteration. **Tensor types (`GridFn{1,2,3}D`) do NOT implement this trait**
/// — there is no canonical neighbour set for a tensor grid, and tensor
/// pipelines use slice pencils (Wave 2).
///
/// The GAT `type Neighbours<'a>` eliminates the `Box<dyn Iterator>` allocation
/// flagged by the v0.14.0 spike (stable since Rust 1.65; MSRV is 1.78).
///
/// ## Boundary conditions
///
/// Implicit Dirichlet zero: returning an empty iterator from `neighbours` for
/// boundary nodes encodes zero-value BC. Explicit BC enum is deferred to v2.x.
pub trait Discrete<F: SemiflowFloat = f64>: State<F> {
/// Node index type. Must be `Copy + Eq + Hash` for use in maps.
type Idx: Copy + Eq + core::hash::Hash;
/// Iterator type for `(neighbour_idx, edge_weight)` pairs.
type Neighbours<'a>: Iterator<Item = (Self::Idx, F)>
where
Self: 'a;
/// Value at node `idx`.
fn get(&self, idx: Self::Idx) -> F;
/// Set value at node `idx` to `val`.
fn set(&mut self, idx: Self::Idx, val: F);
/// Iterate over all node indices. Order is stable across calls
/// when `self` is unchanged.
fn indices(&self) -> impl Iterator<Item = Self::Idx> + '_;
/// Iterate over `(neighbour_idx, edge_weight)` pairs for node `idx`.
///
/// Empty iterator = Dirichlet-zero boundary condition.
fn neighbours(&self, idx: Self::Idx) -> Self::Neighbours<'_>;
}
// ---------------------------------------------------------------------------
// Shared macro helpers — used by impl_state_for_gridfn! (extracted batch H9b)
// ---------------------------------------------------------------------------
/// `axpy_into` body shared by all `GridFn{1,2,3}D<F>` — exact float op order preserved.
#[inline]
pub fn gridfn_axpy_into_slice<F: SemiflowFloat>(dst: &mut [F], alpha: F, src: &[F]) {
debug_assert_eq!(dst.len(), src.len(), "axpy_into: shape mismatch");
for (s, &x) in dst.iter_mut().zip(src.iter()) {
*s += alpha * x;
}
}
/// `copy_from` body shared by all `GridFn{1,2,3}D<F>`.
#[inline]
pub fn gridfn_copy_from_slice<F: SemiflowFloat>(dst: &mut [F], src: &[F]) {
debug_assert_eq!(dst.len(), src.len(), "copy_from: shape mismatch");
dst.copy_from_slice(src);
}
/// `norm_sup` body shared by all `GridFn{1,2,3}D<F>` — exact float op order preserved.
#[inline]
pub fn gridfn_norm_sup_slice<F: SemiflowFloat>(vals: &[F]) -> F {
vals.iter().fold(F::zero(), |acc, &v| {
let av = <F as num_traits::Float>::abs(v);
if av > acc {
av
} else {
acc
}
})
}
/// `dot` body shared by all `GridFn{1,2,3}D<F>` — exact float op order preserved.
#[inline]
pub fn gridfn_dot_slice<F: SemiflowFloat>(a: &[F], b: &[F]) -> F {
debug_assert_eq!(a.len(), b.len(), "dot: shape mismatch");
a.iter()
.zip(b.iter())
.fold(F::zero(), |acc, (&ai, &bi)| acc + ai * bi)
}
// ---------------------------------------------------------------------------
// Shared macro for GridFn{1,2,3}D — State<F> + HilbertState<F>
// ---------------------------------------------------------------------------
/// Generate `State<F>` and `HilbertState<F>` impls for a grid-fn type.
///
/// Used by `GridFn1D`, `GridFn2D`, and `GridFn3D` to keep the implementations
/// textually identical while staying under the 500-LoC file cap for each.
///
/// The generated impl assumes the type has `pub values: Vec<F>` and `pub grid`.
// Scanner note: `impl_state_for_gridfn` contains the substring `fn ` in its name.
// The macro body is compact (≤50 lines) so no #[allow] is needed (batch H9b).
#[macro_export]
macro_rules! impl_state_for_gridfn {
($Ty:path) => {
impl<F: $crate::float::SemiflowFloat> $crate::state::State<F> for $Ty {
#[inline]
fn len(&self) -> usize {
self.values.len()
}
#[inline]
fn axpy_into(&mut self, alpha: F, src: &Self) {
$crate::state::gridfn_axpy_into_slice(&mut self.values, alpha, &src.values);
}
#[inline]
fn copy_from(&mut self, src: &Self) {
$crate::state::gridfn_copy_from_slice(&mut self.values, &src.values);
}
#[inline]
fn zero_into(&mut self) {
for v in &mut self.values {
*v = <F as num_traits::Zero>::zero();
}
}
#[inline]
fn norm_sup(&self) -> F {
$crate::state::gridfn_norm_sup_slice(&self.values)
}
#[inline]
fn scale_into(&mut self, k: F) {
for v in &mut self.values {
*v *= k;
}
}
}
impl<F: $crate::float::SemiflowFloat> $crate::state::HilbertState<F> for $Ty {
#[inline]
fn dot(&self, other: &Self) -> F {
$crate::state::gridfn_dot_slice(&self.values, &other.values)
}
}
};
}