ariadnetor_tensor/block_sparse/storage.rs
1//! `BlockSparseStorage<T>`: pure-data half of the block-sparse tensor split.
2//!
3//! Carries only the packed flat element buffer. Block metadata,
4//! sector indices, flux, shape, and memory order live on the paired
5//! [`BlockSparseLayout<S>`](crate::BlockSparseLayout); the wrapper
6//! [`BlockSparseTensorData<T, S>`](crate::BlockSparseTensorData) joins
7//! the two.
8//!
9//! `T: Scalar`-bound scalar-only data operations (`stored_len`,
10//! `norm`, `norm_frobenius`, `normalize`) live here because their
11//! bodies touch only `data`; they require no layout field, no
12//! symmetry sector, and no compute backend.
13
14use std::sync::Arc;
15
16use aligned_vec::{AVec, ConstAlign};
17use num_traits::{Float, One, Zero};
18
19use crate::{Sector, Storage, StorageFor};
20
21/// Pure-data half of the block-sparse tensor split.
22///
23/// Holds a 64-byte-aligned packed buffer of allowed-block elements
24/// with Arc-based shared ownership (Copy-on-Write via
25/// [`Arc::make_mut`]). The interpretation (which slab maps to which
26/// block coordinate) lives on
27/// [`BlockSparseLayout<S>`](crate::BlockSparseLayout); the
28/// `BlockSparseTensorData<T, S>` wrapper joins them.
29///
30/// Notice that `BlockSparseStorage<T>` is sector-agnostic — the
31/// symmetry sector parameter `S` lives only on the layout, not on
32/// the storage.
33pub struct BlockSparseStorage<T> {
34 data: Arc<AVec<T, ConstAlign<64>>>,
35}
36
37// Manual Clone: Arc::clone does not require T: Clone (same pattern as DenseStorage<T>).
38impl<T> Clone for BlockSparseStorage<T> {
39 fn clone(&self) -> Self {
40 Self {
41 data: Arc::clone(&self.data),
42 }
43 }
44}
45
46impl<T> BlockSparseStorage<T> {
47 /// Construct from a `Vec<T>`, internally rebuilding into a
48 /// 64-byte-aligned buffer.
49 pub fn new(data: Vec<T>) -> Self
50 where
51 T: Clone,
52 {
53 let aligned = AVec::from_slice(64, &data);
54 Self {
55 data: Arc::new(aligned),
56 }
57 }
58
59 /// Construct from an already-aligned `AVec` (zero-copy).
60 pub(crate) fn from_aligned(data: AVec<T, ConstAlign<64>>) -> Self {
61 Self {
62 data: Arc::new(data),
63 }
64 }
65
66 /// Reference to the packed flat buffer.
67 pub fn data(&self) -> &[T] {
68 &self.data[..]
69 }
70
71 /// Mutable reference to the packed flat buffer (triggers CoW if
72 /// shared).
73 pub fn data_mut(&mut self) -> &mut [T]
74 where
75 T: Clone,
76 {
77 Arc::make_mut(&mut self.data).as_mut_slice()
78 }
79
80 /// Mutable access to the underlying `Arc` (for CoW-aware paths).
81 pub(crate) fn arc_mut(&mut self) -> &mut Arc<AVec<T, ConstAlign<64>>> {
82 &mut self.data
83 }
84}
85
86impl<T> Storage for BlockSparseStorage<T> {
87 type Element = T;
88
89 fn flat_len(&self) -> usize {
90 self.data.len()
91 }
92}
93
94impl<T, S: Sector> StorageFor<crate::BlockSparseLayout<S>> for BlockSparseStorage<T> {}
95
96// ---------------------------------------------------------------------------
97// Scalar-only data operations
98//
99// These read only `self.data` (no block metadata, no flux, no backend),
100// so they live on the storage half. The joined-form
101// `BlockSparseTensorData` re-exposes them via thin forwarders.
102// ---------------------------------------------------------------------------
103
104impl<T> BlockSparseStorage<T>
105where
106 T: Clone,
107{
108 /// Scale every stored element by a scalar factor in place
109 /// (triggers CoW if shared).
110 pub(crate) fn scale<S>(&mut self, factor: S)
111 where
112 T: std::ops::Mul<S, Output = T>,
113 S: Clone,
114 {
115 let data = Arc::make_mut(&mut self.data).as_mut_slice();
116 for elem in data.iter_mut() {
117 *elem = elem.clone() * factor.clone();
118 }
119 }
120}
121
122impl<T> BlockSparseStorage<T>
123where
124 T: ariadnetor_core::Scalar,
125{
126 /// Total number of stored elements across all blocks (= length of
127 /// the flat packed buffer).
128 pub(crate) fn stored_len(&self) -> usize {
129 self.data.len()
130 }
131
132 /// Squared Frobenius norm: Σ |element|².
133 fn norm_squared(&self) -> T::Real {
134 self.data
135 .iter()
136 .map(|&x| {
137 let a = x.abs();
138 a * a
139 })
140 .fold(T::Real::zero(), |acc, x| acc + x)
141 }
142
143 /// Frobenius norm: √(Σ |element|²).
144 pub(crate) fn norm_frobenius(&self) -> T::Real {
145 self.norm_squared().sqrt()
146 }
147
148 /// Frobenius norm (alias for [`norm_frobenius`](Self::norm_frobenius)).
149 pub(crate) fn norm(&self) -> T::Real {
150 self.norm_frobenius()
151 }
152
153 /// Normalize to unit Frobenius norm (in-place).
154 ///
155 /// Returns the norm before normalization.
156 /// Panics if the tensor has zero norm.
157 pub(crate) fn normalize(&mut self) -> T::Real {
158 let norm = self.norm_frobenius();
159 assert!(norm != T::Real::zero(), "Cannot normalize zero tensor");
160 let inv_norm = T::Real::one() / norm;
161 let data = Arc::make_mut(&mut self.data);
162 for elem in data.iter_mut() {
163 *elem = elem.scale_real(inv_norm);
164 }
165 norm
166 }
167}