#![cfg(feature = "bytecode")]
use echidna::{BReverse, BytecodeTape};
use std::sync::Arc;
struct Scale;
impl echidna::CustomOp<f64> for Scale {
fn eval(&self, a: f64, _b: f64) -> f64 {
2.0 * a
}
fn partials(&self, _a: f64, _b: f64, _r: f64) -> (f64, f64) {
(2.0, 0.0)
}
}
fn rosenbrock_multi(x: &[BReverse<f64>]) -> Vec<BReverse<f64>> {
let r0 = x[0] * x[0];
let r1 = x[1] * x[1];
vec![r0, r1]
}
#[test]
#[should_panic(expected = "scalar-output")]
fn hessian_rejects_multi_output_tape() {
let (tape, _) = echidna::record_multi(rosenbrock_multi, &[1.0_f64, 2.0]);
let _ = tape.hessian(&[1.0_f64, 2.0]);
}
#[test]
#[should_panic(expected = "scalar-output")]
fn hvp_rejects_multi_output_tape() {
let (tape, _) = echidna::record_multi(rosenbrock_multi, &[1.0_f64, 2.0]);
let _ = tape.hvp(&[1.0_f64, 2.0], &[1.0, 0.0]);
}
#[test]
#[should_panic(expected = "scalar-output")]
fn hessian_vec_rejects_multi_output_tape() {
let (tape, _) = echidna::record_multi(rosenbrock_multi, &[1.0_f64, 2.0]);
let _ = tape.hessian_vec::<2>(&[1.0_f64, 2.0]);
}
#[test]
fn hessian_on_scalar_output_still_works() {
let (tape, _) = echidna::record(
|x: &[BReverse<f64>]| x[0] * x[0] + x[1] * x[1],
&[1.0_f64, 2.0],
);
let (_val, _grad, h) = tape.hessian(&[1.0_f64, 2.0]);
assert_eq!(h.len(), 2);
assert!((h[0][0] - 2.0).abs() < 1e-12);
assert!((h[1][1] - 2.0).abs() < 1e-12);
}
#[cfg(feature = "taylor")]
#[test]
#[should_panic(expected = "custom ops")]
fn taylor_grad_rejects_custom_ops() {
let x = [1.0_f64];
let mut tape = BytecodeTape::with_capacity(10);
let handle = tape.register_custom(Arc::new(Scale));
let idx = tape.new_input(x[0]);
let input = BReverse::from_tape(x[0], idx);
let output = {
let _guard = echidna::bytecode_tape::BtapeGuard::new(&mut tape);
input.custom_unary(handle, 2.0 * x[0])
};
tape.set_output(output.index());
let _ = tape.taylor_grad::<3>(&x, &[1.0]);
}
#[cfg(feature = "taylor")]
#[test]
#[should_panic(expected = "custom ops")]
fn ode_taylor_step_rejects_custom_ops() {
let x = [1.0_f64];
let mut tape = BytecodeTape::with_capacity(10);
let handle = tape.register_custom(Arc::new(Scale));
let idx = tape.new_input(x[0]);
let input = BReverse::from_tape(x[0], idx);
let output = {
let _guard = echidna::bytecode_tape::BtapeGuard::new(&mut tape);
input.custom_unary(handle, 2.0 * x[0])
};
tape.set_output(output.index());
tape.set_outputs(&[output.index()]);
let _ = tape.ode_taylor_step::<3>(&x);
}
#[test]
#[should_panic(expected = "custom ops")]
fn third_order_hvvp_rejects_custom_ops() {
let x = [1.0_f64];
let mut tape = BytecodeTape::with_capacity(10);
let handle = tape.register_custom(Arc::new(Scale));
let idx = tape.new_input(x[0]);
let input = BReverse::from_tape(x[0], idx);
let output = {
let _guard = echidna::bytecode_tape::BtapeGuard::new(&mut tape);
input.custom_unary(handle, 2.0 * x[0])
};
tape.set_output(output.index());
let _ = tape.third_order_hvvp(&x, &[1.0], &[1.0]);
}
#[test]
#[should_panic(expected = "scalar-output")]
fn third_order_hvvp_rejects_multi_output_tape() {
let (tape, _) = echidna::record_multi(rosenbrock_multi, &[1.0_f64, 2.0]);
let _ = tape.third_order_hvvp(&[1.0_f64, 2.0], &[1.0, 0.0], &[0.0, 1.0]);
}
#[test]
#[should_panic(expected = "scalar-output")]
fn sparse_hessian_rejects_multi_output_tape() {
let (tape, _) = echidna::record_multi(rosenbrock_multi, &[1.0_f64, 2.0]);
let _ = tape.sparse_hessian(&[1.0_f64, 2.0]);
}
#[cfg(feature = "parallel")]
#[test]
#[should_panic(expected = "scalar-output")]
fn sparse_hessian_par_rejects_multi_output_tape() {
let (tape, _) = echidna::record_multi(rosenbrock_multi, &[1.0_f64, 2.0]);
let _ = tape.sparse_hessian_par(&[1.0_f64, 2.0]);
}
#[cfg(feature = "parallel")]
#[test]
#[should_panic(expected = "scalar-output")]
fn hessian_par_rejects_multi_output_tape() {
let (tape, _) = echidna::record_multi(rosenbrock_multi, &[1.0_f64, 2.0]);
let _ = tape.hessian_par(&[1.0_f64, 2.0]);
}
#[test]
fn sparse_hessian_on_scalar_output_still_works() {
let (tape, _) = echidna::record(
|x: &[BReverse<f64>]| x[0] * x[0] + x[1] * x[1],
&[1.0_f64, 2.0],
);
let (_val, _grad, _pattern, h) = tape.sparse_hessian(&[1.0_f64, 2.0]);
assert!(h.iter().any(|&v| (v - 2.0).abs() < 1e-12));
}
#[test]
#[should_panic(expected = "No active tape")]
fn with_active_tape_without_guard_panics() {
echidna::tape::with_active_tape::<f64, _>(|_| ());
}
#[test]
#[should_panic(expected = "No active bytecode tape")]
fn with_active_btape_without_guard_panics() {
echidna::bytecode_tape::with_active_btape::<f64, _>(|_| ());
}
#[test]
#[should_panic(expected = "reentrant with_active_tape")]
fn reentrant_with_active_tape_panics() {
use echidna::tape::{with_active_tape, Tape, TapeGuard};
let mut tape: Tape<f64> = Tape::new();
let _guard = TapeGuard::new(&mut tape);
with_active_tape::<f64, _>(|_| {
with_active_tape::<f64, _>(|_| ());
});
}
#[test]
#[should_panic(expected = "reentrant with_active_btape")]
fn reentrant_with_active_btape_panics() {
use echidna::bytecode_tape::{with_active_btape, BtapeGuard};
let mut tape: BytecodeTape<f64> = BytecodeTape::new();
let _guard = BtapeGuard::new(&mut tape);
with_active_btape::<f64, _>(|_| {
with_active_btape::<f64, _>(|_| ());
});
}