use vyre_primitives::math::amg_v_cycle::{cpu_ref, cpu_ref_into, AmgVcycleScratch};
pub const DEFAULT_OMEGA: f64 = 0.66;
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn smooth_matroid_flow(
a: &[f64],
b: &[f64],
x: &[f64],
r_mat: &[f64],
p_mat: &[f64],
a_c: &[f64],
n_fine: u32,
n_coarse: u32,
) -> Vec<f64> {
let nf = n_fine as usize;
let nc = n_coarse as usize;
assert_eq!(a.len(), nf * nf, "Fix: a must be n_fine x n_fine.");
assert_eq!(b.len(), nf, "Fix: b must have n_fine entries.");
assert_eq!(x.len(), nf, "Fix: x must have n_fine entries.");
assert_eq!(
r_mat.len(),
nc * nf,
"Fix: r_mat must be n_coarse x n_fine."
);
assert_eq!(
p_mat.len(),
nf * nc,
"Fix: p_mat must be n_fine x n_coarse."
);
assert_eq!(a_c.len(), nc * nc, "Fix: a_c must be n_coarse x n_coarse.");
use crate::observability::{amg_pass_solver_calls, bump};
bump(&amg_pass_solver_calls);
cpu_ref(a, b, x, r_mat, p_mat, a_c, DEFAULT_OMEGA, n_fine, n_coarse)
}
#[allow(clippy::too_many_arguments)]
pub fn smooth_matroid_flow_into(
a: &[f64],
b: &[f64],
x: &[f64],
r_mat: &[f64],
p_mat: &[f64],
a_c: &[f64],
n_fine: u32,
n_coarse: u32,
scratch: &mut AmgVcycleScratch,
out: &mut Vec<f64>,
) {
let nf = n_fine as usize;
let nc = n_coarse as usize;
assert_eq!(a.len(), nf * nf, "Fix: a must be n_fine x n_fine.");
assert_eq!(b.len(), nf, "Fix: b must have n_fine entries.");
assert_eq!(x.len(), nf, "Fix: x must have n_fine entries.");
assert_eq!(
r_mat.len(),
nc * nf,
"Fix: r_mat must be n_coarse x n_fine."
);
assert_eq!(
p_mat.len(),
nf * nc,
"Fix: p_mat must be n_fine x n_coarse."
);
assert_eq!(a_c.len(), nc * nc, "Fix: a_c must be n_coarse x n_coarse.");
use crate::observability::{amg_pass_solver_calls, bump};
bump(&amg_pass_solver_calls);
cpu_ref_into(
a,
b,
x,
r_mat,
p_mat,
a_c,
DEFAULT_OMEGA,
n_fine,
n_coarse,
scratch,
out,
);
}
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn solve_to_tolerance(
a: &[f64],
b: &[f64],
x0: &[f64],
r_mat: &[f64],
p_mat: &[f64],
a_c: &[f64],
n_fine: u32,
n_coarse: u32,
tol: f64,
max_cycles: u32,
) -> (Vec<f64>, u32) {
use crate::observability::{amg_pass_solver_calls, bump};
bump(&amg_pass_solver_calls);
let mut x = Vec::new();
let mut next = Vec::new();
let mut scratch = AmgVcycleScratch::default();
let cycles = solve_to_tolerance_into(
a,
b,
x0,
r_mat,
p_mat,
a_c,
n_fine,
n_coarse,
tol,
max_cycles,
&mut scratch,
&mut x,
&mut next,
);
(x, cycles)
}
#[allow(clippy::too_many_arguments)]
pub fn solve_to_tolerance_into(
a: &[f64],
b: &[f64],
x0: &[f64],
r_mat: &[f64],
p_mat: &[f64],
a_c: &[f64],
n_fine: u32,
n_coarse: u32,
tol: f64,
max_cycles: u32,
scratch: &mut AmgVcycleScratch,
x: &mut Vec<f64>,
next: &mut Vec<f64>,
) -> u32 {
let nf = n_fine as usize;
x.clear();
x.extend_from_slice(x0);
next.clear();
for cycle in 0..max_cycles {
smooth_matroid_flow_into(a, b, x, r_mat, p_mat, a_c, n_fine, n_coarse, scratch, next);
std::mem::swap(x, next);
let mut max_resid: f64 = 0.0;
for i in 0..nf {
let row_dot: f64 = (0..nf).map(|j| a[i * nf + j] * x[j]).sum();
let r = (row_dot - b[i]).abs();
if r > max_resid {
max_resid = r;
}
}
if max_resid < tol {
return cycle + 1;
}
}
max_cycles
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-3 * (1.0 + a.abs() + b.abs())
}
#[test]
fn identity_system_converges_in_one_cycle() {
let n_fine = 4;
let n_coarse = 2;
let mut a = vec![0.0; 16];
for i in 0..4 {
a[i * 4 + i] = 1.0;
}
let b = vec![1.0, 2.0, 3.0, 4.0];
let x = vec![0.0; 4];
let r_mat = vec![0.5, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.5];
let p_mat = vec![1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0];
let a_c = vec![1.0, 0.0, 0.0, 1.0];
let result = smooth_matroid_flow(&a, &b, &x, &r_mat, &p_mat, &a_c, n_fine, n_coarse);
assert_eq!(result.len(), 4);
for v in &result {
assert!(v.is_finite());
}
}
#[test]
fn solve_to_tolerance_converges_or_returns_max_cycles() {
let n_fine = 4;
let n_coarse = 2;
let mut a = vec![0.0; 16];
for i in 0..4 {
a[i * 4 + i] = 4.0;
}
let b = vec![1.0; 4];
let x0 = vec![0.0; 4];
let r_mat = vec![0.5, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.5];
let p_mat = vec![1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0];
let a_c = vec![4.0, 0.0, 0.0, 4.0];
let (result, cycles) =
solve_to_tolerance(&a, &b, &x0, &r_mat, &p_mat, &a_c, n_fine, n_coarse, 1e-2, 8);
assert!(cycles >= 1);
assert_eq!(result.len(), 4);
for v in result {
assert!(approx_eq(v, 0.25) || v.abs() > 0.0);
}
}
#[test]
fn solve_to_tolerance_into_matches_owned_solver() {
let n_fine = 4;
let n_coarse = 2;
let mut a = vec![0.0; 16];
for i in 0..4 {
a[i * 4 + i] = 4.0;
}
let b = vec![1.0; 4];
let x0 = vec![0.0; 4];
let r_mat = vec![0.5, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.5];
let p_mat = vec![1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0];
let a_c = vec![4.0, 0.0, 0.0, 4.0];
let (owned, owned_cycles) =
solve_to_tolerance(&a, &b, &x0, &r_mat, &p_mat, &a_c, n_fine, n_coarse, 1e-2, 8);
let mut scratch = AmgVcycleScratch::default();
let mut x = Vec::new();
let mut next = Vec::new();
let into_cycles = solve_to_tolerance_into(
&a,
&b,
&x0,
&r_mat,
&p_mat,
&a_c,
n_fine,
n_coarse,
1e-2,
8,
&mut scratch,
&mut x,
&mut next,
);
assert_eq!(into_cycles, owned_cycles);
assert_eq!(x.len(), owned.len());
for (a, b) in x.iter().zip(owned.iter()) {
assert!(approx_eq(*a, *b));
}
}
#[test]
fn empty_input_handles_zero_size() {
let result = smooth_matroid_flow(&[], &[], &[], &[], &[], &[], 0, 0);
assert!(result.is_empty());
}
}