1use num_complex::Complex64;
5
6use crate::KluSolver;
7use crate::csc::CscMatrix;
8use crate::error::{SparseError, SparseResult};
9
10#[derive(Debug, Clone, Copy)]
11struct SourceEntry {
12 top_left: usize,
13 top_right: usize,
14 bottom_left: usize,
15 bottom_right: usize,
16}
17
18type RealExpansion = (Vec<usize>, Vec<usize>, Vec<SourceEntry>, Vec<f64>);
19
20pub struct ComplexKluSolver {
22 n: usize,
23 source_col_ptrs: Vec<usize>,
24 source_row_indices: Vec<usize>,
25 klu: KluSolver,
26 entries: Vec<SourceEntry>,
27 values_buf: Vec<f64>,
28 real_rhs_buf: Vec<f64>,
29}
30
31impl ComplexKluSolver {
32 pub fn new(matrix: &CscMatrix<Complex64>) -> SparseResult<Self> {
33 if !matrix.is_square() {
34 return Err(SparseError::MatrixNotSquare {
35 nrows: matrix.nrows(),
36 ncols: matrix.ncols(),
37 });
38 }
39 let n = matrix.nrows();
40 if n == 0 {
41 return Err(SparseError::EmptyMatrix);
42 }
43
44 let n2 = checked_mul(n, 2, "real-expanded dimension")?;
45 let (col_ptrs, row_indices, entries, values_buf) = build_real_expansion(matrix)?;
46 let mut klu = KluSolver::new(n2, &col_ptrs, &row_indices)?;
47 klu.factor(&values_buf)?;
48
49 Ok(Self {
50 n,
51 source_col_ptrs: matrix.col_ptrs().to_vec(),
52 source_row_indices: matrix.row_indices().to_vec(),
53 klu,
54 entries,
55 values_buf,
56 real_rhs_buf: vec![0.0; n2],
57 })
58 }
59
60 pub fn factor(&mut self, matrix: &CscMatrix<Complex64>) -> SparseResult<()> {
61 self.update_values(matrix)?;
62 self.klu.factor(&self.values_buf)
63 }
64
65 pub fn refactor(&mut self, matrix: &CscMatrix<Complex64>) -> SparseResult<()> {
66 self.update_values(matrix)?;
67 match self.klu.refactor(&self.values_buf) {
68 Ok(()) => Ok(()),
69 Err(SparseError::KluRefactorFailed) => self.klu.factor(&self.values_buf),
70 Err(error) => Err(error),
71 }
72 }
73
74 pub fn solve(&mut self, rhs: &[Complex64]) -> SparseResult<Vec<Complex64>> {
75 let mut solution = rhs.to_vec();
76 self.solve_in_place(&mut solution)?;
77 Ok(solution)
78 }
79
80 pub fn solve_in_place(&mut self, rhs: &mut [Complex64]) -> SparseResult<()> {
81 if rhs.len() != self.n {
82 return Err(SparseError::RhsLengthMismatch {
83 expected: self.n,
84 found: rhs.len(),
85 });
86 }
87
88 for (index, value) in rhs.iter().enumerate() {
89 self.real_rhs_buf[index] = value.re;
90 self.real_rhs_buf[index + self.n] = value.im;
91 }
92 self.klu.solve(&mut self.real_rhs_buf)?;
93
94 for (index, value) in rhs.iter_mut().enumerate() {
95 *value = Complex64::new(self.real_rhs_buf[index], self.real_rhs_buf[index + self.n]);
96 }
97 Ok(())
98 }
99
100 pub fn dim(&self) -> usize {
101 self.n
102 }
103
104 pub fn rcond(&self) -> f64 {
105 self.klu.rcond()
106 }
107
108 fn update_values(&mut self, matrix: &CscMatrix<Complex64>) -> SparseResult<()> {
109 if !matrix.has_same_pattern_slices(&self.source_col_ptrs, &self.source_row_indices) {
110 return Err(SparseError::PatternMismatch);
111 }
112
113 let values = matrix.values();
114 for (source_index, entry) in self.entries.iter().enumerate() {
115 let value = values[source_index];
116 self.values_buf[entry.top_left] = value.re;
117 self.values_buf[entry.top_right] = -value.im;
118 self.values_buf[entry.bottom_left] = value.im;
119 self.values_buf[entry.bottom_right] = value.re;
120 }
121 Ok(())
122 }
123}
124
125fn build_real_expansion(source: &CscMatrix<Complex64>) -> SparseResult<RealExpansion> {
126 let n = source.nrows();
127 let source_nnz = source.nnz();
128 let n2 = checked_mul(n, 2, "real-expanded dimension")?;
129 let total_nnz = checked_mul(source_nnz, 4, "real-expanded nnz")?;
130 let col_ptr_len = checked_add(n2, 1, "real-expanded column-pointer length")?;
131
132 let mut col_ptrs = vec![0usize; col_ptr_len];
133 let mut row_indices = Vec::with_capacity(total_nnz);
134 let mut entries = Vec::with_capacity(total_nnz);
135 let mut values = Vec::with_capacity(total_nnz);
136
137 entries.resize(
138 source_nnz,
139 SourceEntry {
140 top_left: 0,
141 top_right: 0,
142 bottom_left: 0,
143 bottom_right: 0,
144 },
145 );
146
147 let mut nnz = 0usize;
148 for (col, window) in source.col_ptrs().windows(2).enumerate() {
149 let start = window[0];
150 let end = window[1];
151 col_ptrs[col] = nnz;
152
153 for ((entry, &row), &value) in entries[start..end]
154 .iter_mut()
155 .zip(source.row_indices()[start..end].iter())
156 .zip(source.values()[start..end].iter())
157 {
158 row_indices.push(row);
159 entry.top_left = nnz;
160 values.push(value.re);
161 nnz += 1;
162 }
163 for ((entry, &row), &value) in entries[start..end]
164 .iter_mut()
165 .zip(source.row_indices()[start..end].iter())
166 .zip(source.values()[start..end].iter())
167 {
168 row_indices.push(row + n);
169 entry.bottom_left = nnz;
170 values.push(value.im);
171 nnz += 1;
172 }
173 col_ptrs[col + 1] = nnz;
174 }
175
176 for (col, window) in source.col_ptrs().windows(2).enumerate() {
177 let start = window[0];
178 let end = window[1];
179 let real_col = col + n;
180 col_ptrs[real_col] = nnz;
181
182 for ((entry, &row), &value) in entries[start..end]
183 .iter_mut()
184 .zip(source.row_indices()[start..end].iter())
185 .zip(source.values()[start..end].iter())
186 {
187 row_indices.push(row);
188 entry.top_right = nnz;
189 values.push(-value.im);
190 nnz += 1;
191 }
192 for ((entry, &row), &value) in entries[start..end]
193 .iter_mut()
194 .zip(source.row_indices()[start..end].iter())
195 .zip(source.values()[start..end].iter())
196 {
197 row_indices.push(row + n);
198 entry.bottom_right = nnz;
199 values.push(value.re);
200 nnz += 1;
201 }
202 col_ptrs[real_col + 1] = nnz;
203 }
204
205 Ok((col_ptrs, row_indices, entries, values))
206}
207
208fn checked_mul(lhs: usize, rhs: usize, what: &'static str) -> SparseResult<usize> {
209 lhs.checked_mul(rhs)
210 .ok_or(SparseError::SizeOverflow { what })
211}
212
213fn checked_add(lhs: usize, rhs: usize, what: &'static str) -> SparseResult<usize> {
214 lhs.checked_add(rhs)
215 .ok_or(SparseError::SizeOverflow { what })
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use crate::CscMatrix;
222
223 fn complex_csc(
224 n: usize,
225 col_ptrs: Vec<usize>,
226 row_indices: Vec<usize>,
227 values: Vec<Complex64>,
228 ) -> CscMatrix<Complex64> {
229 CscMatrix::try_new(n, n, col_ptrs, row_indices, values).unwrap()
230 }
231
232 #[test]
233 fn rejects_pattern_changes() {
234 let mat = complex_csc(1, vec![0, 1], vec![0], vec![Complex64::new(1.0, 2.0)]);
235 let mut solver = ComplexKluSolver::new(&mat).expect("valid factorization");
236
237 let changed = complex_csc(1, vec![0, 1], vec![0], vec![Complex64::new(2.0, 3.0)]);
238 solver
239 .refactor(&changed)
240 .expect("value-only change should be allowed");
241
242 let changed_pattern = complex_csc(
243 2,
244 vec![0, 2, 4],
245 vec![0, 1, 0, 1],
246 vec![
247 Complex64::new(1.0, 0.0),
248 Complex64::new(-1.0, 0.0),
249 Complex64::new(-1.0, 0.0),
250 Complex64::new(1.0, 0.0),
251 ],
252 );
253
254 let error = solver
255 .refactor(&changed_pattern)
256 .expect_err("pattern change must be rejected");
257 assert_eq!(error, SparseError::PatternMismatch);
258 }
259
260 #[test]
261 fn solve_in_place_reuses_caller_buffer() {
262 let mat = complex_csc(1, vec![0, 1], vec![0], vec![Complex64::new(2.0, 0.0)]);
263 let mut solver = ComplexKluSolver::new(&mat).expect("valid factorization");
264 let mut rhs = vec![Complex64::new(4.0, 0.0)];
265 solver
266 .solve_in_place(&mut rhs)
267 .expect("solve_in_place should succeed");
268 assert_eq!(rhs, vec![Complex64::new(2.0, 0.0)]);
269 }
270
271 #[test]
272 fn complex_klu_zero_rhs_returns_zero() {
273 let mat = complex_csc(
274 2,
275 vec![0, 2, 4],
276 vec![0, 1, 0, 1],
277 vec![
278 Complex64::new(4.0, -1.0),
279 Complex64::new(-1.0, 0.0),
280 Complex64::new(-1.0, 0.0),
281 Complex64::new(4.0, -1.0),
282 ],
283 );
284 let mut solver = ComplexKluSolver::new(&mat).unwrap();
285
286 let result = solver
287 .solve(&[Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0)])
288 .unwrap();
289 for val in &result {
290 assert!(val.re.abs() < 1e-14, "re should be ~0, got {}", val.re);
291 assert!(val.im.abs() < 1e-14, "im should be ~0, got {}", val.im);
292 }
293 }
294
295 #[test]
296 fn complex_klu_pure_real_rhs() {
297 let mat = complex_csc(
298 2,
299 vec![0, 2, 4],
300 vec![0, 1, 0, 1],
301 vec![
302 Complex64::new(2.0, 0.0),
303 Complex64::new(1.0, 0.0),
304 Complex64::new(1.0, 0.0),
305 Complex64::new(3.0, 0.0),
306 ],
307 );
308 let mut solver = ComplexKluSolver::new(&mat).unwrap();
309
310 let result = solver
311 .solve(&[Complex64::new(3.0, 0.0), Complex64::new(4.0, 0.0)])
312 .unwrap();
313 assert!((result[0].re - 1.0).abs() < 1e-12);
314 assert!((result[1].re - 1.0).abs() < 1e-12);
315 assert!(result[0].im.abs() < 1e-14);
316 assert!(result[1].im.abs() < 1e-14);
317 }
318
319 #[test]
320 fn complex_klu_dim_and_rcond() {
321 let mat = complex_csc(
322 3,
323 vec![0, 1, 2, 3],
324 vec![0, 1, 2],
325 vec![
326 Complex64::new(1.0, 0.0),
327 Complex64::new(2.0, 0.0),
328 Complex64::new(3.0, 0.0),
329 ],
330 );
331 let solver = ComplexKluSolver::new(&mat).unwrap();
332
333 assert_eq!(solver.dim(), 3);
334 assert!(solver.rcond() > 0.0);
335 assert!(solver.rcond() <= 1.0);
336 }
337
338 #[test]
339 fn complex_klu_full_complex_solve() {
340 let mat = complex_csc(
341 2,
342 vec![0, 1, 2],
343 vec![0, 1],
344 vec![Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)],
345 );
346 let mut solver = ComplexKluSolver::new(&mat).unwrap();
347
348 let result = solver
349 .solve(&[Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)])
350 .unwrap();
351 assert!((result[0].re - 1.0).abs() < 1e-12);
352 assert!(result[0].im.abs() < 1e-12);
353 assert!((result[1].re - 1.0).abs() < 1e-12);
354 assert!(result[1].im.abs() < 1e-12);
355 }
356
357 #[test]
358 fn complex_klu_pure_imaginary_rhs() {
359 let mat = complex_csc(
360 2,
361 vec![0, 1, 2],
362 vec![0, 1],
363 vec![Complex64::new(2.0, 0.0), Complex64::new(4.0, 0.0)],
364 );
365 let mut solver = ComplexKluSolver::new(&mat).unwrap();
366
367 let result = solver
368 .solve(&[Complex64::new(0.0, 2.0), Complex64::new(0.0, 8.0)])
369 .unwrap();
370 assert!(result[0].re.abs() < 1e-14);
371 assert!((result[0].im - 1.0).abs() < 1e-12);
372 assert!(result[1].re.abs() < 1e-14);
373 assert!((result[1].im - 2.0).abs() < 1e-12);
374 }
375
376 #[test]
377 fn complex_klu_wrong_rhs_length() {
378 let mat = complex_csc(
379 2,
380 vec![0, 1, 2],
381 vec![0, 1],
382 vec![Complex64::new(1.0, 0.0), Complex64::new(1.0, 0.0)],
383 );
384 let mut solver = ComplexKluSolver::new(&mat).unwrap();
385
386 let err = solver.solve(&[Complex64::new(1.0, 0.0)]).unwrap_err();
387 assert!(matches!(err, SparseError::RhsLengthMismatch { .. }));
388 }
389
390 #[test]
391 fn complex_klu_factor_refactor() {
392 let mat1 = complex_csc(1, vec![0, 1], vec![0], vec![Complex64::new(2.0, 0.0)]);
393 let mut solver = ComplexKluSolver::new(&mat1).unwrap();
394
395 let result = solver.solve(&[Complex64::new(6.0, 0.0)]).unwrap();
396 assert!((result[0].re - 3.0).abs() < 1e-12);
397
398 let mat2 = complex_csc(1, vec![0, 1], vec![0], vec![Complex64::new(3.0, 0.0)]);
399 solver.factor(&mat2).unwrap();
400
401 let result = solver.solve(&[Complex64::new(6.0, 0.0)]).unwrap();
402 assert!((result[0].re - 2.0).abs() < 1e-12);
403 }
404}