#![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)
}
}
#[test]
#[should_panic(expected = "custom ops")]
fn jacobian_forward_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.jacobian_forward(&x);
}
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);
}