diffsol_la/linear_solver/
mod.rs1use crate::{error::LaError, LinearOp, Matrix};
2
3#[cfg(feature = "nalgebra")]
4pub mod nalgebra;
5
6#[cfg(feature = "faer")]
7pub mod faer;
8
9#[cfg(feature = "suitesparse")]
10pub mod suitesparse;
11
12#[cfg(feature = "cuda")]
13#[allow(deprecated)]
14pub mod cuda;
15
16#[cfg(feature = "cuda-oxide")]
17pub mod cuda_oxide;
18
19pub use faer::lu::LU as FaerLU;
20pub use nalgebra::lu::ReusableLU as NalgebraLU;
21
22pub trait LinearSolver<M: Matrix>: Default {
24 fn set_linearisation<C: LinearOp<V = M::V, T = M::T, M = M, C = M::C>>(&mut self, op: &C);
29
30 fn set_sparsity<C: LinearOp<V = M::V, T = M::T, M = M, C = M::C>>(&mut self, op: &C);
35
36 fn solve(&self, b: &M::V) -> Result<M::V, LaError> {
40 let mut b = b.clone();
41 self.solve_in_place(&mut b)?;
42 Ok(b)
43 }
44
45 fn solve_in_place(&self, b: &mut M::V) -> Result<(), LaError>;
46
47 fn solve_transpose_in_place(&self, b: &mut M::V) -> Result<(), LaError>;
51}
52
53#[cfg(test)]
56macro_rules! generate_lu_tests {
57 ($suffix:ident, $M:ty, $LS:ty, $ctx2:expr) => {
58 paste::paste! {
59 #[test]
60 fn [<test_lu_ $suffix>]() {
61 $crate::linear_solver::tests::test_diagonal_lu_solve::<$M, $LS>();
62 }
63 #[test]
64 fn [<test_grouped_lu_ $suffix>]() {
65 $crate::linear_solver::tests::test_grouped_lu_solve::<$M, $LS>($ctx2);
66 }
67 #[test]
68 fn [<test_transpose_lu_ $suffix>]() {
69 $crate::linear_solver::tests::test_transpose_lu_solve::<$M, $LS>($ctx2);
70 }
71 #[test]
72 #[should_panic(expected = "incompatible nbatch")]
73 fn [<test_narrow_state_lu_ $suffix>]() {
74 $crate::linear_solver::tests::test_narrow_state_lu_solve::<$M, $LS>($ctx2);
75 }
76 }
77 };
78}
79#[cfg(test)]
80pub(crate) use generate_lu_tests;
81
82#[cfg(test)]
83pub(crate) mod tests {
84 use super::LinearSolver;
85 use crate::{Context, IndexType, LinearOp, Matrix, Vector};
86
87 pub struct DiagonalOp<M: Matrix> {
89 pub matrix: M,
90 }
91
92 pub fn diagonal_op<M: Matrix>(value: f64) -> DiagonalOp<M> {
94 use num_traits::FromPrimitive;
95 let v = M::T::from_f64(value).unwrap();
96 let diag = M::V::from_vec(vec![v, v], Default::default());
97 DiagonalOp {
98 matrix: M::from_diagonal(&diag),
99 }
100 }
101
102 #[cfg_attr(not(feature = "cuda-oxide"), allow(dead_code))]
104 pub fn diagonal_op_n<M: Matrix>(nstates: IndexType, value: f64, ctx: M::C) -> DiagonalOp<M> {
105 use num_traits::FromPrimitive;
106 let v = M::T::from_f64(value).unwrap();
107 let diag = M::V::from_element(nstates, v, ctx);
108 DiagonalOp {
109 matrix: M::from_diagonal(&diag),
110 }
111 }
112
113 pub fn batched_diagonal_op<M: Matrix>(values: &[f64], ctx: M::C) -> DiagonalOp<M> {
115 use num_traits::FromPrimitive;
116 assert_eq!(values.len(), ctx.nbatch());
117 let diag = M::V::from_vec(
118 values
119 .iter()
120 .flat_map(|&v| [M::T::from_f64(v).unwrap(); 2])
121 .collect(),
122 ctx,
123 );
124 DiagonalOp {
125 matrix: M::from_diagonal(&diag),
126 }
127 }
128
129 pub fn test_diagonal_lu_solve<M: Matrix, LS: LinearSolver<M>>() {
131 use num_traits::FromPrimitive;
132 let f = |x: f64| M::T::from_f64(x).unwrap();
133 let op = diagonal_op::<M>(2.0);
134 let mut s = LS::default();
135 s.set_sparsity(&op);
136 s.set_linearisation(&op);
137 let b = M::V::from_vec(vec![f(2.0), f(4.0)], Default::default());
138 let x = s.solve(&b).unwrap();
139 x.assert_eq_eps(
140 &M::V::from_vec(vec![f(1.0), f(2.0)], Default::default()),
141 100.0,
142 );
143 }
144
145 pub fn test_grouped_lu_solve<M: Matrix, LS: LinearSolver<M>>(ctx2: M::C) {
149 use num_traits::FromPrimitive;
150 let f = |x: f64| M::T::from_f64(x).unwrap();
151 let wide = ctx2.clone_with_nbatch(4).unwrap();
152 let op = batched_diagonal_op::<M>(&[2.0, 5.0], ctx2);
153 let mut s = LS::default();
154 s.set_sparsity(&op);
155 s.set_linearisation(&op);
156 let b = M::V::from_vec((1..=8).map(|i| f(i as f64)).collect(), wide.clone());
157 let x = s.solve(&b).unwrap();
158 let expected = M::V::from_vec(
159 vec![
160 f(0.5),
161 f(1.0),
162 f(1.5),
163 f(2.0),
164 f(1.0),
165 f(1.2),
166 f(1.4),
167 f(1.6),
168 ],
169 wide,
170 );
171 x.assert_eq_eps(&expected, 100.0);
172 }
173
174 pub fn test_transpose_lu_solve<M: Matrix, LS: LinearSolver<M>>(ctx2: M::C) {
178 use num_traits::FromPrimitive;
179 let f = |x: f64| M::T::from_f64(x).unwrap();
180 let wide = ctx2.clone_with_nbatch(4).unwrap();
181 let indices = vec![(0, 1), (0, 2), (1, 0), (1, 1), (2, 0), (2, 2)];
182 let values = [1.0, 2.0, 3.0, 1.0, 1.0, 4.0, 3.0, 1.0, 1.0, 1.0, 2.0, 4.0];
183 let matrix =
184 M::try_from_triplets(3, 3, indices, values.iter().map(|&v| f(v)).collect(), ctx2)
185 .unwrap();
186 let op = DiagonalOp { matrix };
187 let mut s = LS::default();
188 s.set_sparsity(&op);
189 s.set_linearisation(&op);
190 let rhs = |a: [f64; 3], b: [f64; 3]| {
191 M::V::from_vec(
192 [a, a, b, b].iter().flatten().map(|&v| f(v)).collect(),
193 wide.clone(),
194 )
195 };
196 let expected = rhs([1.0, 2.0, 3.0], [1.0, 2.0, 3.0]);
197 let (a_x, at_x) = ([8.0, 5.0, 13.0], [9.0, 3.0, 14.0]);
198
199 let mut b = rhs(a_x, at_x);
200 s.solve_in_place(&mut b).unwrap();
201 b.assert_eq_eps(&expected, 100.0);
202
203 let mut b = rhs(at_x, a_x);
204 s.solve_transpose_in_place(&mut b).unwrap();
205 b.assert_eq_eps(&expected, 100.0);
206 }
207
208 pub fn test_narrow_state_lu_solve<M: Matrix, LS: LinearSolver<M>>(ctx2: M::C) {
211 let wide = ctx2.clone_with_nbatch(4).unwrap();
212 let op = batched_diagonal_op::<M>(&[2.0, 3.0, 4.0, 5.0], wide);
213 let mut s = LS::default();
214 s.set_sparsity(&op);
215 s.set_linearisation(&op);
216 let mut b = M::V::zeros(2, ctx2);
217 s.solve_in_place(&mut b).unwrap();
218 }
219
220 impl<M: Matrix> LinearOp for DiagonalOp<M> {
221 type T = M::T;
222 type V = M::V;
223 type M = M;
224 type C = M::C;
225
226 fn nrows(&self) -> IndexType {
227 self.matrix.nrows()
228 }
229 fn ncols(&self) -> IndexType {
230 self.matrix.ncols()
231 }
232 fn context(&self) -> &Self::C {
233 self.matrix.context()
234 }
235 fn matrix_inplace(&self, y: &mut Self::M) {
236 y.copy_from(&self.matrix);
237 }
238 fn sparsity(&self) -> Option<<Self::M as Matrix>::Sparsity> {
239 self.matrix.sparsity().map(|s| {
240 use crate::matrix::sparsity::MatrixSparsityRef;
241 s.to_owned()
242 })
243 }
244 }
245}