1use crate::{
29 geop_error::{GeopError, GeopResult},
30 matrix::Matrix,
31 scalars::Scalar,
32 vector::{Vector, Vector3},
33};
34
35pub fn solve_linear_system<const N: usize>(
53 a: &Matrix<N, N>,
54 b: &Vector<N>,
55) -> GeopResult<Vector<N>> {
56 let mut a = *a;
57 let mut b = *b;
58
59 for col in 0..N {
60 let mut pivot = col;
61 for row in (col + 1)..N {
62 if a[(row, col)]
63 .abs()
64 .sharpen()
65 .definitely_greater(a[(pivot, col)].abs().sharpen())
66 {
67 pivot = row;
68 }
69 }
70 a.swap_rows(col, pivot);
71 let tmp = b[col];
72 b[col] = b[pivot];
73 b[pivot] = tmp;
74
75 for row in (col + 1)..N {
76 let factor = a[(row, col)].div(a[(col, col)]).map_err(|e| {
77 e.with_context(format!(
78 "solve_linear_system: singular at column {col}, pivot={:?}",
79 a[(col, col)]
80 ))
81 })?;
82 for k in col..N {
83 a[(row, k)] = a[(row, k)].sub(factor.mul(a[(col, k)]));
84 }
85 b[row] = b[row].sub(factor.mul(b[col]));
86 }
87 }
88
89 let mut x = Vector::<N>::zero();
90 for row in (0..N).rev() {
91 let mut sum = b[row];
92 for k in (row + 1)..N {
93 sum = sum.sub(a[(row, k)].mul(x[k]));
94 }
95 x[row] = sum.div(a[(row, row)]).map_err(|e| {
96 e.with_context(format!(
97 "solve_linear_system: singular at row {row}, pivot={:?}",
98 a[(row, row)]
99 ))
100 })?;
101 }
102 Ok(x)
103}
104
105pub fn solve_symmetric2(
109 (a11, a12, a22): (Scalar, Scalar, Scalar),
110 (b1, b2): (Scalar, Scalar),
111) -> GeopResult<(Scalar, Scalar)> {
112 let det = a11.mul(a22).sub(a12.mul(a12));
113 Ok((
114 b1.mul(a22).sub(b2.mul(a12)).div(det)?,
115 a11.mul(b2).sub(a12.mul(b1)).div(det)?,
116 ))
117}
118
119#[allow(
126 clippy::needless_range_loop,
127 reason = "rows of one matrix, indexed alike"
128)]
129pub fn solve_pivoted<const C: usize>(
130 mut a: Vec<Vec<Scalar>>,
131 mut rhs: Vec<Vector<C>>,
132) -> GeopResult<Vec<Vector<C>>> {
133 let n = a.len();
134 for col in 0..n {
135 let pivot = (col..n)
136 .max_by(|&i, &j| {
137 a[i][col]
138 .abs()
139 .to_f64()
140 .total_cmp(&a[j][col].abs().to_f64())
141 })
142 .expect("a column has rows");
143 if a[pivot][col].could_be_equal(Scalar::ZERO) {
144 return Err(GeopError::new(format!(
145 "solve_pivoted: column {col} has no entry that is definitely not zero"
146 )));
147 }
148 a.swap(col, pivot);
149 rhs.swap(col, pivot);
150 for row in (col + 1)..n {
151 let factor = a[row][col].div(a[col][col])?;
152 for c in col..n {
153 a[row][c] = a[row][c].sub(factor.mul(a[col][c]));
154 }
155 rhs[row] = rhs[row].sub(&rhs[col].prod_scalar(factor));
156 }
157 }
158 let mut x = vec![Vector::<C>::zero(); n];
159 for row in (0..n).rev() {
160 let mut sum = rhs[row];
161 for col in (row + 1)..n {
162 sum = sum.sub(&x[col].prod_scalar(a[row][col]));
163 }
164 x[row] = sum.prod_scalar(Scalar::ONE.div(a[row][row])?);
165 }
166 Ok(x)
167}
168
169#[allow(
185 clippy::needless_range_loop,
186 reason = "rows of one matrix, indexed alike"
187)]
188pub fn solve_banded<const C: usize>(
189 mut a: Vec<Vec<Scalar>>,
190 first: &[usize],
191 last: &[usize],
192 mut rhs: Vec<Vector<C>>,
193) -> GeopResult<Vec<Vector<C>>> {
194 let m = rhs.len();
195 for col in 0..m {
196 let pivot = a[col][col];
197 for row in (col + 1)..m {
198 if first[row] > col {
199 break;
200 }
201 let factor = a[row][col].div(pivot)?;
202 for c in col..=last[col] {
203 a[row][c] = a[row][c].sub(factor.mul(a[col][c]));
204 }
205 for c in 0..C {
206 rhs[row][c] = rhs[row][c].sub(factor.mul(rhs[col][c]));
207 }
208 }
209 }
210
211 let mut x = vec![Vector::<C>::zero(); m];
212 for row in (0..m).rev() {
213 let mut sum = rhs[row];
214 for col in (row + 1)..=last[row] {
215 let coef = a[row][col];
216 for c in 0..C {
217 sum[c] = sum[c].sub(coef.mul(x[col][c]));
218 }
219 }
220 for c in 0..C {
221 x[row][c] = sum[c].div(a[row][row])?;
222 }
223 }
224 Ok(x)
225}
226
227pub fn cholesky(a: &[Vec<Scalar>]) -> GeopResult<(Vec<Vec<Scalar>>, Vec<bool>)> {
233 let n = a.len();
234 let mut l = vec![vec![Scalar::ZERO; n]; n];
235 let mut left_out = vec![false; n];
236 for i in 0..n {
237 for j in 0..=i {
238 if left_out[j] {
239 continue;
240 }
241 let sum = (0..j).fold(a[i][j], |sum, k| sum.sub(l[i][k].mul(l[j][k])));
242 if i == j {
243 let root = if sum.definitely_greater(Scalar::ZERO) {
247 Some(sum.sqrt()?).filter(|r| Scalar::ONE.div(*r).is_ok_and(|v| v.is_finite()))
248 } else {
249 None
250 };
251 if let Some(root) = root {
252 l[i][i] = root;
253 } else {
254 left_out[i] = true;
255 l[i].iter_mut().for_each(|v| *v = Scalar::ZERO);
256 }
257 } else {
258 l[i][j] = sum.div(l[j][j])?;
259 }
260 }
261 }
262 Ok((l, left_out))
263}
264
265pub fn solve_cholesky(
267 l: &[Vec<Scalar>],
268 left_out: &[bool],
269 b: &[Scalar],
270) -> GeopResult<Vec<Scalar>> {
271 let n = b.len();
272 let mut y = vec![Scalar::ZERO; n];
273 for i in (0..n).filter(|&i| !left_out[i]) {
274 let sum = (0..i).fold(b[i], |sum, k| sum.sub(l[i][k].mul(y[k])));
275 y[i] = sum.div(l[i][i])?;
276 }
277 let mut x = vec![Scalar::ZERO; n];
278 for i in (0..n).rev().filter(|&i| !left_out[i]) {
279 let sum = (i + 1..n).fold(y[i], |sum, k| sum.sub(l[k][i].mul(x[k])));
280 x[i] = sum.div(l[i][i])?;
281 }
282 Ok(x)
283}
284
285pub struct Elimination {
287 pub rows: Vec<Vec<Scalar>>,
290 pub pivots: Vec<(usize, usize)>,
293 pub origin: Vec<usize>,
296}
297
298pub fn eliminate(mut rows: Vec<Vec<Scalar>>, n: usize) -> Elimination {
313 super::factorised(rows.len(), n);
314 let mut support: Vec<Vec<usize>> = rows
321 .iter()
322 .map(|row| (0..row.len()).filter(|&c| !row[c].is_zero()).collect())
323 .collect();
324 let mut origin: Vec<usize> = (0..rows.len()).collect();
325 let mut pivots: Vec<(usize, usize)> = Vec::new();
326 let mut used = vec![false; n];
327 let mut r = 0;
328 loop {
329 let size = |v: Scalar| v.abs().lower();
330 let mut best: Option<(usize, usize)> = None;
331 for i in r..rows.len() {
332 for &col in support[i].iter().filter(|&&c| c < n && !used[c]) {
333 let v = rows[i][col];
334 if v.definitely_not_equal(Scalar::ZERO)
335 && best.is_none_or(|(bi, bc)| size(v).definitely_greater(size(rows[bi][bc])))
336 {
337 best = Some((i, col));
338 }
339 }
340 }
341 let Some((best, col)) = best else {
342 break;
343 };
344 rows.swap(r, best);
345 support.swap(r, best);
346 origin.swap(r, best);
347 let pivot = rows[r][col];
348 for &c in &support[r] {
349 rows[r][c] = rows[r][c]
350 .div(pivot)
351 .expect("a pivot is definitely not zero");
352 }
353 let pivot_support = std::mem::take(&mut support[r]);
354 for i in (0..rows.len()).filter(|&i| i != r) {
355 let factor = rows[i][col];
356 if factor.is_zero() {
357 continue;
358 }
359 for &c in &pivot_support {
360 rows[i][c] = rows[i][c].sub(factor.mul(rows[r][c]));
361 }
362 support[i] = union(&support[i], &pivot_support);
363 }
364 support[r] = pivot_support;
365 used[col] = true;
366 pivots.push((col, origin[r]));
367 r += 1;
368 }
369 Elimination {
370 rows,
371 pivots,
372 origin,
373 }
374}
375
376fn union(a: &[usize], b: &[usize]) -> Vec<usize> {
378 let mut out = Vec::with_capacity(a.len() + b.len());
379 let (mut i, mut j) = (0, 0);
380 while i < a.len() || j < b.len() {
381 match (a.get(i), b.get(j)) {
382 (Some(&x), Some(&y)) if x == y => {
383 out.push(x);
384 i += 1;
385 j += 1;
386 }
387 (Some(&x), Some(&y)) if x < y => {
388 out.push(x);
389 i += 1;
390 }
391 (Some(&x), None) => {
392 out.push(x);
393 i += 1;
394 }
395 (_, Some(&y)) => {
396 out.push(y);
397 j += 1;
398 }
399 (None, None) => unreachable!("the loop ends when both are exhausted"),
400 }
401 }
402 out
403}
404
405pub fn null_space(rows: Vec<Vec<Scalar>>, n: usize) -> Vec<Vec<Scalar>> {
410 let Elimination { rows, pivots, .. } = eliminate(rows, n);
411 let is_pivot: Vec<bool> = (0..n).map(|c| pivots.iter().any(|p| p.0 == c)).collect();
412 (0..n)
413 .filter(|&f| !is_pivot[f])
414 .map(|f| {
415 let mut v = vec![Scalar::ZERO; n];
416 v[f] = Scalar::ONE;
417 for (i, &(pc, _)) in pivots.iter().enumerate() {
418 v[pc] = rows[i][f].neg();
419 }
420 v
421 })
422 .collect()
423}
424
425pub fn rank(rows: Vec<Vec<Scalar>>, n: usize) -> usize {
428 eliminate(rows, n).pivots.len()
429}
430
431pub fn augmented(rows: impl ExactSizeIterator<Item = Vec<Scalar>>) -> Vec<Vec<Scalar>> {
435 let m = rows.len();
436 rows.enumerate()
437 .map(|(i, mut row)| {
438 row.extend((0..m).map(|j| if i == j { Scalar::ONE } else { Scalar::ZERO }));
439 row
440 })
441 .collect()
442}
443
444pub fn inverse(a: &[Vec<Scalar>]) -> Option<Vec<Vec<Scalar>>> {
448 let m = a.len();
449 let Elimination { rows, pivots, .. } = eliminate(augmented(a.iter().cloned()), m);
450 if pivots.len() < m {
451 return None;
452 }
453 let mut inverse = vec![Vec::new(); m];
454 for (row, &(col, _)) in rows.into_iter().zip(&pivots) {
455 inverse[col] = row[m..].to_vec();
456 }
457 Some(inverse)
458}
459
460pub fn shortest_solution(rows: &[(Vector3, Scalar)]) -> GeopResult<Vector3> {
467 fn combine<const N: usize>(rows: &[(Vector3, Scalar)]) -> GeopResult<Vector3> {
470 let mut gram = Matrix::<N, N>::zero();
471 let mut r = Vector::<N>::zero();
472 for i in 0..N {
473 r[i] = rows[i].1;
474 for j in 0..N {
475 gram[(i, j)] = rows[i].0.prod_dot(&rows[j].0);
476 }
477 }
478 let lambda = solve_linear_system(&gram, &r)?;
479 Ok((0..N).fold(Vector3::zero(), |d, i| {
480 d.add(&rows[i].0.prod_scalar(lambda[i]))
481 }))
482 }
483 match rows.len() {
484 0 => Err(GeopError::new("no condition to meet")),
485 1 => combine::<1>(rows),
486 2 => combine::<2>(rows),
487 3 => solve_linear_system(
488 &Matrix::from_rows([
489 rows[0].0.to_array(),
490 rows[1].0.to_array(),
491 rows[2].0.to_array(),
492 ]),
493 &Vector3::from_array([rows[0].1, rows[1].1, rows[2].1]),
494 ),
495 _ => {
496 let mut normal = Matrix::<3, 3>::zero();
497 let mut b = Vector::<3>::zero();
498 for (n, r) in rows {
499 for i in 0..3 {
500 b[i] = b[i].add(n[i].mul(*r));
501 for j in 0..3 {
502 normal[(i, j)] = normal[(i, j)].add(n[i].mul(n[j]));
503 }
504 }
505 }
506 solve_linear_system(&normal, &b)
507 }
508 }
509}
510
511#[cfg(test)]
512mod tests {
513
514 use super::*;
515
516 fn chain(m: usize, block: usize) -> (Vec<Vec<Scalar>>, usize) {
520 let n = (m + 1) * block;
521 let mut rows = Vec::new();
522 for k in 0..m {
523 for e in 0..block {
524 let mut row = vec![Scalar::ZERO; n];
525 for b in 0..block {
526 let own = if b == e { 10.0 } else { 0.0 };
527 row[k * block + b] = Scalar::from_f64(own + ((k + e + b) % 5) as f64 * 0.1);
528 row[(k + 1) * block + b] =
529 Scalar::from_f64(((k * 3 + e * 5 + b) % 7) as f64 * 0.1 - 0.3);
530 }
531 rows.push(row);
532 }
533 }
534 (rows, n)
535 }
536
537 #[test]
541 fn a_sparse_elimination_finds_the_null_space() {
542 let (rows, n) = chain(12, 6);
543 assert_eq!(eliminate(rows.clone(), n).pivots.len(), rows.len());
544 let basis = null_space(rows.clone(), n);
545 assert_eq!(basis.len(), n - rows.len());
546 for v in &basis {
547 for row in &rows {
548 let dot: f64 = row
549 .iter()
550 .zip(v)
551 .map(|(a, b)| a.to_f64() * b.to_f64())
552 .sum();
553 assert!(dot.abs() < 1e-9, "a row is {dot} on a null vector");
554 }
555 }
556 }
557
558 #[test]
560 fn a_chain_has_its_rank() {
561 let (rows, n) = chain(150, 6);
562 let m = rows.len();
563 assert_eq!(eliminate(rows, n).pivots.len(), m);
564 }
565
566 #[test]
569 fn the_inverse_of_a_sparse_matrix_is_one() {
570 let a: Vec<Vec<Scalar>> = [[2.0, 0.0, 1.0], [0.0, 3.0, 0.0], [1.0, 0.0, 4.0]]
571 .map(|row| row.map(Scalar::from_f64).to_vec())
572 .to_vec();
573 let inverse = inverse(&a).expect("the matrix is not singular");
574 for (i, row) in a.iter().enumerate() {
575 for j in 0..3 {
576 let dot: f64 = (row.iter().zip(&inverse))
577 .map(|(a, inverse)| a.to_f64() * inverse[j].to_f64())
578 .sum();
579 let want = if i == j { 1.0 } else { 0.0 };
580 assert!((dot - want).abs() < 1e-12, "({i}, {j}): {dot}");
581 }
582 }
583 }
584}
585
586#[cfg(test)]
587mod system_tests {
588 use super::*;
589
590 #[test]
591 fn solves_identity() {
592 let a = Matrix::from_rows([[Scalar::ONE, Scalar::ZERO], [Scalar::ZERO, Scalar::ONE]]);
593 let b = Vector::<2>::from_array([Scalar::from_f64(3.0), Scalar::from_f64(4.0)]);
594 let x = solve_linear_system(&a, &b).unwrap();
595 assert!(x[0].could_be_equal(Scalar::from_f64(3.0)));
596 assert!(x[1].could_be_equal(Scalar::from_f64(4.0)));
597 }
598
599 #[test]
600 fn solves_general_2x2() {
601 let a = Matrix::from_rows([
603 [Scalar::from_f64(2.0), Scalar::from_f64(1.0)],
604 [Scalar::from_f64(1.0), Scalar::from_f64(3.0)],
605 ]);
606 let b = Vector::from_array([Scalar::from_f64(5.0), Scalar::from_f64(10.0)]);
607 let x = solve_linear_system(&a, &b).unwrap();
608 assert!(x[0].could_be_equal(Scalar::ONE));
609 assert!(x[1].could_be_equal(Scalar::from_f64(3.0)));
610 }
611
612 #[test]
613 fn singular_errs() {
614 let a = Matrix::from_rows([[Scalar::ONE, Scalar::ONE], [Scalar::ONE, Scalar::ONE]]);
615 let b = Vector::from_array([Scalar::ONE, Scalar::TWO]);
616 assert!(solve_linear_system(&a, &b).is_err());
617 }
618}