use vyre_primitives::math::multigrid::{jacobi_smooth_step_cpu, jacobi_smooth_step_cpu_into};
#[must_use]
pub fn matroid_solve_step(a: &[f64], b: &[f64], x_in: &[f64], omega: f64, n: u32) -> Vec<f64> {
use crate::observability::{bump, multigrid_matroid_solver_calls};
bump(&multigrid_matroid_solver_calls);
jacobi_smooth_step_cpu(a, b, x_in, omega, n)
}
pub fn matroid_solve_step_into(
a: &[f64],
b: &[f64],
x_in: &[f64],
omega: f64,
n: u32,
out: &mut Vec<f64>,
) {
use crate::observability::{bump, multigrid_matroid_solver_calls};
bump(&multigrid_matroid_solver_calls);
jacobi_smooth_step_cpu_into(a, b, x_in, omega, n, out);
}
#[must_use]
pub fn solve_to_tolerance(
a: &[f64],
b: &[f64],
x0: &[f64],
omega: f64,
n: u32,
tol: f64,
max_iters: u32,
) -> (Vec<f64>, u32) {
let mut x = Vec::new();
let mut next = Vec::new();
let iters = solve_to_tolerance_into(a, b, x0, omega, n, tol, max_iters, &mut x, &mut next);
(x, iters)
}
#[allow(clippy::too_many_arguments)]
pub fn solve_to_tolerance_into(
a: &[f64],
b: &[f64],
x0: &[f64],
omega: f64,
n: u32,
tol: f64,
max_iters: u32,
x: &mut Vec<f64>,
next: &mut Vec<f64>,
) -> u32 {
x.clear();
x.extend_from_slice(x0);
next.clear();
let n_us = n as usize;
for iter in 0..max_iters {
matroid_solve_step_into(a, b, x, omega, n, next);
std::mem::swap(x, next);
let mut max_resid = 0.0_f64;
for i in 0..n_us {
let row_dot: f64 = (0..n_us).map(|j| a[i * n_us + j] * x[j]).sum();
let r = (row_dot - b[i]).abs();
if r > max_resid {
max_resid = r;
}
}
if max_resid < tol {
return iter + 1;
}
}
max_iters
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-4 * (1.0 + a.abs() + b.abs())
}
#[test]
fn identity_system_converges_to_b() {
let a = vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
let b = vec![1.0, 2.0, 3.0];
let x0 = vec![0.0; 3];
let (x, iters) = solve_to_tolerance(&a, &b, &x0, 1.0, 3, 1e-6, 100);
for (a, b) in x.iter().zip(b.iter()) {
assert!(approx_eq(*a, *b));
}
assert!(iters <= 5, "identity converges in 1 step");
}
#[test]
fn diagonally_dominant_system_converges() {
let a = vec![4.0, 1.0, 2.0, 5.0];
let b = vec![9.0, 8.0];
let x0 = vec![0.0, 0.0];
let (x, _) = solve_to_tolerance(&a, &b, &x0, 0.66, 2, 1e-4, 1000);
assert!(approx_eq(x[0], 37.0 / 18.0));
assert!(approx_eq(x[1], 14.0 / 18.0));
}
#[test]
fn zero_max_iters_returns_initial() {
let a = vec![1.0, 0.0, 0.0, 1.0];
let b = vec![5.0, 7.0];
let x0 = vec![0.0, 0.0];
let (x, iters) = solve_to_tolerance(&a, &b, &x0, 1.0, 2, 1e-6, 0);
assert_eq!(x, x0);
assert_eq!(iters, 0);
}
#[test]
fn solve_to_tolerance_into_matches_owned_solver() {
let a = vec![4.0, 1.0, 2.0, 5.0];
let b = vec![9.0, 8.0];
let x0 = vec![0.0, 0.0];
let (owned, owned_iters) = solve_to_tolerance(&a, &b, &x0, 0.66, 2, 1e-4, 1000);
let mut x = Vec::new();
let mut next = Vec::new();
let into_iters =
solve_to_tolerance_into(&a, &b, &x0, 0.66, 2, 1e-4, 1000, &mut x, &mut next);
assert_eq!(into_iters, owned_iters);
assert_eq!(x.len(), owned.len());
for (a, b) in x.iter().zip(owned.iter()) {
assert!(approx_eq(*a, *b));
}
}
#[test]
fn matroid_solve_step_is_jacobi_iteration() {
let a = vec![2.0, 0.0, 0.0, 2.0];
let b = vec![6.0, 8.0];
let x_in = vec![0.0, 0.0];
let x_out = matroid_solve_step(&a, &b, &x_in, 1.0, 2);
assert!(approx_eq(x_out[0], 3.0));
assert!(approx_eq(x_out[1], 4.0));
}
}