1use nalgebra::linalg::{gauss_step, gauss_step_swap};
2use nalgebra::{Dim, Dyn, Matrix as NaMatrix, StorageMut};
3
4use crate::context::broadcast_batch;
5use crate::{
6 error::LaError, linear_solver_error, matrix::dense_nalgebra_serial::NalgebraMat, Context,
7 LinearOp, LinearSolver, Matrix, NalgebraContext, NalgebraScalar, NalgebraVec,
8};
9
10struct BatchLu<T: NalgebraScalar> {
20 a: NalgebraMat<T>,
21 piv: Vec<Vec<(usize, usize)>>,
24}
25
26impl<T: NalgebraScalar> BatchLu<T> {
27 fn new(a: NalgebraMat<T>) -> Self {
28 let nbatch = a.context.nbatch();
29 Self {
30 a,
31 piv: vec![Vec::new(); nbatch],
32 }
33 }
34
35 fn nbatch(&self) -> usize {
36 self.piv.len()
37 }
38
39 fn logical_ncols(&self) -> usize {
40 self.a.data.ncols() / self.nbatch()
41 }
42
43 fn factor(&mut self) {
47 let ncols = self.logical_ncols();
48 for batch in 0..self.nbatch() {
49 let piv = &mut self.piv[batch];
50 piv.clear();
51 let mut block = self.a.data.columns_mut(batch * ncols, ncols);
52 for i in 0..ncols {
53 let p = block.view_range(i.., i).icamax() + i;
54 let diag = block[(p, i)];
55 if diag.is_zero() {
56 continue;
57 }
58 if p != i {
59 piv.push((i, p));
60 block.columns_range_mut(..i).swap_rows(i, p);
61 gauss_step_swap(&mut block, diag, i, p);
62 } else {
63 gauss_step(&mut block, diag, i);
64 }
65 }
66 }
67 }
68
69 fn solve_block<C2: Dim, S2: StorageMut<T, Dyn, C2>>(
72 &self,
73 batch: usize,
74 b: &mut NaMatrix<T, Dyn, C2, S2>,
75 ) -> bool {
76 let ncols = self.logical_ncols();
77 let block = self.a.data.columns(batch * ncols, ncols);
78 for &(i, p) in &self.piv[batch] {
79 b.swap_rows(i, p);
80 }
81 let _ = block.solve_lower_triangular_with_diag_mut(b, T::one());
82 block.solve_upper_triangular_mut(b)
83 }
84
85 fn solve_transpose_block<C2: Dim, S2: StorageMut<T, Dyn, C2>>(
87 &self,
88 batch: usize,
89 b: &mut NaMatrix<T, Dyn, C2, S2>,
90 ) -> bool {
91 let ncols = self.logical_ncols();
92 let block = self.a.data.columns(batch * ncols, ncols);
93 if !block.tr_solve_upper_triangular_mut(b) {
94 return false;
95 }
96 for c in 0..b.ncols() {
98 for i in (0..ncols.saturating_sub(1)).rev() {
99 let s = block.view_range(i + 1.., i).dot(&b.view_range(i + 1.., c));
100 b[(i, c)] -= s;
101 }
102 }
103 for &(i, p) in self.piv[batch].iter().rev() {
104 b.swap_rows(i, p);
105 }
106 true
107 }
108
109 fn solve(&self, state: &mut NalgebraVec<T>, transpose: bool) -> Result<(), LaError> {
110 state
111 .context
112 .assert_broadcastable_into(self.nbatch(), "lu_solve");
113 let nb = state.context.nbatch();
114 for batch in 0..nb {
115 let lu_batch = broadcast_batch(batch, self.nbatch(), nb);
116 let mut state_batch = state.data.column_mut(batch);
117 let ok = if transpose {
118 self.solve_transpose_block(lu_batch, &mut state_batch)
119 } else {
120 self.solve_block(lu_batch, &mut state_batch)
121 };
122 if !ok {
123 return Err(linear_solver_error!(LuSolveFailed));
124 }
125 }
126 Ok(())
127 }
128}
129
130pub struct ReusableLU<T>
134where
135 T: NalgebraScalar,
136{
137 lu: Option<BatchLu<T>>,
138}
139
140impl<T: NalgebraScalar> Default for ReusableLU<T> {
141 fn default() -> Self {
142 Self { lu: None }
143 }
144}
145
146impl<T: NalgebraScalar> LinearSolver<NalgebraMat<T>> for ReusableLU<T> {
147 fn solve_in_place(&self, state: &mut NalgebraVec<T>) -> Result<(), LaError> {
148 let Some(lu) = self.lu.as_ref() else {
149 return Err(linear_solver_error!(LuNotInitialized));
150 };
151 lu.solve(state, false)
152 }
153
154 fn solve_transpose_in_place(&self, state: &mut NalgebraVec<T>) -> Result<(), LaError> {
155 let Some(lu) = self.lu.as_ref() else {
156 return Err(linear_solver_error!(LuNotInitialized));
157 };
158 lu.solve(state, true)
159 }
160
161 fn set_linearisation<
162 C: LinearOp<T = T, V = NalgebraVec<T>, M = NalgebraMat<T>, C = NalgebraContext>,
163 >(
164 &mut self,
165 op: &C,
166 ) {
167 let lu = self.lu.as_mut().expect("Matrix not set");
168 op.matrix_inplace(&mut lu.a);
169 lu.factor();
170 }
171
172 fn set_sparsity<
173 C: LinearOp<T = T, V = NalgebraVec<T>, M = NalgebraMat<T>, C = NalgebraContext>,
174 >(
175 &mut self,
176 op: &C,
177 ) {
178 let ncols = op.ncols();
179 let nrows = op.nrows();
180 let a = C::M::new_from_sparsity(nrows, ncols, op.sparsity(), *op.context());
181 self.lu = Some(BatchLu::new(a));
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 crate::linear_solver::generate_lu_tests!(
190 f64,
191 NalgebraMat<f64>,
192 ReusableLU<f64>,
193 NalgebraContext::with_nbatch(2)
194 );
195 crate::linear_solver::generate_lu_tests!(
196 f32,
197 NalgebraMat<f32>,
198 ReusableLU<f32>,
199 NalgebraContext::with_nbatch(2)
200 );
201
202 fn make_mat(n: usize, data: &[f64]) -> NalgebraMat<f64> {
204 let mut m: NalgebraMat<f64> = Matrix::new_from_sparsity(n, n, None, Default::default());
205 for i in 0..n {
206 for j in 0..n {
207 m[(i, j)] = data[i * n + j];
208 }
209 }
210 m
211 }
212
213 #[test]
217 fn matches_nalgebra_lu() {
218 let cases: Vec<(usize, Vec<f64>)> = vec![
219 (1, vec![2.0]),
220 (2, vec![0.0, 1.0, 1.0, 1.0]),
221 (3, vec![2.0, 1.0, 1.0, 4.0, 3.0, 3.0, 8.0, 7.0, 9.0]),
222 (
223 5,
224 vec![
225 2.0, 0.0, 1.0, 0.0, 0.0, 1.0, 3.0, 0.0, 0.0, 1.0, 0.0, 1.0, 4.0, 1.0, 0.0, 0.0,
226 0.0, 2.0, 5.0, 1.0, 1.0, 0.0, 0.0, 1.0, 6.0,
227 ],
228 ),
229 ];
230 for (n, data) in cases {
231 let nm = make_mat(n, &data);
232 let b = nalgebra::DMatrix::<f64>::from_fn(n, 1, |i, _| (i + 1) as f64);
233
234 let mut lu = BatchLu::new(nm.clone());
235 lu.factor();
236 let mut got = b.clone();
237 assert!(lu.solve_block(0, &mut got));
238
239 let m = nalgebra::DMatrix::from_row_slice(n, n, &data);
240 let expected_lu = m.lu();
241 let mut expected = b.clone();
242 assert!(expected_lu.solve_mut(&mut expected));
243
244 for i in 0..n {
245 assert!(
246 (got[(i, 0)] - expected[(i, 0)]).abs() < 1e-9,
247 "n={n} row={i}: got {} expected {}",
248 got[(i, 0)],
249 expected[(i, 0)]
250 );
251 }
252
253 lu.a = make_mat(n, &data);
258 lu.factor();
259 let mut got2 = b.clone();
260 assert!(lu.solve_block(0, &mut got2));
261 for i in 0..n {
262 assert!((got2[(i, 0)] - expected[(i, 0)]).abs() < 1e-9);
263 }
264 }
265 }
266
267 #[test]
268 fn singular_matrix_fails_like_nalgebra_lu() {
269 let nm = make_mat(2, &[1.0, 2.0, 2.0, 4.0]);
270 let mut lu = BatchLu::new(nm);
271 lu.factor();
272 let mut b = nalgebra::DMatrix::<f64>::from_row_slice(2, 1, &[1.0, 2.0]);
273 assert!(!lu.solve_block(0, &mut b));
274 }
275}