Skip to main content

diffsol_la/matrix/
dense_faer_serial.rs

1use std::ops::{Add, AddAssign, Index, IndexMut, Mul, Sub, SubAssign};
2
3use super::default_solver::DefaultSolver;
4use super::sparsity::{Dense, DenseRef};
5use std::mem::MaybeUninit;
6
7use crate::context::broadcast_batch;
8use crate::error::LaError;
9use crate::{scalar::Scale, Context, FaerScalar, Vector};
10use crate::{
11    DenseMatrix, FaerContext, FaerLU, FaerVec, FaerVecIndex, FaerVecMut, FaerVecRef, Matrix,
12    MatrixCommon, VectorIndex,
13};
14
15use faer::reborrow::{Reborrow, ReborrowMut};
16use faer::{linalg::matmul::matmul, unzip, zip, Accum, Mat, MatMut, MatRef};
17
18/// A batched matrix, stored as `nbatch` side-by-side blocks of `logical_ncols` columns.
19///
20/// Invariant: `data.ncols() == logical_ncols() * context.nbatch()`, so batch `b` column `j`
21/// lives at physical column `col(b, j)`.
22#[derive(Clone, Debug, PartialEq)]
23pub struct FaerMat<T: FaerScalar> {
24    pub(crate) data: Mat<T>,
25    pub(crate) context: FaerContext,
26}
27impl<T: FaerScalar> FaerMat<T> {
28    fn logical_ncols(&self) -> usize {
29        self.data.ncols() / self.context.nbatch()
30    }
31    /// Physical column of logical column `j` in this matrix's own batch `b`.
32    fn col(&self, b: usize, j: usize) -> usize {
33        debug_assert!(b < self.context.nbatch(), "batch out of bounds");
34        b * self.logical_ncols() + j
35    }
36    /// Physical column feeding batch `b` of a `nbatch`-batch destination, broadcasting this
37    /// matrix over contiguous groups of destination batches (see [`broadcast_batch`]).
38    fn col_bcast(&self, b: usize, j: usize, nbatch: usize) -> usize {
39        self.col(broadcast_batch(b, self.context.nbatch(), nbatch), j)
40    }
41}
42/// Scalars of staging buffer `mul_cols_by` uses per row tile. The tile is
43/// `MUL_COLS_TILE / ncols` rows, so a narrow range gets a tall tile and a wide one a short
44/// tile, and either way the staged tile and the destination it writes back stay in L1.
45///
46/// Swept over 64..=2048 against `lin_alg_ops mul_cols_by` (nstates 2..=10000, `ncols` 2 and
47/// 6): 1024 is the best or tied-best at every tall shape and 8% ahead of 512 on
48/// `mul_cols_by/faer/k6/10000`, while 2048 falls off again.  The buffer is uninitialised, so
49/// short vectors no longer pay for the part of it they do not use and the choice is decided
50/// purely by the tall shapes.
51const MUL_COLS_TILE: usize = 1024;
52
53impl<T: FaerScalar> DefaultSolver for FaerMat<T> {
54    type LS = FaerLU<T>;
55}
56
57impl<T: FaerScalar> MatrixCommon for FaerMat<T> {
58    type T = T;
59    type V = FaerVec<T>;
60    type C = FaerContext;
61    type Inner = Mat<T>;
62    fn nrows(&self) -> usize {
63        self.data.nrows()
64    }
65    fn ncols(&self) -> usize {
66        self.logical_ncols()
67    }
68    fn inner(&self) -> &Self::Inner {
69        &self.data
70    }
71}
72/// `self` is the owned operand, so it is the destination -- which makes the in-place op the
73/// whole implementation, broadcast check and whole-matrix fast path included.
74macro_rules! binary_owned_lhs {
75    ($tr:ident, $fn:ident, $assign_tr:ident, $assign:ident, $rhs:ty) => {
76        impl<T: FaerScalar> $tr<$rhs> for FaerMat<T> {
77            type Output = FaerMat<T>;
78
79            fn $fn(mut self, rhs: $rhs) -> Self::Output {
80                $assign_tr::$assign(&mut self, rhs);
81                self
82            }
83        }
84    };
85}
86binary_owned_lhs!(Add, add, AddAssign, add_assign, &FaerMat<T>);
87binary_owned_lhs!(Sub, sub, SubAssign, sub_assign, &FaerMat<T>);
88
89/// `rhs` is the owned operand, so it is the destination.
90///
91/// A commutative op is just the in-place op with the operands swapped.  A non-commutative one
92/// cannot be: `rhs -= lhs` computes `rhs - lhs`, so it writes `combine(&mut rhs_ij, lhs_ij)`
93/// instead, which gets `lhs - rhs` in the same single pass.
94macro_rules! binary_owned_rhs {
95    (commutes, $tr:ident, $fn:ident, $assign_tr:ident, $assign:ident, $lhs:ty, $op:tt,
96     $combine:expr) => {
97        impl<T: FaerScalar> $tr<FaerMat<T>> for $lhs {
98            type Output = FaerMat<T>;
99
100            fn $fn(self, mut rhs: FaerMat<T>) -> Self::Output {
101                $assign_tr::$assign(&mut rhs, self);
102                rhs
103            }
104        }
105    };
106    (noncommutes, $tr:ident, $fn:ident, $assign_tr:ident, $assign:ident, $lhs:ty, $op:tt, $combine:expr) => {
107        impl<T: FaerScalar> $tr<FaerMat<T>> for $lhs {
108            type Output = FaerMat<T>;
109
110            fn $fn(self, mut rhs: FaerMat<T>) -> Self::Output {
111                rhs.context
112                    .assert_broadcastable_into(self.context.nbatch(), stringify!($fn));
113                let nc = self.ncols();
114                let nb = rhs.context.nbatch();
115                for b in 0..nb {
116                    let c = rhs.col(b, 0);
117                    let columns = rhs.data.rb_mut().subcols_mut(c, nc);
118                    zip!(
119                        columns,
120                        self.data.rb().subcols(self.col_bcast(b, 0, nb), nc)
121                    )
122                    .for_each(|unzip!(r, l)| $combine(r, *l));
123                }
124                rhs
125            }
126        }
127    };
128}
129binary_owned_rhs!(
130    commutes, Add, add, AddAssign, add_assign, &FaerMat<T>, +=, |rhs: &mut T, lhs: T| *rhs += lhs
131);
132binary_owned_rhs!(
133    noncommutes, Sub, sub, SubAssign, sub_assign, &FaerMat<T>, -=,
134    |rhs: &mut T, lhs: T| *rhs = lhs - *rhs
135);
136macro_rules! assign {
137    ($tr:ident, $fn:ident, $l:ty, $r:ty, $op:tt) => {
138        impl<T: FaerScalar> $tr<$r> for $l {
139            fn $fn(&mut self, rhs: $r) {
140                self.context
141                    .assert_broadcastable_into(rhs.context.nbatch(), stringify!($fn));
142                let nb = self.context.nbatch();
143                let nc = self.ncols();
144                if nb == rhs.context.nbatch()
145                    && self.data.ncols() == nc * nb
146                    && rhs.data.ncols() == nc * nb
147                {
148                    self.data $op rhs.data.rb();
149                    return;
150                }
151                for b in 0..nb {
152                    let c = self.col(b, 0);
153                    let mut columns = self.data.rb_mut().subcols_mut(c, nc);
154                    columns $op rhs.data.rb().subcols(rhs.col_bcast(b, 0, nb), nc);
155                }
156            }
157        }
158    };
159}
160assign!(AddAssign, add_assign, FaerMat<T>, &FaerMat<T>, +=);
161assign!(SubAssign, sub_assign, FaerMat<T>, &FaerMat<T>, -=);
162impl<T: FaerScalar> Mul<Scale<T>> for FaerMat<T> {
163    type Output = Self;
164    fn mul(mut self, r: Scale<T>) -> Self {
165        self.data *= faer::Scale(r.value());
166        self
167    }
168}
169macro_rules! scale_ref {
170    ($t:ty) => {
171        impl<T: FaerScalar> Mul<Scale<T>> for $t {
172            type Output = FaerMat<T>;
173            fn mul(self, r: Scale<T>) -> Self::Output {
174                // every batch is scaled by the same factor, and the batches tile the columns,
175                // so this is one whole-matrix multiply whatever the batch count
176                FaerMat {
177                    data: self.data.rb() * faer::Scale(r.value()),
178                    context: self.context,
179                }
180            }
181        }
182    };
183}
184scale_ref!(&FaerMat<T>);
185macro_rules! ind {
186    ($t:ty) => {
187        impl<T: FaerScalar> Index<(usize, usize)> for $t {
188            type Output = T;
189            fn index(&self, x: (usize, usize)) -> &T {
190                assert_eq!(
191                    self.context.nbatch(),
192                    1,
193                    "indexing not supported for batched matrices"
194                );
195                &self.data[(x.0, self.col(0, x.1))]
196            }
197        }
198    };
199}
200ind!(FaerMat<T>);
201impl<T: FaerScalar> IndexMut<(usize, usize)> for FaerMat<T> {
202    fn index_mut(&mut self, x: (usize, usize)) -> &mut T {
203        assert_eq!(
204            self.context.nbatch(),
205            1,
206            "indexing not supported for batched matrices"
207        );
208        let c = self.col(0, x.1);
209        &mut self.data[(x.0, c)]
210    }
211}
212
213/// `y_b = a * self_b * x_b + beta * y_b` for every batch of `y`, broadcasting single-batch
214/// operands. `x` is a full vector operand of length `ncols`.
215macro_rules! gemv_data {
216    ($self:ident, $a:ident, $x:ident, $beta:ident, $y:ident, $op:literal) => {{
217        $y.context
218            .assert_broadcastable_into($self.context.nbatch(), $op);
219        $y.context
220            .assert_broadcastable_into($x.context.nbatch(), $op);
221        let nc = $self.ncols();
222        let nb = $y.data.ncols();
223        for b in 0..nb {
224            let mut column = $y.data.rb_mut().col_mut(b);
225            let accum = if $beta.is_zero() {
226                Accum::Replace
227            } else {
228                if !$beta.is_one() {
229                    column *= faer::Scale($beta);
230                }
231                Accum::Add
232            };
233            matmul(
234                column,
235                accum,
236                $self.data.rb().subcols($self.col_bcast(b, 0, nb), nc),
237                $x.data.rb().col($x.batch(b, nb)),
238                $a,
239                $self.context.par,
240            );
241        }
242    }};
243}
244
245/// `y_b = a * self_b[:, start..start + nc] * x + beta * y_b` for every batch of `y`, with `x` a
246/// host coefficient slice shared by all batches. `ColRef::from_slice` wraps it without copying,
247/// so faer's own `matmul` still does the work.
248macro_rules! gemv_cols_data {
249    ($self:ident, $start:expr, $nc:expr, $a:ident, $x:ident, $beta:ident, $y:ident, $op:literal) => {{
250        $y.context
251            .assert_broadcastable_into($self.context.nbatch(), $op);
252        let (start, nc) = ($start, $nc);
253        // only a debug assert: `ncols()` on the owned matrix is a division, and this runs once
254        // per Runge-Kutta stage.  An out-of-range range still panics in release, inside faer's
255        // `subcols`.
256        debug_assert!(
257            start + nc <= $self.ncols(),
258            concat!($op, ": column range out of bounds")
259        );
260        // an empty column range contributes nothing, leaving y = beta * y
261        if nc == 0 {
262            if $beta.is_zero() {
263                $y.fill(T::zero());
264            } else if !$beta.is_one() {
265                for b in 0..$y.data.ncols() {
266                    let mut column = $y.data.rb_mut().col_mut(b);
267                    column *= faer::Scale($beta);
268                }
269            }
270        } else {
271            let x = faer::ColRef::from_slice(&$x[..nc]);
272            let nb = $y.data.ncols();
273            for b in 0..nb {
274                let mut column = $y.data.rb_mut().col_mut(b);
275                let accum = if $beta.is_zero() {
276                    Accum::Replace
277                } else {
278                    if !$beta.is_one() {
279                        column *= faer::Scale($beta);
280                    }
281                    Accum::Add
282                };
283                matmul(
284                    column,
285                    accum,
286                    $self.data.rb().subcols($self.col_bcast(b, start, nb), nc),
287                    x,
288                    $a,
289                    $self.context.par,
290                );
291            }
292        }
293    }};
294}
295
296impl<T: FaerScalar> Matrix for FaerMat<T> {
297    type Sparsity = Dense<Self>;
298    type SparsityRef<'a> = DenseRef<'a, Self>;
299    fn sparsity(&self) -> Option<Self::SparsityRef<'_>> {
300        None
301    }
302    fn context(&self) -> &Self::C {
303        &self.context
304    }
305    fn inner_mut(&mut self) -> &mut Self::Inner {
306        &mut self.data
307    }
308    fn set_data_with_indices(&mut self, dst: &FaerVecIndex, src: &FaerVecIndex, data: &FaerVec<T>) {
309        self.context
310            .assert_broadcastable_into(data.context.nbatch(), "set_data_with_indices");
311        let nb = self.context.nbatch();
312        for (d, s) in dst.data.iter().zip(src.data.iter()) {
313            let i = d % self.nrows();
314            let j = d / self.nrows();
315            for b in 0..nb {
316                let c = self.col(b, j);
317                self.data[(i, c)] = data.data[(*s, data.batch(b, nb))]
318            }
319        }
320    }
321    fn gather(&mut self, o: &Self, idx: &FaerVecIndex) {
322        self.context
323            .assert_broadcastable_into(o.context.nbatch(), "gather");
324        let nb = self.context.nbatch();
325        for b in 0..nb {
326            for (d, s) in idx.data.iter().enumerate() {
327                let i = d % self.nrows();
328                let j = d / self.nrows();
329                let oi = s % o.nrows();
330                let oj = s / o.nrows();
331                let c = self.col(b, j);
332                self.data[(i, c)] = o.data[(oi, o.col_bcast(b, oj, nb))]
333            }
334        }
335    }
336    fn partition_indices_by_zero_diagonal(&self) -> (FaerVecIndex, FaerVecIndex) {
337        let (mut z, mut nz) = (Vec::new(), Vec::new());
338        for i in 0..self.nrows() {
339            if self.data[(i, self.col(0, i))].is_zero() {
340                z.push(i)
341            } else {
342                nz.push(i)
343            }
344        }
345        (
346            FaerVecIndex::from_vec(z, self.context),
347            FaerVecIndex::from_vec(nz, self.context),
348        )
349    }
350    fn add_column_to_vector(&self, j: usize, v: &mut FaerVec<T>) {
351        v.context
352            .assert_broadcastable_into(self.context.nbatch(), "add_column_to_vector");
353        let nb = v.context.nbatch();
354        for b in 0..nb {
355            zip!(
356                v.data.rb_mut().col_mut(b),
357                self.data.rb().col(self.col_bcast(b, j, nb))
358            )
359            .for_each(|unzip!(v, column)| *v += *column);
360        }
361    }
362    fn add_columns_to_batched_vector(&self, v: &mut FaerVec<T>) {
363        assert_eq!(v.len(), self.nrows(), "row count mismatch");
364        assert_eq!(
365            v.context.nbatch(),
366            self.context.nbatch() * self.ncols(),
367            "batch count mismatch: the destination holds one lane per (batch, column)"
368        );
369        // batch `b` column `j` lives at physical column `b * ncols + j`, which is exactly the
370        // destination lane, so the two buffers have the same shape and faer can add them whole
371        v.data += self.data.rb();
372    }
373    fn triplet_iter(
374        &self,
375    ) -> (
376        impl Iterator<Item = (usize, usize)> + '_,
377        impl Iterator<Item = T> + '_,
378    ) {
379        let pos: Vec<_> = (0..self.ncols())
380            .flat_map(|j| (0..self.nrows()).map(move |i| (i, j)))
381            .collect();
382        let val: Vec<_> = (0..self.context.nbatch())
383            .flat_map(|b| {
384                pos.iter()
385                    .map(move |&(i, j)| self.data[(i, self.col(b, j))])
386            })
387            .collect();
388        (pos.into_iter(), val.into_iter())
389    }
390    fn try_from_triplets(
391        nr: usize,
392        nc: usize,
393        idx: Vec<(usize, usize)>,
394        v: Vec<T>,
395        ctx: Self::C,
396    ) -> Result<Self, LaError> {
397        assert_eq!(v.len(), idx.len() * ctx.nbatch());
398        let mut out = Self::zeros(nr, nc, ctx);
399        for b in 0..ctx.nbatch() {
400            for (k, &(i, j)) in idx.iter().enumerate() {
401                let c = out.col(b, j);
402                out.data[(i, c)] = v[b * idx.len() + k]
403            }
404        }
405        Ok(out)
406    }
407    fn zeros(nr: usize, nc: usize, ctx: Self::C) -> Self {
408        Self {
409            data: Mat::zeros(nr, nc * ctx.nbatch()),
410            context: ctx,
411        }
412    }
413    fn from_diagonal(v: &FaerVec<T>) -> Self {
414        let mut out = Self::zeros(v.len(), v.len(), v.context);
415        let nc = out.ncols();
416        for b in 0..v.context.nbatch() {
417            let c = out.col(b, 0);
418            out.data
419                .rb_mut()
420                .subcols_mut(c, nc)
421                .diagonal_mut()
422                .column_vector_mut()
423                .copy_from(v.data.rb().col(b));
424        }
425        out
426    }
427    fn gemv(&self, a: T, x: &FaerVec<T>, beta: T, y: &mut FaerVec<T>) {
428        gemv_data!(self, a, x, beta, y, "gemv")
429    }
430    fn copy_from(&mut self, o: &Self) {
431        self.context
432            .assert_broadcastable_into(o.context.nbatch(), "copy_from");
433        if self.context.nbatch() == o.context.nbatch() {
434            self.data.rb_mut().copy_from(o.data.rb());
435            return;
436        }
437        let nc = self.ncols();
438        let nb = self.context.nbatch();
439        for b in 0..nb {
440            let c = self.col(b, 0);
441            self.data
442                .rb_mut()
443                .subcols_mut(c, nc)
444                .copy_from(o.data.rb().subcols(o.col_bcast(b, 0, nb), nc));
445        }
446    }
447    fn set_column(&mut self, j: usize, v: &FaerVec<T>) {
448        self.context
449            .assert_broadcastable_into(v.context.nbatch(), "set_column");
450        let nb = self.context.nbatch();
451        for b in 0..nb {
452            let c = self.col(b, j);
453            self.data
454                .rb_mut()
455                .col_mut(c)
456                .copy_from(v.data.rb().col(v.batch(b, nb)));
457        }
458    }
459    fn scale_add_and_assign(&mut self, x: &Self, b: T, y: &Self) {
460        self.context
461            .assert_broadcastable_into(x.context.nbatch(), "scale_add_and_assign");
462        self.context
463            .assert_broadcastable_into(y.context.nbatch(), "scale_add_and_assign");
464        let nc = self.ncols();
465        let nb = self.context.nbatch();
466        for batch in 0..nb {
467            let c = self.col(batch, 0);
468            zip!(
469                self.data.rb_mut().subcols_mut(c, nc),
470                x.data.rb().subcols(x.col_bcast(batch, 0, nb), nc),
471                y.data.rb().subcols(y.col_bcast(batch, 0, nb), nc)
472            )
473            .for_each(|unzip!(s, x, y)| *s = *x + b * *y);
474        }
475    }
476    fn new_from_sparsity(nr: usize, nc: usize, _: Option<Self::Sparsity>, ctx: Self::C) -> Self {
477        Self::zeros(nr, nc, ctx)
478    }
479}
480impl<T: FaerScalar> DenseMatrix for FaerMat<T> {
481    fn fill(&mut self, value: T) {
482        self.data.rb_mut().fill(value);
483    }
484    fn resize_cols(&mut self, nc: usize) {
485        let old = self.ncols();
486        if old == nc {
487            return;
488        }
489        let keep = old.min(nc);
490        let mut d = Mat::zeros(self.nrows(), nc * self.context.nbatch());
491        for b in 0..self.context.nbatch() {
492            d.rb_mut()
493                .subcols_mut(b * nc, keep)
494                .copy_from(self.data.rb().subcols(self.col(b, 0), keep));
495        }
496        self.data = d
497    }
498    fn get_index(&self, i: usize, j: usize) -> T {
499        assert_eq!(
500            self.context.nbatch(),
501            1,
502            "get_index not supported for batched matrices"
503        );
504        self.data[(i, self.col(0, j))]
505    }
506    fn gemv_cols(&self, start: usize, end: usize, alpha: T, x: &[T], beta: T, y: &mut FaerVec<T>) {
507        assert!(start <= end, "gemv_cols: column range start > end");
508        assert!(
509            end - start <= crate::matrix::MAX_SMALL_COLS,
510            "gemv_cols: column range exceeds MAX_SMALL_COLS"
511        );
512        assert!(
513            x.len() >= end - start,
514            "gemv_cols: x must hold at least end - start values"
515        );
516        gemv_cols_data!(self, start, end - start, alpha, x, beta, y, "gemv_cols")
517    }
518    fn from_vec(nr: usize, nc: usize, d: Vec<T>, ctx: Self::C) -> Self {
519        assert_eq!(d.len(), nr * nc * ctx.nbatch());
520        Self {
521            data: Mat::from_fn(nr, nc * ctx.nbatch(), |i, j| d[j * nr + i]),
522            context: ctx,
523        }
524    }
525    fn column_mut(&mut self, i: usize) -> FaerVecMut<'_, T> {
526        let nc = self.ncols();
527        assert!(i < nc, "column index out of bounds");
528        if self.context.nbatch() == 1 {
529            return FaerVecMut {
530                data: self.data.rb_mut().subcols_mut(i, 1),
531                context: self.context,
532            };
533        }
534        let (nr, nb) = (self.nrows(), self.context.nbatch());
535        // column i of every batch: batch b holds it at physical column b * nc + i, so the
536        // batches are `nc` columns apart in the underlying (padded) storage
537        let stride = self.data.col_stride() * nc as isize;
538        let data = unsafe {
539            MatMut::from_raw_parts_mut(
540                self.data.rb_mut().ptr_at_mut(0, i),
541                nr,
542                nb,
543                self.data.row_stride(),
544                stride,
545            )
546        };
547        FaerVecMut {
548            data,
549            context: self.context,
550        }
551    }
552    fn set_index(&mut self, i: usize, j: usize, v: T) {
553        assert_eq!(
554            self.context.nbatch(),
555            1,
556            "set_index not supported for batched matrices"
557        );
558        let c = self.col(0, j);
559        self.data[(i, c)] = v;
560    }
561    fn set_index_batch(&mut self, batch: usize, i: usize, j: usize, value: T) {
562        let c = self.col(batch, j);
563        self.data[(i, c)] = value;
564    }
565    fn column(&self, i: usize) -> FaerVecRef<'_, T> {
566        let nc = self.ncols();
567        assert!(i < nc, "column index out of bounds");
568        if self.context.nbatch() == 1 {
569            return FaerVecRef {
570                data: self.data.rb().subcols(i, 1),
571                context: self.context,
572            };
573        }
574        let (nr, nb) = (self.nrows(), self.context.nbatch());
575        // column i of every batch: batch b holds it at physical column b * nc + i, so the
576        // batches are `nc` columns apart in the underlying (padded) storage
577        let stride = self.data.col_stride() * nc as isize;
578        let data = unsafe {
579            MatRef::from_raw_parts(
580                self.data.rb().ptr_at(0, i),
581                nr,
582                nb,
583                self.data.row_stride(),
584                stride,
585            )
586        };
587        FaerVecRef {
588            data,
589            context: self.context,
590        }
591    }
592    fn mul_cols_by(&mut self, ncols: usize, rhs: &[T]) {
593        assert!(
594            ncols <= self.logical_ncols(),
595            "mul_cols_by: column range out of bounds"
596        );
597        assert_eq!(
598            rhs.len(),
599            ncols * ncols,
600            "mul_cols_by: rhs must hold ncols * ncols values"
601        );
602        if ncols == 0 {
603            return;
604        }
605        assert!(
606            ncols <= crate::matrix::MAX_SMALL_COLS,
607            "mul_cols_by: ncols exceeds MAX_SMALL_COLS"
608        );
609        let nrows = self.nrows();
610        let nbatch = self.context.nbatch();
611        // A column is contiguous, so a tile of consecutive rows is a contiguous run of each
612        // column: stage the tile's old values, then overwrite in place. Tall for a narrow
613        // range, short for a wide one, so the tile always fits the same buffer.
614        let tile = (MUL_COLS_TILE / ncols).max(1);
615        // uninitialised on purpose: the staging loop below writes every element that the
616        // accumulate loop reads, and zeroing the whole buffer costs more than the entire
617        // operation for the two- and three-state systems the ODE solvers spend their time in
618        let mut buf: [MaybeUninit<T>; MUL_COLS_TILE] =
619            [const { MaybeUninit::uninit() }; MUL_COLS_TILE];
620        for b in 0..nbatch {
621            let base = self.col(b, 0);
622            let mut row = 0;
623            while row < nrows {
624                let rows = tile.min(nrows - row);
625                for j in 0..ncols {
626                    // a column is contiguous whatever the column stride, so the tile is one
627                    // run and stages as a single copy
628                    let col = self.data.rb().col(base + j);
629                    let src = &col.try_as_col_major().unwrap().as_slice()[row..][..rows];
630                    // SAFETY: `MaybeUninit<T>` is layout-compatible with `T`, and
631                    // `j * rows + rows <= rows * ncols <= MUL_COLS_TILE` (see below), so the
632                    // destination run is in bounds; the two buffers cannot overlap.
633                    unsafe {
634                        std::ptr::copy_nonoverlapping(
635                            src.as_ptr(),
636                            buf.as_mut_ptr().add(j * rows) as *mut T,
637                            rows,
638                        );
639                    }
640                }
641                // SAFETY: the loop above initialised `buf[..rows * ncols]`, and
642                // `rows * ncols <= tile * ncols <= MUL_COLS_TILE` -- the `.max(1)` branch
643                // cannot be taken because `ncols <= MAX_SMALL_COLS`, asserted above.
644                // `MaybeUninit<T>` is layout-compatible with `T`.
645                let buf =
646                    unsafe { &*(&buf[..rows * ncols] as *const [MaybeUninit<T>] as *const [T]) };
647                for j in 0..ncols {
648                    let w = &rhs[j * ncols..][..ncols];
649                    let mut dst = self.data.rb_mut().col_mut(base + j);
650                    // accumulate column by column so every access is a contiguous run
651                    for i in 0..rows {
652                        dst[row + i] = buf[i] * w[0];
653                    }
654                    for l in 1..ncols {
655                        let src = &buf[l * rows..][..rows];
656                        for i in 0..rows {
657                            dst[row + i] += src[i] * w[l];
658                        }
659                    }
660                }
661                row += rows;
662            }
663        }
664    }
665
666    fn update_backward_diff(&mut self, order: usize, d: &FaerVec<T>) {
667        assert!(order + 2 < self.logical_ncols(), "order out of bounds");
668        self.context
669            .assert_broadcastable_into(d.context.nbatch(), "update_backward_diff");
670        let nb = self.context.nbatch();
671        for b in 0..nb {
672            let base = self.col(b, 0);
673            let d_col = d.batch(b, nb);
674            // diff[:, order+2] = d - diff[:, order+1]
675            {
676                let src = base + order + 1;
677                let dst = base + order + 2;
678                let (left, right) = self.data.rb_mut().split_at_col_mut(dst);
679                zip!(right.col_mut(0), d.data.rb().col(d_col), left.col(src))
680                    .for_each(|unzip!(out, dval, sval)| *out = *dval - *sval);
681            }
682            // for i in (0..=order+1).rev(): diff[:, i] += diff[:, i+1]
683            for i in (0..=order + 1).rev() {
684                let dst = base + i;
685                let src = base + i + 1;
686                let (left, right) = self.data.rb_mut().split_at_col_mut(src);
687                zip!(left.col_mut(dst), right.col(0)).for_each(|unzip!(d, s)| *d += *s);
688            }
689        }
690    }
691}
692
693#[cfg(test)]
694mod tests {
695    use super::*;
696
697    #[test]
698    fn test_update_backward_diff() {
699        super::super::tests::test_update_backward_diff::<FaerMat<f64>>();
700    }
701
702    #[test]
703    fn test_index_operator() {
704        let mut mat = FaerMat::from_vec(2, 2, vec![1.0, 3.0, 2.0, 4.0], FaerContext::default());
705        assert_eq!(mat[(1, 0)], 3.0);
706        mat[(1, 0)] = 30.0;
707        assert_eq!(mat.get_index(1, 0), 30.0);
708    }
709
710    #[test]
711    fn test_partition_indices_by_zero_diagonal() {
712        super::super::tests::test_partition_indices_by_zero_diagonal::<FaerMat<f64>>();
713    }
714
715    #[test]
716    fn test_resize_cols() {
717        super::super::tests::test_resize_cols::<FaerMat<f64>>();
718    }
719
720    #[test]
721    fn test_owned_rhs() {
722        super::super::tests::test_owned_rhs_m::<FaerMat<f64>>();
723    }
724
725    #[test]
726    fn test_batched_owned_rhs_broadcast() {
727        super::super::tests::test_batched_owned_rhs_broadcast_m::<FaerMat<f64>>(
728            FaerContext::with_nbatch(2),
729        );
730    }
731
732    #[test]
733    #[should_panic(expected = "incompatible nbatch")]
734    fn test_batched_owned_rhs_narrow() {
735        super::super::tests::test_batched_owned_rhs_narrow_m::<FaerMat<f64>>(
736            FaerContext::with_nbatch(2),
737        );
738    }
739
740    super::super::generate_matrix_tests_nonbatched!(faer, FaerMat<f64>);
741    super::super::generate_matrix_tests_nonbatched!(faer_f32, FaerMat<f32>);
742    super::super::generate_matrix_tests_batched!(
743        faer,
744        FaerMat<f64>,
745        FaerContext::default(),
746        FaerContext::with_nbatch(2)
747    );
748    super::super::generate_matrix_tests_batched!(
749        faer_f32,
750        FaerMat<f32>,
751        FaerContext::default(),
752        FaerContext::with_nbatch(2)
753    );
754    super::super::generate_dense_matrix_tests_nonbatched!(faer, FaerMat<f64>);
755    super::super::generate_dense_matrix_tests_nonbatched!(faer_f32, FaerMat<f32>);
756    super::super::generate_dense_matrix_tests_batched!(
757        faer,
758        FaerMat<f64>,
759        FaerContext::default(),
760        FaerContext::with_nbatch(2)
761    );
762    super::super::generate_dense_matrix_tests_batched!(
763        faer_f32,
764        FaerMat<f32>,
765        FaerContext::default(),
766        FaerContext::with_nbatch(2)
767    );
768}