#![cfg(feature = "bytecode")]
use echidna::record_multi;
use echidna::sparse::{
column_coloring, greedy_coloring, row_coloring, JacobianSparsityPattern, SparsityPattern,
};
#[test]
fn greedy_coloring_empty_pattern() {
let pattern = SparsityPattern {
dim: 0,
rows: vec![],
cols: vec![],
};
let (colors, num_colors) = greedy_coloring(&pattern);
assert!(colors.is_empty());
assert_eq!(num_colors, 0);
}
#[test]
fn greedy_coloring_diagonal_only() {
let pattern = SparsityPattern {
dim: 4,
rows: vec![0, 1, 2, 3],
cols: vec![0, 1, 2, 3],
};
let (colors, num_colors) = greedy_coloring(&pattern);
assert_eq!(colors.len(), 4);
assert_eq!(num_colors, 1);
}
#[test]
fn greedy_coloring_full_dense() {
let pattern = SparsityPattern {
dim: 3,
rows: vec![0, 1, 1, 2, 2, 2],
cols: vec![0, 0, 1, 0, 1, 2],
};
let (colors, num_colors) = greedy_coloring(&pattern);
assert_eq!(colors.len(), 3);
assert_eq!(num_colors, 3);
let mut unique = colors.clone();
unique.sort();
unique.dedup();
assert_eq!(unique.len(), 3);
}
#[test]
fn greedy_coloring_banded() {
let pattern = SparsityPattern {
dim: 4,
rows: vec![0, 1, 1, 2, 2, 3, 3],
cols: vec![0, 0, 1, 1, 2, 2, 3],
};
let (colors, num_colors) = greedy_coloring(&pattern);
assert_eq!(colors.len(), 4);
assert!(num_colors <= 4, "got {} colors for tridiagonal", num_colors);
assert_ne!(colors[0], colors[1]);
assert_ne!(colors[1], colors[2]);
}
#[test]
fn greedy_coloring_valid() {
let pattern = SparsityPattern {
dim: 3,
rows: vec![0, 1, 1, 2, 2],
cols: vec![0, 0, 1, 1, 2],
};
let (colors, num_colors) = greedy_coloring(&pattern);
assert_eq!(num_colors, 3);
assert_ne!(colors[0], colors[1]);
assert_ne!(colors[0], colors[2]);
assert_ne!(colors[1], colors[2]);
}
#[test]
fn column_coloring_empty() {
let pattern = JacobianSparsityPattern {
num_outputs: 0,
num_inputs: 0,
rows: vec![],
cols: vec![],
};
let (colors, num_colors) = column_coloring(&pattern);
assert!(colors.is_empty());
assert_eq!(num_colors, 0);
}
#[test]
fn column_coloring_independent_columns() {
let pattern = JacobianSparsityPattern {
num_outputs: 3,
num_inputs: 3,
rows: vec![0, 1, 2],
cols: vec![0, 1, 2],
};
let (colors, num_colors) = column_coloring(&pattern);
assert_eq!(colors.len(), 3);
assert_eq!(num_colors, 1);
}
#[test]
fn column_coloring_fully_dense() {
let pattern = JacobianSparsityPattern {
num_outputs: 2,
num_inputs: 3,
rows: vec![0, 0, 0, 1, 1, 1],
cols: vec![0, 1, 2, 0, 1, 2],
};
let (colors, num_colors) = column_coloring(&pattern);
assert_eq!(colors.len(), 3);
assert_eq!(num_colors, 3);
}
#[test]
fn column_coloring_partial_overlap() {
let pattern = JacobianSparsityPattern {
num_outputs: 2,
num_inputs: 3,
rows: vec![0, 0, 1, 1],
cols: vec![0, 1, 1, 2],
};
let (colors, num_colors) = column_coloring(&pattern);
assert_eq!(colors.len(), 3);
assert_eq!(num_colors, 2);
assert_ne!(colors[0], colors[1]); assert_ne!(colors[1], colors[2]); assert_eq!(colors[0], colors[2]); }
#[test]
fn row_coloring_empty() {
let pattern = JacobianSparsityPattern {
num_outputs: 0,
num_inputs: 0,
rows: vec![],
cols: vec![],
};
let (colors, num_colors) = row_coloring(&pattern);
assert!(colors.is_empty());
assert_eq!(num_colors, 0);
}
#[test]
fn row_coloring_independent_rows() {
let pattern = JacobianSparsityPattern {
num_outputs: 3,
num_inputs: 3,
rows: vec![0, 1, 2],
cols: vec![0, 1, 2],
};
let (colors, num_colors) = row_coloring(&pattern);
assert_eq!(colors.len(), 3);
assert_eq!(num_colors, 1);
}
#[test]
fn row_coloring_fully_dense() {
let pattern = JacobianSparsityPattern {
num_outputs: 3,
num_inputs: 2,
rows: vec![0, 0, 1, 1, 2, 2],
cols: vec![0, 1, 0, 1, 0, 1],
};
let (colors, num_colors) = row_coloring(&pattern);
assert_eq!(colors.len(), 3);
assert_eq!(num_colors, 3);
}
#[test]
fn detected_sparsity_produces_valid_coloring() {
let x = [1.0_f64, 2.0, 3.0];
let (tape, _) = record_multi(|v| vec![v[0] * v[1], v[2]], &x);
let pattern = tape.detect_jacobian_sparsity();
let (col_colors, col_n) = column_coloring(&pattern);
let (row_colors, row_n) = row_coloring(&pattern);
assert_eq!(col_n, 2);
assert_ne!(col_colors[0], col_colors[1]);
assert_eq!(row_n, 1);
assert_eq!(row_colors[0], row_colors[1]);
}
#[test]
fn sparse_jacobian_matches_dense() {
let x = [2.0_f64, 3.0, 4.0];
let (mut tape, _) = record_multi(|v| vec![v[0] * v[1], v[1] + v[2], v[0] * v[2]], &x);
let dense_jac = tape.jacobian(&x);
let (_, _, sparse_vals) = tape.sparse_jacobian(&x);
let pattern = tape.detect_jacobian_sparsity();
for k in 0..pattern.nnz() {
let row = pattern.rows[k] as usize;
let col = pattern.cols[k] as usize;
let dense_val = dense_jac[row][col];
let sparse_val = sparse_vals[k];
assert!(
(dense_val - sparse_val).abs() < 1e-10,
"mismatch at ({}, {}): dense={}, sparse={}",
row,
col,
dense_val,
sparse_val
);
}
}