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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
//! Lazy normalized design matrices.
//!
//! A [`LazyMatrix`] wraps an underlying (typically sparse) matrix `X` together
//! with optional column **centers** `c` and column **scales** `s`, and presents
//! the *normalized* matrix
//!
//! ```text
//! X̃ = (X − 1cᵀ) S⁻¹, S = diag(s)
//! ```
//!
//! as a linear operator — **without ever materializing `X − 1cᵀ`**. Centering a
//! sparse matrix turns its structural zeros into nonzeros, destroying sparsity;
//! factoring the normalization into the matrix–vector products avoids that:
//!
//! ```text
//! X̃ v = X (S⁻¹ v) − 1 · (cᵀ S⁻¹ v)
//! X̃ᵀ u = S⁻¹ (Xᵀ u − c · Σu)
//! ```
//!
//! Both centering and scaling are independently optional, giving the four
//! combinations handled by the `if let Some` branches in the operator impls.
//!
//! # Eager normalization
//!
//! [`LazyMatrix::to_eager`] explicitly materializes normalized dense data from
//! a dense, sparse, or Zarr input. The caller chooses the destination backend.
//! [`LazyMatrix::to_eager_into`] fills existing dense storage and returns an
//! [`EagerMatrix`] borrowing it. [`LazyMatrix::into_eager`] consumes writable
//! dense data and normalizes it in its existing allocation.
//!
//! Eager operations use the already normalized backend data. Original fitted
//! centers and scales remain available through [`EagerMatrix::normalization`]
//! and can be reused with [`LazyMatrix::from_normalization`] for prediction.
//! Conversion does not recompute statistics. Sparse and Zarr materialization
//! requires O(nrows * ncols) dense output storage; the lazy path remains the
//! default. Arithmetic order changes, so rounding and nonfinite product results
//! can differ from lazy products.
//!
//! ```
//! use lazymatrix::{ColumnStats, EagerMatrix, LazyMatrix, MaterializeDense,
//! MatrixOwned, Normalization};
//!
//! fn eager_design<M, D>(x: &M, spec: Normalization)
//! -> Result<EagerMatrix<D>, M::Error>
//! where
//! M: ColumnStats<f64> + MaterializeDense<f64>,
//! D: MatrixOwned<f64>,
//! {
//! LazyMatrix::new(x, spec)?.to_eager()
//! }
//! ```
//!
//! # Backends
//!
//! The core is generic over the backend matrix `M` and scalar `F` and pulls in
//! no linear-algebra dependency by itself. Concrete implementations are provided
//! behind feature flags:
//!
//! * `faer` — `faer::Mat` and `faer::sparse::SparseColMat` over `faer::Col`.
//! * `nalgebra` — `nalgebra::DMatrix` and `nalgebra_sparse::CscMatrix` over
//! `nalgebra::DVector`.
//! * `ndarray` — `ndarray::Array2` and borrowed, strided matrix views over
//! `ndarray::Array1`.
//! * `sprs` — CSC and CSR `sprs::CsMat` matrices and borrowed views over `Vec`.
//! Supports `Array1` vectors from every enabled ndarray release.
//! `SprsCsc` checks CSC orientation for borrowed columns; `SprsCsr` checks
//! CSR orientation for borrowed rows.
//! * `zarrs` — synchronous chunked `ZarrMatrix` arrays over `Vec`, with
//! fallible products and statistics. Supports `f32` and `f64`.
//! * `parallel` — parallel column statistics through Rayon; also enables the
//! enabled faer releases' Rayon support.
//!
//! Unversioned features select the newest supported release. Use a versioned
//! feature to stay on a particular release line:
//!
//! | Backend | Versioned features | Unversioned feature selects |
//! | --- | --- | --- |
//! | faer | `faer_v0_22`, `faer_v0_23`, `faer_v0_24` | 0.24 |
//! | nalgebra | `nalgebra_v0_32`, `nalgebra_v0_33`, `nalgebra_v0_34`, `nalgebra_v0_35` | 0.35 |
//! | ndarray | `ndarray_v0_15`, `ndarray_v0_16`, `ndarray_v0_17` | 0.17 |
//! | sprs | `sprs_v0_11` | 0.11 |
//! | zarrs | `zarrs_v0_22` | 0.22 |
//!
//! If Cargo enables several releases of one backend, every enabled release
//! receives its own trait implementations. Another dependency enabling a newer
//! adapter does not remove support for existing types. Select a matching version
//! feature for each direct backend dependency. The `*_all` features are internal
//! markers and cannot be enabled without a version feature.
//!
//! The core requires Rust 1.85. Other backends retain their dependency MSRVs. The `nalgebra` and
//! `nalgebra_v0_35` features require Rust 1.89. The `nalgebra` feature previously
//! selected 0.34; use `nalgebra_v0_34` to retain that release and Rust 1.87 support.
//! The sprs backend supports Rust 1.85 with sprs 0.11.4, as locked in this
//! repository. sprs 0.11.5 requires Rust 1.88.
//!
//! Any type implementing the [`traits`] surface (a dense matrix, say) works too.
//!
//! # Example
//!
//! ```ignore
//! use lazymatrix::{LazyMatrix, MatVec, Normalization, Centering, Scaling};
//!
//! // `x` is some backend matrix implementing `MatVec`, `MatTransposeVec`,
//! // `ColumnStats`, `MatrixShape`; `v` a backend vector.
//! let spec = Normalization::new(Centering::Mean, Scaling::Sd);
//! let lazy = LazyMatrix::new(x, spec).unwrap();
//! let y = lazy.matvec(&v).unwrap(); // == ((X − 1cᵀ)S⁻¹) v, sparsity preserved
//! ```
//!
//! With any ndarray version feature, a matrix view borrows the original array:
//!
//! ```
//! # #[cfg(feature = "ndarray_all")]
//! # {
//! # #[cfg(all(feature = "ndarray_v0_15", not(any(feature = "ndarray_v0_16", feature = "ndarray_v0_17"))))]
//! # use ndarray_0_15 as ndarray;
//! # #[cfg(all(feature = "ndarray_v0_16", not(feature = "ndarray_v0_17")))]
//! # use ndarray_0_16 as ndarray;
//! use lazymatrix::{Centering, LazyMatrix, MatVec, Normalization, Scaling};
//! use ndarray::array;
//!
//! let x = array![[1.0, 0.0], [2.0, 3.0], [0.0, 4.0]];
//! let lazy = LazyMatrix::new(
//! x.view(),
//! Normalization::new(Centering::Mean, Scaling::Sd),
//! ).unwrap();
//! let y = lazy.matvec(&array![1.0, -1.0]).unwrap();
//! assert_eq!(y.len(), 3);
//! # }
//! ```
//!
//! Allocating ndarray products use owned `Array1` vectors. Reusable-output
//! products also support mutable, strided destinations. Lazy forward products
//! require a clonable, mutable input, so convert immutable input views with
//! `to_owned()` first. Logical-column operations and reusable transpose products
//! accept immutable vector views directly. Raw columns borrow in O(1) time;
//! dense logical-column operations take O(nrows) time.
//!
//! [`MatVecScaledInto`] and [`MatTransposeVecScaledInto`] fuse a product with
//! output scaling for all supported matrix backends, including normalized
//! [`LazyMatrix`] and [`WithIntercept`] wrappers. Exact zero `alpha` skips the
//! product; exact zero `beta` ignores previous output values. Scaling a normalized
//! forward product needs O(ncols) coefficient scratch. The optional
//! [`LazyMatrix::matvec_scaled_with_workspace`] and
//! [`LazyMatrix::mat_transpose_vec_scaled_with_workspace`] methods let callers
//! reuse that storage across calls.
//! [`WithIntercept::matvec_scaled_with_workspace`] and
//! [`WithIntercept::mat_transpose_vec_scaled_with_workspace`] reuse the wrapper's
//! predictor buffer, whose length excludes the intercept. Inner normalization
//! may still allocate its own scratch. Fusing an operation alone does not
//! guarantee fewer allocations or faster products; see the consumer benchmarks.
//!
//! sprs products and statistics work directly on either CSC or CSR storage.
//! They visit stored entries without copying or materializing a normalized
//! matrix. CSC statistics take O(ncols + nnz) time and support `parallel`;
//! CSR statistics scan rows serially in O(nrows + ncols + nnz) time using
//! O(ncols) workspace. `sprs` does not select an ndarray backend version.
//!
//! To borrow columns, pass CSC storage or a view to `SprsCsc::try_new` before
//! constructing a `LazyMatrix`. The wrapper checks orientation in O(1) time
//! without copying, and returns CSR inputs unchanged as `Err`. Logical columns
//! accept any sprs index type; `SparseColumns` requires `usize` row indices.
//! Raw columns borrow in O(1) time. A centered column dot takes
//! O(nrows + nnz_column), or O(nnz_column) with `dot_with_sum`.
//!
//! [`SparseRows`] borrows raw column-index and value slices from CSR storage in
//! O(1) time, preserving explicitly stored zeros. It is available for faer's
//! `SparseRowMat`, `SparseRowMatRef`, and `SparseRowMatMut` with `usize` indices,
//! and nalgebra-sparse's `CsrMatrix`. These CSR types provide shape and borrowed
//! row access; their operator and column-statistics implementations remain future
//! work. With sprs, wrap a CSR matrix or view in `SprsCsr::try_new`. The wrapper
//! returns CSC inputs unchanged as `Err` and forwards existing products and
//! statistics. Borrowing rows requires `usize` column indices, while pointer
//! indices may use any supported width. The slices describe the original
//! matrix, before normalization.
//!
//! [`LazyMatrix::row`] requires [`SparseRows`] and returns a borrowed [`LazyRow`]
//! in O(1) time without allocation. The view exposes raw storage and the full
//! optional center and scale slices. Its logical length is the number of columns,
//! even for an empty raw row. Centering generally makes the logical row dense:
//! [`LazyRow::implicit_value`] exposes the background at a column, and
//! [`LazyRow::stored_corrections`] iterates over the scaled raw entries.
//! # An implicit intercept
//!
//! [`WithIntercept`] represents `[1, predictors]`. Normalize predictors first,
//! then wrap them so the intercept remains one. It also accepts unnormalized
//! operators and borrowed inputs. Coefficient zero is always the intercept.
//! Products retain backend errors and use coefficient scratch without allocating
//! a column of ones. Fitting and coefficient transformations stay downstream.
//!
//! ```
//! # #[cfg(feature = "ndarray_all")]
//! # {
//! # #[cfg(all(feature = "ndarray_v0_15", not(any(feature = "ndarray_v0_16", feature = "ndarray_v0_17"))))]
//! # use ndarray_0_15 as ndarray;
//! # #[cfg(all(feature = "ndarray_v0_16", not(feature = "ndarray_v0_17")))]
//! # use ndarray_0_16 as ndarray;
//! use lazymatrix::{LazyMatrix, MatVec, WithIntercept};
//! use ndarray::array;
//!
//! let x = array![[1.0], [3.0]];
//! let predictors = LazyMatrix::with_centers(x.view(), vec![2.0]);
//! let design = WithIntercept::new(&predictors);
//! assert_eq!(design.matvec(&array![3.0, 2.0]).unwrap(), array![1.0, 5.0]);
//! # }
//! ```
//!
//! Intercept Gram products require [`WeightedGramInto`] and
//! [`WeightedColumnSumsInto`] on the predictors. Cross terms use a separate pass
//! with direct centering, preserving the existing Gram kernels' numerical policy.
//! [`WeightedColumnSumsKernel`] supplies explicit backend normalization, and
//! [`VectorOwned`] supplies owned scratch compatible with backend vector views.
//!
//! # Operational errors and out-of-core storage
//!
//! [`WeightedGramInto`] computes `Aᵀ diag(weights) A` for dense ndarray and
//! `usize`-index CSC inputs from faer, nalgebra, and `SprsCsc` (when enabled),
//! as well as CSR inputs through `SprsCsr`. CSR kernels use bounded row panels
//! with O(nrows × ncols²) arithmetic and no storage conversion.
//! [`MatrixWrite`] destinations include owned and mutable-view dense matrices
//! from ndarray, faer, and nalgebra. The output backend is independent of the
//! input backend. Both triangles are overwritten, and weights can be signed,
//! zero, or nonfinite. Kernels center values before accumulation, avoiding
//! cancellation from subtracting large raw moments. Bounded dense panels or
//! sparse working vectors avoid materializing the full logical design matrix.
//! See `examples/weighted_gram.rs` for a borrowed-input demonstration.
//!
//! Products, [`ColumnStats`] methods, and [`LazyMatrix::new`] return `Result`.
//! [`MatrixErrorType`] gives each backend one shared error type. In-memory
//! backends use [`std::convert::Infallible`]; storage backends propagate read
//! and decoding errors. Dimension mismatches still panic. After a failed
//! reusable-output product, discard the partial output or overwrite it with a
//! successful product. Borrowed views and explicit normalization parameters do
//! not require I/O and retain their infallible APIs.
//!
//! [`LazyMatrix::from_parts`] and [`LazyMatrix::with_scales`] panic on explicit
//! zero scales, including negative zero. Explicit parameters are otherwise
//! preserved unchanged, including negative scales and nonfinite centers or
//! scales. Computed normalization through [`LazyMatrix::new`] replaces exact
//! zero scales with one while preserving nonfinite statistics.
//!
//! With `zarrs`, `ZarrMatrix` wraps an opened synchronous array without reading
//! its chunks. Each product scans chunks serially, and normalization shares work
//! through [`ColumnStats::normalization_stats`] to need at most two scans. Working
//! vectors stay in RAM. Memory for data and codecs depends on chunk size, including
//! the outer shard for sharded storage; no strict byte budget is imposed. The
//! backing array must remain unchanged throughout normalization and use.
//! Filesystem and gzip support are enabled; additional codecs can be selected
//! through a direct zarrs dependency. No ndarray backend is selected by `zarrs`.
//!
//! ```
//! # #[cfg(feature = "zarrs_all")]
//! # {
//! use std::sync::Arc;
//! use lazymatrix::{Centering, LazyMatrix, MatVec, Normalization, Scaling, ZarrMatrix};
//! use zarrs::{array::{ArrayBuilder, DataType}, storage::store::MemoryStore};
//!
//! let array = ArrayBuilder::new(vec![3, 2], vec![2, 2], DataType::Float64, 1.0f64)
//! .build(Arc::new(MemoryStore::new()), "/matrix")?;
//! let matrix = ZarrMatrix::<_, f64>::try_new(array)?;
//! let lazy = LazyMatrix::new(matrix, Normalization::new(Centering::Mean, Scaling::Sd))?;
//! assert_eq!(lazy.matvec(&vec![1.0, 2.0])?, vec![0.0; 3]);
//! # }
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! Borrowed ndarray views can also wrap memory-mapped `.npy` data. The
//! `ndarray_mmap` example uses ndarray 0.17 and a private, immutable backing file.
//! The `zarrs_chunked` example creates a filesystem array chunk by chunk. Both
//! accept row and column counts and print normalization and product timings.
// Each enabled release keeps its own crate identity and implementations.
extern crate faer_0_22;
extern crate faer_traits_0_22;
extern crate faer_0_23;
extern crate faer_traits_0_23;
extern crate faer as faer_0_24;
extern crate faer_traits as faer_traits_0_24;
extern crate nalgebra_0_32;
extern crate nalgebra_sparse_0_9;
extern crate nalgebra_0_33;
extern crate nalgebra_sparse_0_10;
extern crate nalgebra_0_34;
extern crate nalgebra_sparse_0_11;
extern crate nalgebra as nalgebra_0_35;
extern crate nalgebra_sparse as nalgebra_sparse_0_12;
extern crate ndarray_0_15;
extern crate ndarray_0_16;
extern crate ndarray as ndarray_0_17;
extern crate sprs;
extern crate zarrs;
compile_error!;
compile_error!;
compile_error!;
compile_error!;
compile_error!;
pub use ;
pub use ;
pub use ;
pub use EagerMatrix;
pub use WithIntercept;
pub use LazyMatrix;
pub use ;
pub use LazyRow;
pub use ;