#![cfg(feature = "bytecode")]
use echidna::{record_multi, Scalar};
fn linear_map<T: Scalar>(x: &[T]) -> Vec<T> {
let two = T::from_f(<T::Float as num_traits::FromPrimitive>::from_f64(2.0).unwrap());
let three = T::from_f(<T::Float as num_traits::FromPrimitive>::from_f64(3.0).unwrap());
vec![two * x[0] + x[1], x[1] + three * x[2]]
}
fn nonlinear_map<T: Scalar>(x: &[T]) -> Vec<T> {
vec![x[0] * x[1], x[1] * x[2], x[0] * x[0]]
}
#[test]
fn jacobian_sparsity_linear() {
let x = [1.0_f64, 2.0, 3.0];
let (tape, _) = record_multi(|v| linear_map(v), &x);
let pattern = tape.detect_jacobian_sparsity();
assert!(pattern.contains(0, 0));
assert!(pattern.contains(0, 1));
assert!(!pattern.contains(0, 2));
assert!(!pattern.contains(1, 0));
assert!(pattern.contains(1, 1));
assert!(pattern.contains(1, 2));
}
#[test]
fn jacobian_sparsity_nonlinear() {
let x = [1.0_f64, 2.0, 3.0];
let (tape, _) = record_multi(|v| nonlinear_map(v), &x);
let pattern = tape.detect_jacobian_sparsity();
assert!(pattern.contains(0, 0));
assert!(pattern.contains(0, 1));
assert!(!pattern.contains(0, 2));
assert!(!pattern.contains(1, 0));
assert!(pattern.contains(1, 1));
assert!(pattern.contains(1, 2));
assert!(pattern.contains(2, 0));
assert!(!pattern.contains(2, 1));
assert!(!pattern.contains(2, 2));
}
#[test]
fn column_coloring_diagonal() {
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 (_, num_colors) = echidna::sparse::column_coloring(&pattern);
assert_eq!(num_colors, 1);
}
#[test]
fn sparse_jacobian_vs_dense() {
let x = [1.0_f64, 2.0, 3.0];
let (mut tape, _) = record_multi(|v| nonlinear_map(v), &x);
let dense_jac = tape.jacobian(&x);
let (_, pattern, sparse_vals) = tape.sparse_jacobian(&x);
for (k, (&row, &col)) in pattern.rows.iter().zip(pattern.cols.iter()).enumerate() {
let r = row as usize;
let c = col as usize;
assert!(
(sparse_vals[k] - dense_jac[r][c]).abs() < 1e-10,
"mismatch at ({}, {}): sparse={}, dense={}",
r,
c,
sparse_vals[k],
dense_jac[r][c]
);
}
}
#[test]
fn sparse_jacobian_forward_vs_reverse() {
let x = [1.5_f64, 2.5, 0.5];
let (mut tape, _) = record_multi(|v| nonlinear_map(v), &x);
let (vals_fwd, pat_fwd, jac_fwd) = tape.sparse_jacobian_forward(&x);
let (vals_rev, pat_rev, jac_rev) = tape.sparse_jacobian_reverse(&x);
assert_eq!(vals_fwd.len(), vals_rev.len());
for i in 0..vals_fwd.len() {
assert!((vals_fwd[i] - vals_rev[i]).abs() < 1e-10);
}
assert_eq!(pat_fwd.nnz(), pat_rev.nnz());
for k in 0..jac_fwd.len() {
assert!(
(jac_fwd[k] - jac_rev[k]).abs() < 1e-10,
"mismatch at k={}: fwd={}, rev={}",
k,
jac_fwd[k],
jac_rev[k]
);
}
}
#[test]
fn sparse_jacobian_vec_matches() {
let x = [1.0_f64, 2.0, 3.0];
let (mut tape, _) = record_multi(|v| nonlinear_map(v), &x);
let (_, _, jac_scalar) = tape.sparse_jacobian_forward(&x);
let (_, _, jac_vec) = tape.sparse_jacobian_vec::<4>(&x);
for k in 0..jac_scalar.len() {
assert!(
(jac_scalar[k] - jac_vec[k]).abs() < 1e-10,
"vec mismatch at k={}",
k
);
}
}
#[test]
fn sparse_jacobian_with_pattern_precomputed() {
let x = [1.0_f64, 2.0, 3.0];
let (mut tape, _) = record_multi(|v| nonlinear_map(v), &x);
let pattern = tape.detect_jacobian_sparsity();
let (colors, num_colors) = echidna::sparse::column_coloring(&pattern);
let x2 = [3.0, 1.0, 2.0];
let (vals, jac_precomputed) =
tape.sparse_jacobian_with_pattern(&x2, &pattern, &colors, num_colors, true);
let (vals2, _, jac_fresh) = tape.sparse_jacobian_forward(&x2);
for i in 0..vals.len() {
assert!((vals[i] - vals2[i]).abs() < 1e-10);
}
for k in 0..jac_precomputed.len() {
assert!(
(jac_precomputed[k] - jac_fresh[k]).abs() < 1e-10,
"precomputed mismatch at k={}",
k
);
}
}
#[test]
fn jacobian_forward_vs_reverse() {
let x = [1.0_f64, 2.0, 3.0];
let (mut tape, _) = record_multi(|v| nonlinear_map(v), &x);
let jac_rev = tape.jacobian(&x);
let jac_fwd = tape.jacobian_forward(&x);
assert_eq!(jac_rev.len(), jac_fwd.len());
for i in 0..jac_rev.len() {
for j in 0..jac_rev[i].len() {
assert!(
(jac_rev[i][j] - jac_fwd[i][j]).abs() < 1e-10,
"mismatch at ({}, {})",
i,
j
);
}
}
}
#[test]
fn sparse_hessian_with_pattern_precomputed() {
let x = vec![1.5_f64, 2.0, 0.5];
let (tape, _) = echidna::record(|v| v[0] * v[1] + v[1] * v[2] + v[0] * v[0], &x);
let pattern = tape.detect_sparsity();
let (colors, num_colors) = echidna::sparse::greedy_coloring(&pattern);
let (val1, grad1, _, hess1) = tape.sparse_hessian(&x);
let (val2, grad2, hess2) = tape.sparse_hessian_with_pattern(&x, &pattern, &colors, num_colors);
assert!((val1 - val2).abs() < 1e-10);
for i in 0..grad1.len() {
assert!((grad1[i] - grad2[i]).abs() < 1e-10);
}
for k in 0..hess1.len() {
assert!((hess1[k] - hess2[k]).abs() < 1e-10);
}
}
#[test]
fn sparse_jacobian_auto_selects_reverse() {
fn wide_input_map<T: Scalar>(x: &[T]) -> Vec<T> {
vec![
x[0] + x[1] + x[2] + x[3] + x[4],
x[0] * x[1] * x[2] * x[3] * x[4],
]
}
let x = [1.0_f64, 2.0, 0.5, 1.5, 3.0];
let (mut tape, _) = record_multi(|v| wide_input_map(v), &x);
tape.forward(&x);
let pattern = tape.detect_jacobian_sparsity();
let (_, num_col_colors) = echidna::sparse::column_coloring(&pattern);
let (_, num_row_colors) = echidna::sparse::row_coloring(&pattern);
assert!(
num_row_colors < num_col_colors,
"test precondition: need row_colors ({}) < col_colors ({}) to force reverse",
num_row_colors,
num_col_colors
);
let (_, pat_auto, jac_auto) = tape.sparse_jacobian(&x);
let (_, pat_rev, jac_rev) = tape.sparse_jacobian_reverse(&x);
assert_eq!(pat_auto.nnz(), pat_rev.nnz());
for k in 0..jac_auto.len() {
assert!(
(jac_auto[k] - jac_rev[k]).abs() < 1e-10,
"auto vs reverse mismatch at k={}: auto={}, rev={}",
k,
jac_auto[k],
jac_rev[k]
);
}
let dense_jac = tape.jacobian(&x);
for (k, (&row, &col)) in pat_auto.rows.iter().zip(pat_auto.cols.iter()).enumerate() {
let r = row as usize;
let c = col as usize;
assert!(
(jac_auto[k] - dense_jac[r][c]).abs() < 1e-10,
"auto vs dense mismatch at ({}, {}): auto={}, dense={}",
r,
c,
jac_auto[k],
dense_jac[r][c]
);
}
}
#[test]
fn api_sparse_jacobian() {
let x = vec![1.0_f64, 2.0, 3.0];
let (vals, pattern, jac_vals) = echidna::sparse_jacobian(|v| nonlinear_map(v), &x);
assert_eq!(vals.len(), 3);
assert!(!pattern.is_empty());
assert!(!jac_vals.is_empty());
let (mut tape, _) = record_multi(|v| nonlinear_map(v), &x);
let dense_jac = tape.jacobian(&x);
for (k, (&row, &col)) in pattern.rows.iter().zip(pattern.cols.iter()).enumerate() {
let r = row as usize;
let c = col as usize;
assert!(
(jac_vals[k] - dense_jac[r][c]).abs() < 1e-10,
"api mismatch at ({}, {})",
r,
c
);
}
}