1use core::fmt::Debug;
26
27use dyn_stack::{MemStack, StackReq};
28use faer::{
29 Conj, MatMut, MatRef, Par,
30 linalg::solvers::{ShapeCore, SolveCore},
31 matrix_free::{BiLinOp, BiPrecond, LinOp, Precond},
32};
33use faer_traits::ComplexField;
34
35#[derive(Debug, Clone)]
40pub struct SolvePrecond<S> {
41 solver: S,
42}
43
44impl<S> SolvePrecond<S> {
45 pub fn new(solver: S) -> Self {
47 Self { solver }
48 }
49
50 pub fn inner(&self) -> &S {
52 &self.solver
53 }
54
55 pub fn into_inner(self) -> S {
57 self.solver
58 }
59}
60
61impl<T, S> LinOp<T> for SolvePrecond<S>
62where
63 T: ComplexField,
64 S: ShapeCore + SolveCore<T> + Sync + Debug,
65{
66 fn apply_scratch(&self, _rhs_ncols: usize, _par: Par) -> StackReq {
67 StackReq::EMPTY
68 }
69
70 fn nrows(&self) -> usize {
71 self.solver.nrows()
72 }
73
74 fn ncols(&self) -> usize {
75 self.solver.ncols()
76 }
77
78 fn apply(&self, mut out: MatMut<'_, T>, rhs: MatRef<'_, T>, _par: Par, _stack: &mut MemStack) {
79 assert_eq!(
80 rhs.nrows(),
81 self.ncols(),
82 "rhs row count must match operator input dimension"
83 );
84 assert_eq!(
85 out.nrows(),
86 self.nrows(),
87 "out row count must match operator output dimension"
88 );
89 assert_eq!(
90 out.ncols(),
91 rhs.ncols(),
92 "out and rhs must have the same number of columns"
93 );
94
95 out.copy_from(rhs);
96 self.solver.solve_in_place_with_conj(Conj::No, out);
97 }
98
99 fn conj_apply(
100 &self,
101 mut out: MatMut<'_, T>,
102 rhs: MatRef<'_, T>,
103 _par: Par,
104 _stack: &mut MemStack,
105 ) {
106 assert_eq!(
107 rhs.nrows(),
108 self.ncols(),
109 "rhs row count must match operator input dimension"
110 );
111 assert_eq!(
112 out.nrows(),
113 self.nrows(),
114 "out row count must match operator output dimension"
115 );
116 assert_eq!(
117 out.ncols(),
118 rhs.ncols(),
119 "out and rhs must have the same number of columns"
120 );
121
122 out.copy_from(rhs);
123 self.solver.solve_in_place_with_conj(Conj::Yes, out);
124 }
125}
126
127impl<T, S> Precond<T> for SolvePrecond<S>
128where
129 T: ComplexField,
130 S: ShapeCore + SolveCore<T> + Sync + Debug,
131{
132 fn apply_in_place_scratch(&self, _rhs_ncols: usize, _par: Par) -> StackReq {
133 StackReq::EMPTY
134 }
135
136 fn apply_in_place(&self, rhs: MatMut<'_, T>, _par: Par, _stack: &mut MemStack) {
137 self.solver.solve_in_place_with_conj(Conj::No, rhs);
138 }
139
140 fn conj_apply_in_place(&self, rhs: MatMut<'_, T>, _par: Par, _stack: &mut MemStack) {
141 self.solver.solve_in_place_with_conj(Conj::Yes, rhs);
142 }
143}
144
145impl<T, S> BiLinOp<T> for SolvePrecond<S>
146where
147 T: ComplexField,
148 S: ShapeCore + SolveCore<T> + Sync + Debug,
149{
150 fn transpose_apply_scratch(&self, _rhs_ncols: usize, _par: Par) -> StackReq {
151 StackReq::EMPTY
152 }
153
154 fn transpose_apply(
155 &self,
156 mut out: MatMut<'_, T>,
157 rhs: MatRef<'_, T>,
158 _par: Par,
159 _stack: &mut MemStack,
160 ) {
161 assert_eq!(
162 rhs.nrows(),
163 self.ncols(),
164 "rhs row count must match operator input dimension"
165 );
166 assert_eq!(
167 out.nrows(),
168 self.nrows(),
169 "out row count must match operator output dimension"
170 );
171 assert_eq!(
172 out.ncols(),
173 rhs.ncols(),
174 "out and rhs must have the same number of columns"
175 );
176
177 out.copy_from(rhs);
178 self.solver
179 .solve_transpose_in_place_with_conj(Conj::No, out);
180 }
181
182 fn adjoint_apply(
183 &self,
184 mut out: MatMut<'_, T>,
185 rhs: MatRef<'_, T>,
186 _par: Par,
187 _stack: &mut MemStack,
188 ) {
189 assert_eq!(
190 rhs.nrows(),
191 self.ncols(),
192 "rhs row count must match operator input dimension"
193 );
194 assert_eq!(
195 out.nrows(),
196 self.nrows(),
197 "out row count must match operator output dimension"
198 );
199 assert_eq!(
200 out.ncols(),
201 rhs.ncols(),
202 "out and rhs must have the same number of columns"
203 );
204
205 out.copy_from(rhs);
206 self.solver
207 .solve_transpose_in_place_with_conj(Conj::Yes, out);
208 }
209}
210
211impl<T, S> BiPrecond<T> for SolvePrecond<S>
212where
213 T: ComplexField,
214 S: ShapeCore + SolveCore<T> + Sync + Debug,
215{
216 fn transpose_apply_in_place_scratch(&self, _rhs_ncols: usize, _par: Par) -> StackReq {
217 StackReq::EMPTY
218 }
219
220 fn transpose_apply_in_place(&self, rhs: MatMut<'_, T>, _par: Par, _stack: &mut MemStack) {
221 self.solver
222 .solve_transpose_in_place_with_conj(Conj::No, rhs);
223 }
224
225 fn adjoint_apply_in_place(&self, rhs: MatMut<'_, T>, _par: Par, _stack: &mut MemStack) {
226 self.solver
227 .solve_transpose_in_place_with_conj(Conj::Yes, rhs);
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use core::mem::MaybeUninit;
234
235 use super::*;
236 use faer::{
237 Mat, MatRef, Side,
238 linalg::solvers::Llt,
239 mat,
240 matrix_free::{BiLinOp, BiPrecond, LinOp, Precond},
241 };
242
243 fn with_stack(req: StackReq, f: impl FnOnce(&mut MemStack)) {
244 let nbytes = req.unaligned_bytes_required().max(1);
245 let mut buf = vec![MaybeUninit::<u8>::uninit(); nbytes].into_boxed_slice();
246 f(MemStack::new(&mut buf));
247 }
248
249 fn assert_close(lhs: MatRef<'_, f64>, rhs: MatRef<'_, f64>, tol: f64) {
250 assert_eq!(lhs.nrows(), rhs.nrows());
251 assert_eq!(lhs.ncols(), rhs.ncols());
252
253 for j in 0..lhs.ncols() {
254 for i in 0..lhs.nrows() {
255 let diff = (*lhs.get(i, j) - *rhs.get(i, j)).abs();
256 assert!(
257 diff <= tol,
258 "mismatch at ({i}, {j}): lhs={}, rhs={}, diff={diff}",
259 *lhs.get(i, j),
260 *rhs.get(i, j),
261 );
262 }
263 }
264 }
265
266 fn test_solver() -> SolvePrecond<Llt<f64>> {
267 let a = mat![[4.0, 1.0], [1.0, 3.0f64],];
268 let llt = Llt::new(a.as_ref(), Side::Lower).expect("matrix should be SPD");
269 SolvePrecond::new(llt)
270 }
271
272 #[test]
273 fn exposes_dimensions() {
274 let pc = test_solver();
275 assert_eq!(pc.nrows(), 2);
276 assert_eq!(pc.ncols(), 2);
277 }
278
279 #[test]
280 fn apply_solves_system() {
281 let pc = test_solver();
282 let rhs = mat![[1.0], [2.0f64],];
283 let mut out = Mat::<f64>::zeros(2, 1);
284
285 with_stack(pc.apply_scratch(rhs.ncols(), Par::Seq), |stack| {
286 pc.apply(out.as_mut(), rhs.as_ref(), Par::Seq, stack);
287 });
288
289 let expected = mat![[1.0 / 11.0], [7.0 / 11.0f64],];
290 assert_close(out.as_ref(), expected.as_ref(), 1e-12);
291 }
292
293 #[test]
294 fn apply_in_place_matches_apply() {
295 let pc = test_solver();
296 let rhs = mat![[1.0], [2.0f64],];
297
298 let mut out = Mat::<f64>::zeros(2, 1);
299 with_stack(pc.apply_scratch(rhs.ncols(), Par::Seq), |stack| {
300 pc.apply(out.as_mut(), rhs.as_ref(), Par::Seq, stack);
301 });
302
303 let mut inplace = rhs.to_owned();
304 with_stack(
305 pc.apply_in_place_scratch(inplace.ncols(), Par::Seq),
306 |stack| {
307 pc.apply_in_place(inplace.as_mut(), Par::Seq, stack);
308 },
309 );
310
311 assert_close(out.as_ref(), inplace.as_ref(), 1e-12);
312 }
313
314 #[test]
315 fn transpose_apply_is_usable() {
316 let pc = test_solver();
317 let rhs = mat![[1.0], [2.0f64],];
318 let mut out = Mat::<f64>::zeros(2, 1);
319
320 with_stack(pc.transpose_apply_scratch(rhs.ncols(), Par::Seq), |stack| {
321 pc.transpose_apply(out.as_mut(), rhs.as_ref(), Par::Seq, stack);
322 });
323
324 let expected = mat![[1.0 / 11.0], [7.0 / 11.0f64],];
325 assert_close(out.as_ref(), expected.as_ref(), 1e-12);
326 }
327
328 #[test]
329 fn adjoint_apply_is_usable() {
330 let pc = test_solver();
331 let rhs = mat![[1.0], [2.0f64],];
332 let mut out = Mat::<f64>::zeros(2, 1);
333
334 with_stack(pc.transpose_apply_scratch(rhs.ncols(), Par::Seq), |stack| {
335 pc.adjoint_apply(out.as_mut(), rhs.as_ref(), Par::Seq, stack);
336 });
337
338 let expected = mat![[1.0 / 11.0], [7.0 / 11.0f64],];
339 assert_close(out.as_ref(), expected.as_ref(), 1e-12);
340 }
341
342 #[test]
343 fn transpose_and_adjoint_in_place_are_usable() {
344 let pc = test_solver();
345
346 let mut rhs_t = mat![[1.0], [2.0f64],];
347 with_stack(
348 pc.transpose_apply_in_place_scratch(rhs_t.ncols(), Par::Seq),
349 |stack| {
350 pc.transpose_apply_in_place(rhs_t.as_mut(), Par::Seq, stack);
351 },
352 );
353
354 let mut rhs_h = mat![[1.0], [2.0f64],];
355 with_stack(
356 pc.transpose_apply_in_place_scratch(rhs_h.ncols(), Par::Seq),
357 |stack| {
358 pc.adjoint_apply_in_place(rhs_h.as_mut(), Par::Seq, stack);
359 },
360 );
361
362 let expected = mat![[1.0 / 11.0], [7.0 / 11.0f64],];
363 assert_close(rhs_t.as_ref(), expected.as_ref(), 1e-12);
364 assert_close(rhs_h.as_ref(), expected.as_ref(), 1e-12);
365 }
366
367 #[test]
368 fn inner_accessors_work() {
369 let pc = test_solver();
370 assert_eq!(pc.inner().nrows(), 2);
371
372 let pc2 = test_solver();
373 let solver = pc2.into_inner();
374 assert_eq!(solver.nrows(), 2);
375 }
376}