use super::algo::{run_elimination as run_elimination_amd, run_elimination_amf, StepFlops};
use super::workspace::Workspace;
use crate::OrderingError;
pub trait Metric {
type Score: Copy + Ord + Default;
fn n_buckets(n: usize) -> usize;
fn init_score(len: i32) -> Self::Score;
fn bucket(score: Self::Score, n: usize) -> usize;
fn coarse_bucket(idx: usize, n: usize) -> bool;
fn merge_supervariable(parent: &mut Self::Score, child: Self::Score);
fn run_elimination(ws: &mut Workspace, aggressive: bool) -> Result<StepFlops, OrderingError>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct MinDegree;
impl Metric for MinDegree {
type Score = i32;
#[inline(always)]
fn n_buckets(n: usize) -> usize {
n
}
#[inline(always)]
fn init_score(len: i32) -> i32 {
len
}
#[inline(always)]
fn bucket(score: i32, _n: usize) -> usize {
score as usize
}
#[inline(always)]
fn coarse_bucket(_idx: usize, _n: usize) -> bool {
false
}
#[inline(always)]
fn merge_supervariable(_parent: &mut i32, _child: i32) {
}
#[inline(always)]
fn run_elimination(ws: &mut Workspace, aggressive: bool) -> Result<StepFlops, OrderingError> {
run_elimination_amd(ws, aggressive)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct MinFill;
impl Metric for MinFill {
type Score = i32;
#[inline(always)]
fn n_buckets(n: usize) -> usize {
2 * n + 2
}
#[inline(always)]
fn init_score(len: i32) -> i32 {
len
}
#[inline]
fn bucket(score: i32, n: usize) -> usize {
if score <= 0 {
return 0;
}
let s = score as usize;
if s <= n {
return s;
}
let pas = (n / 8).max(1);
let nbbuck = 2 * n;
let coarse = (s - n) / pas + n;
coarse.min(nbbuck)
}
#[inline(always)]
fn coarse_bucket(idx: usize, n: usize) -> bool {
idx > n
}
#[inline(always)]
fn merge_supervariable(parent: &mut i32, child: i32) {
if child > *parent {
*parent = child;
}
}
fn run_elimination(ws: &mut Workspace, aggressive: bool) -> Result<StepFlops, OrderingError> {
run_elimination_amf(ws, aggressive)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn min_degree_n_buckets_matches_workspace_alloc() {
for n in [0usize, 1, 5, 100, 10_000] {
assert_eq!(MinDegree::n_buckets(n), n);
}
}
#[test]
fn min_degree_init_score_is_identity() {
for len in [0i32, 1, 17, 1024] {
assert_eq!(MinDegree::init_score(len), len);
}
}
#[test]
fn min_degree_bucket_is_identity() {
for s in [0i32, 1, 7, 100] {
assert_eq!(MinDegree::bucket(s, 200), s as usize);
}
}
#[test]
fn min_degree_no_coarse_buckets() {
for n in [10usize, 100, 10_000] {
for idx in [0usize, 1, n / 2, n - 1] {
assert!(!MinDegree::coarse_bucket(idx, n));
}
}
}
#[test]
fn min_degree_merge_does_not_touch_parent() {
let mut parent: i32 = 42;
MinDegree::merge_supervariable(&mut parent, 7);
assert_eq!(parent, 42, "AMD merge is a true no-op on the score");
}
#[test]
fn min_fill_n_buckets_is_2n_plus_2() {
for n in [0usize, 1, 5, 100, 10_000] {
assert_eq!(MinFill::n_buckets(n), 2 * n + 2);
}
}
#[test]
fn min_fill_init_score_is_len() {
for len in [0i32, 1, 17, 1024] {
assert_eq!(MinFill::init_score(len), len);
}
}
#[test]
fn min_fill_bucket_fine_region_is_identity() {
let n = 100usize;
for s in [0i32, 1, 50, 99, 100] {
assert_eq!(MinFill::bucket(s, n), s as usize);
}
}
#[test]
fn min_fill_bucket_coarse_region_quantizes_with_pas() {
let n = 100usize;
assert_eq!(MinFill::bucket(101, n), 100);
assert_eq!(MinFill::bucket(112, n), 101);
assert_eq!(MinFill::bucket(113, n), 101);
assert_eq!(MinFill::bucket(124, n), 102);
}
#[test]
fn min_fill_bucket_caps_at_nbbuck() {
let n = 100usize;
let nbbuck = 2 * n;
assert_eq!(MinFill::bucket(1_000_000, n), nbbuck);
assert_eq!(MinFill::bucket(i32::MAX, n), nbbuck);
}
#[test]
fn min_fill_bucket_pas_is_at_least_one() {
assert_eq!(MinFill::bucket(5, 4), 5);
assert_eq!(MinFill::bucket(6, 4), 6);
assert_eq!(MinFill::bucket(8, 4), 8);
assert_eq!(MinFill::bucket(9, 4), 8);
}
#[test]
fn min_fill_bucket_clamps_nonpositive() {
assert_eq!(MinFill::bucket(0, 100), 0);
assert_eq!(MinFill::bucket(-1, 100), 0);
assert_eq!(MinFill::bucket(i32::MIN, 100), 0);
}
#[test]
fn min_fill_coarse_bucket_threshold() {
let n = 100usize;
for idx in [0usize, 50, 99, 100] {
assert!(!MinFill::coarse_bucket(idx, n), "{idx} <= n is fine");
}
for idx in [101usize, 150, 200, 201] {
assert!(MinFill::coarse_bucket(idx, n), "{idx} > n is coarse");
}
}
#[test]
fn min_fill_merge_takes_max() {
let mut parent: i32 = 10;
MinFill::merge_supervariable(&mut parent, 25);
assert_eq!(parent, 25, "child larger ⇒ adopt child");
let mut parent: i32 = 100;
MinFill::merge_supervariable(&mut parent, 7);
assert_eq!(parent, 100, "child smaller ⇒ keep parent");
let mut parent: i32 = 42;
MinFill::merge_supervariable(&mut parent, 42);
assert_eq!(parent, 42, "equal ⇒ unchanged");
}
#[test]
fn min_fill_run_elimination_completes() {
use crate::quotient_graph::WorkspaceOptions;
use crate::CscPattern;
let cp = [0i32, 1, 2];
let ri = [0i32, 1];
let p = CscPattern::new(2, &cp, &ri).unwrap();
let mut ws =
Workspace::new_with_n_buckets(&p, &WorkspaceOptions::default(), MinFill::n_buckets(2))
.unwrap();
MinFill::run_elimination(&mut ws, true).expect("AMF loop runs");
assert_eq!(ws.nel, ws.n);
}
}