ariadnetor_tensor/dense/storage.rs
1//! `DenseStorage<T>`: pure-data half of the dense tensor split.
2//!
3//! Carries only the flat element buffer. Shape and memory order live
4//! on the paired [`DenseLayout`](crate::DenseLayout); the wrapper
5//! [`DenseTensorData<T>`](crate::DenseTensorData) joins the two.
6//!
7//! `T: Scalar`-bound scalar-only data operations (`norm`,
8//! `norm_frobenius`, `normalize`) live here because their bodies
9//! touch only `data`; they require no shape, no memory order, and no
10//! compute backend. Symmetric with the BSp side.
11
12use std::ops::Mul;
13use std::sync::Arc;
14
15use aligned_vec::{AVec, ConstAlign};
16use num_traits::{Float, One, Zero};
17
18use super::Align64;
19use crate::{Storage, StorageFor};
20
21/// Pure-data half of the dense tensor split.
22///
23/// Holds a 64-byte-aligned flat buffer with Arc-based shared
24/// ownership (Copy-on-Write via [`Arc::make_mut`]). Shape and memory
25/// order are not carried here — they live on
26/// [`DenseLayout`](crate::DenseLayout). For the full
27/// storage + layout bundle, use
28/// [`DenseTensorData<T>`](crate::DenseTensorData).
29///
30/// # Type Parameters
31///
32/// * `T` - Element type (default: f64)
33pub struct DenseStorage<T = f64> {
34 data: Arc<AVec<T, Align64>>,
35}
36
37// Manual Clone impl: Arc<AVec<T, _>> is Clone regardless of T.
38// #[derive(Clone)] would unnecessarily require T: Clone.
39impl<T> Clone for DenseStorage<T> {
40 fn clone(&self) -> Self {
41 Self {
42 data: Arc::clone(&self.data),
43 }
44 }
45}
46
47impl<T> DenseStorage<T> {
48 /// Construct from a `Vec<T>`, internally rebuilding into a
49 /// 64-byte-aligned buffer.
50 pub fn new(data: Vec<T>) -> Self {
51 let len = data.len();
52 let mut aligned: AVec<T, ConstAlign<64>> = AVec::with_capacity(64, len);
53 for elem in data {
54 aligned.push(elem);
55 }
56 Self {
57 data: Arc::new(aligned),
58 }
59 }
60
61 /// Construct from an already-aligned `AVec` (zero-copy).
62 pub(crate) fn from_aligned(data: AVec<T, ConstAlign<64>>) -> Self {
63 Self {
64 data: Arc::new(data),
65 }
66 }
67
68 /// Get a reference to the underlying contiguous data.
69 pub fn data(&self) -> &[T] {
70 &self.data[..]
71 }
72
73 /// Get a mutable reference to the underlying data (triggers CoW
74 /// if shared).
75 pub fn data_mut(&mut self) -> &mut [T]
76 where
77 T: Clone,
78 {
79 Arc::make_mut(&mut self.data).as_mut_slice()
80 }
81
82 /// Iterate over all stored elements in flat (storage) order.
83 pub fn iter(&self) -> std::slice::Iter<'_, T> {
84 self.data[..].iter()
85 }
86
87 /// Get pointer to the underlying data for FFI.
88 pub fn as_ptr(&self) -> *const T {
89 self.data.as_ptr()
90 }
91
92 /// Get mutable pointer to the underlying data for FFI (triggers
93 /// CoW if shared).
94 pub fn as_mut_ptr(&mut self) -> *mut T
95 where
96 T: Clone,
97 {
98 Arc::make_mut(&mut self.data).as_mut_ptr()
99 }
100}
101
102impl<T> Storage for DenseStorage<T> {
103 type Element = T;
104
105 fn flat_len(&self) -> usize {
106 self.data.len()
107 }
108}
109
110impl<T> StorageFor<crate::DenseLayout> for DenseStorage<T> {}
111
112// ---------------------------------------------------------------------------
113// Length-preserving data operations
114//
115// Bodies only touch `data`, so they live on the storage half.
116// Mirrors the BSp storage pattern.
117// ---------------------------------------------------------------------------
118
119impl<T> DenseStorage<T>
120where
121 T: Clone,
122{
123 /// Fill every stored element with a constant value (triggers CoW
124 /// if shared).
125 pub(crate) fn fill(&mut self, value: T) {
126 Arc::make_mut(&mut self.data).as_mut_slice().fill(value);
127 }
128
129 /// Apply a function to each stored element in place (triggers CoW
130 /// if shared).
131 ///
132 /// Element ordering follows storage layout; the closure sees raw
133 /// flat positions without coordinate context.
134 pub(crate) fn map_mut<F>(&mut self, f: F)
135 where
136 F: Fn(&T) -> T,
137 {
138 let data = Arc::make_mut(&mut self.data).as_mut_slice();
139 for x in data.iter_mut() {
140 *x = f(x);
141 }
142 }
143
144 /// Scale every stored element by a scalar factor in place
145 /// (triggers CoW if shared).
146 pub(crate) fn scale<S>(&mut self, factor: S)
147 where
148 T: Mul<S, Output = T>,
149 S: Clone,
150 {
151 let data = Arc::make_mut(&mut self.data).as_mut_slice();
152 for elem in data.iter_mut() {
153 *elem = elem.clone() * factor.clone();
154 }
155 }
156}
157
158// ---------------------------------------------------------------------------
159// Scalar-only data operations
160//
161// Symmetric with `BlockSparseStorage`: norm-related operations read
162// only the flat buffer (no shape, no layout-internal metadata).
163// ---------------------------------------------------------------------------
164
165impl<T> DenseStorage<T>
166where
167 T: ariadnetor_core::Scalar,
168{
169 /// Squared Frobenius norm: Σ |element|².
170 fn norm_squared(&self) -> T::Real {
171 self.data
172 .iter()
173 .map(|&x| {
174 let a = x.abs();
175 a * a
176 })
177 .fold(T::Real::zero(), |acc, x| acc + x)
178 }
179
180 /// Frobenius norm: √(Σ |element|²).
181 pub(crate) fn norm_frobenius(&self) -> T::Real {
182 self.norm_squared().sqrt()
183 }
184
185 /// Frobenius norm (alias for [`norm_frobenius`](Self::norm_frobenius)).
186 pub(crate) fn norm(&self) -> T::Real {
187 self.norm_frobenius()
188 }
189
190 /// Normalize to unit Frobenius norm (in-place).
191 ///
192 /// Returns the norm before normalization. Panics if the tensor has
193 /// zero norm.
194 pub(crate) fn normalize(&mut self) -> T::Real {
195 let norm = self.norm_frobenius();
196 assert!(norm != T::Real::zero(), "Cannot normalize zero tensor");
197 let inv_norm = T::Real::one() / norm;
198 let data = Arc::make_mut(&mut self.data);
199 for elem in data.iter_mut() {
200 *elem = elem.scale_real(inv_norm);
201 }
202 norm
203 }
204}