use super::*;
#[test]
fn forward_gradients_match_scalar_complex_finite_differences() {
let x = laddu_expr::Expr::from(parameter!("x", initial: 0.4));
let y = laddu_expr::Expr::from(parameter!("y", initial: -0.2));
let expression = complex(x.clone().sin(), y.clone().exp()).norm_sqr() + (x * y).cos();
let model = CompiledModel::from_expr(&expression).unwrap();
let params = Arc::new(model.params().clone()).default_values();
let plan = CpuBackend.prepare(&model);
let result = plan.evaluate_with_gradient(¶ms).unwrap();
let ir_result = gradient_interpreter::GradientInterpreter::new(
plan.scalar_kernel.as_ref().unwrap(),
model.params().free_params(),
)
.unwrap()
.evaluate(¶ms, None)
.unwrap()
.1;
for (actual, expected) in ir_result.iter().zip(result.gradient()) {
assert!(
(actual - expected).norm() < 1.0e-12,
"{actual} != {expected}"
);
}
for (parameter, derivative) in result.gradient().iter().enumerate() {
let expected = finite_difference(&plan, ¶ms, parameter);
assert!((derivative - expected).norm() < 1.0e-8);
}
}
#[test]
fn reverse_gradients_match_forward_for_scalar_complex_operations() {
let x = laddu_expr::Expr::from(parameter!("x", initial: 0.8));
let y = laddu_expr::Expr::from(parameter!("y", initial: -0.3));
let z = complex(x.clone(), y.clone());
let expression = x.clone().sqrt()
+ x.clone().log()
+ x.clone().powi(-2)
+ x.clone().sin()
+ x.clone().cos()
+ x.clone().exp()
+ z.clone().conj()
+ z.clone().real()
+ z.clone().imag()
+ z.norm_sqr()
+ atan2(y.clone(), x.clone())
+ x * y;
let model = CompiledModel::from_expr(&expression).unwrap();
let params = Arc::new(model.params().clone()).default_values();
let forward = CpuBackend.prepare_with_execution_mode(&model, CpuExecutionMode::Interpreter);
let reverse = CpuBackend
.prepare_with_autodiff_mode(&model, AutodiffMode::Reverse)
.unwrap();
let expected = forward.evaluate_with_gradient(¶ms).unwrap();
let actual = reverse.evaluate_with_gradient(¶ms).unwrap();
assert!((actual.value() - expected.value()).norm() < 1.0e-12);
assert_gradient_close(actual.gradient(), expected.gradient(), 1.0e-12);
}
#[test]
fn reverse_gradients_match_forward_for_structured_linear_algebra() {
let a = laddu_expr::Expr::from(parameter!("a", initial: 0.7));
let b = laddu_expr::Expr::from(parameter!("b", initial: -0.2));
let c = laddu_expr::Expr::from(parameter!("c", initial: 1.1));
let d = laddu_expr::Expr::from(parameter!("d", initial: 0.4));
let x = laddu_expr::Expr::from(parameter!("x", initial: -0.3));
let y = laddu_expr::Expr::from(parameter!("y", initial: 0.9));
let left = matrix([
[a.clone(), complex(b.clone(), 0.2)],
[1.3.into(), c.clone()],
]);
let right = matrix([[complex(0.5, -0.1), d.clone()], [b.clone(), 0.8.into()]]);
let product = matmul(left, right);
let input_vector = vector([x.clone(), complex(y.clone(), -0.4)]);
let projected = matvec(product.clone(), input_vector);
let expression = dot(projected.clone(), vector([complex(0.25, 0.3), c.clone()]))
+ product.matrix_element(1, 0)
+ projected.component(1);
let model = CompiledModel::from_expr(&expression).unwrap();
let params = Arc::new(model.params().clone()).default_values();
let forward = CpuBackend.prepare_with_execution_mode(&model, CpuExecutionMode::Interpreter);
let reverse = CpuBackend
.prepare_with_autodiff_mode(&model, AutodiffMode::Reverse)
.unwrap();
let expected = forward.evaluate_with_gradient(¶ms).unwrap();
let actual = reverse.evaluate_with_gradient(¶ms).unwrap();
assert!((actual.value() - expected.value()).norm() < 1.0e-12);
assert_gradient_close(actual.gradient(), expected.gradient(), 1.0e-12);
}
#[test]
fn reverse_gradients_match_forward_for_parameter_dependent_solve() {
let a = laddu_expr::Expr::from(parameter!("a", initial: 2.0));
let b = laddu_expr::Expr::from(parameter!("b", initial: 0.3));
let r = laddu_expr::Expr::from(parameter!("r", initial: 1.2));
let solution = solve(
matrix([[a, complex(b.clone(), 0.1)], [b, 1.7.into()]]),
vector([r, complex(0.5, -0.1)]),
);
let expression = dot(solution, vector([complex(1.0, 0.2), (-0.4).into()]));
let model = CompiledModel::from_expr(&expression).unwrap();
let params = Arc::new(model.params().clone()).default_values();
let forward = CpuBackend.prepare_with_execution_mode(&model, CpuExecutionMode::Interpreter);
let reverse = CpuBackend
.prepare_with_autodiff_mode(&model, AutodiffMode::Reverse)
.unwrap();
let expected = forward.evaluate_with_gradient(¶ms).unwrap();
let actual = reverse.evaluate_with_gradient(¶ms).unwrap();
assert!((actual.value() - expected.value()).norm() < 1.0e-12);
assert_gradient_close(actual.gradient(), expected.gradient(), 1.0e-12);
}
#[test]
fn reverse_cached_event_gradients_match_forward() {
let x = event_scalar("x");
let scale = laddu_expr::Expr::from(parameter!("scale", initial: 0.4));
let phase = laddu_expr::Expr::from(parameter!("phase", initial: -0.2));
let expression =
complex((x.clone() * &scale).sin(), (x.clone() + phase).cos()).norm_sqr() + x * scale;
let model = CompiledModel::from_expr(&expression).unwrap();
let params = Arc::new(model.params().clone()).default_values();
let forward = CpuBackend.prepare_with_execution_mode(&model, CpuExecutionMode::Interpreter);
let reverse = CpuBackend
.prepare_with_autodiff_mode(&model, AutodiffMode::Reverse)
.unwrap();
let batch = EventBatch::from_events(
Arc::new(Schema::new(std::iter::empty::<&str>(), ["x"], false).unwrap()),
[
OwnedEvent::new(vec![], vec![0.25]),
OwnedEvent::new(vec![], vec![0.75]),
OwnedEvent::new(vec![], vec![1.25]),
],
)
.unwrap();
let expected = forward
.evaluate_cache_with_gradient(¶ms, &forward.cache_event_batch(&batch).unwrap())
.unwrap();
let actual = reverse
.evaluate_cache_with_gradient(¶ms, &reverse.cache_event_batch(&batch).unwrap())
.unwrap();
for (actual, expected) in actual.iter().zip(&expected) {
assert!((actual.value() - expected.value()).norm() < 1.0e-12);
assert_gradient_close(actual.gradient(), expected.gradient(), 1.0e-12);
}
}
#[test]
fn reverse_cached_event_materialization_is_a_leaf() {
let event_sum = event_scalar("x") + event_scalar("y").sin();
let scale = laddu_expr::Expr::from(parameter!("scale", initial: 1.25));
let expression = scale * event_sum;
let model = CompiledModel::from_expr(&expression).unwrap();
let params = Arc::new(model.params().clone()).default_values();
let forward = CpuBackend.prepare_with_execution_mode(&model, CpuExecutionMode::Interpreter);
let reverse = CpuBackend
.prepare_with_autodiff_mode(&model, AutodiffMode::Reverse)
.unwrap();
let batch = EventBatch::from_events(
Arc::new(Schema::new(std::iter::empty::<&str>(), ["x", "y"], false).unwrap()),
[OwnedEvent::new(vec![], vec![0.5, 0.25])],
)
.unwrap();
assert!(
reverse
.cache_slots
.iter()
.enumerate()
.any(|(index, slot)| slot.is_some() && reverse.cached_value_slots[index].is_some())
);
let expected = forward
.evaluate_cache_row_with_gradient(¶ms, &forward.cache_event_batch(&batch).unwrap(), 0)
.unwrap();
let actual = reverse
.evaluate_cache_row_with_gradient(¶ms, &reverse.cache_event_batch(&batch).unwrap(), 0)
.unwrap();
assert_eq!(actual.value(), expected.value());
assert_gradient_close(actual.gradient(), expected.gradient(), 1.0e-12);
}
#[test]
fn reverse_f32_gradients_match_forward() {
let expression = laddu_expr::Expr::from(parameter!("x", initial: 0.4)).sin();
let model = CompiledModel::from_expr(&expression).unwrap();
let params = Arc::new(model.params().clone()).default_values();
let reverse = CpuBackend
.prepare_with_modes_precision(
&model,
AutodiffMode::Reverse,
CpuExecutionMode::Interpreter,
Precision::F32,
)
.unwrap();
let forward = CpuBackend
.prepare_with_modes_precision(
&model,
AutodiffMode::Forward,
CpuExecutionMode::Interpreter,
Precision::F32,
)
.unwrap();
assert_eq!(
reverse.evaluate_with_gradient(¶ms).unwrap(),
forward.evaluate_with_gradient(¶ms).unwrap()
);
}
#[test]
fn forward_gradients_cover_unary_atan2_and_zero_products() {
let x = laddu_expr::Expr::from(parameter!("x", initial: 0.8));
let y = laddu_expr::Expr::from(parameter!("y", initial: 0.0));
let z = complex(x.clone(), y.clone());
let expression = x.clone().sqrt()
+ x.clone().log()
+ x.clone().powi(-2)
+ x.clone().sin()
+ x.clone().cos()
+ x.clone().exp()
+ z.clone().conj().real()
+ z.clone().imag()
+ z.norm_sqr()
+ atan2(y.clone(), x.clone())
+ y * x;
let model = CompiledModel::from_expr(&expression).unwrap();
let params = Arc::new(model.params().clone()).default_values();
let plan = CpuBackend.prepare(&model);
let result = plan.evaluate_with_gradient(¶ms).unwrap();
for (parameter, derivative) in result.gradient().iter().enumerate() {
let expected = finite_difference(&plan, ¶ms, parameter);
assert!((derivative - expected).norm() < 1.0e-7);
}
}
#[test]
fn forward_gradients_cover_matrix_vector_and_dot_operations() {
let a = laddu_expr::Expr::from(parameter!("a", initial: 0.7));
let b = laddu_expr::Expr::from(parameter!("b", initial: -0.2));
let c = laddu_expr::Expr::from(parameter!("c", initial: 1.1));
let d = laddu_expr::Expr::from(parameter!("d", initial: 0.4));
let x = laddu_expr::Expr::from(parameter!("x", initial: -0.3));
let y = laddu_expr::Expr::from(parameter!("y", initial: 0.9));
let left = matrix([
[a.clone(), complex(b.clone(), 0.2)],
[1.3.into(), c.clone()],
]);
let right = matrix([[complex(0.5, -0.1), d.clone()], [b.clone(), 0.8.into()]]);
let product = matmul(left, right);
let input_vector = vector([x.clone(), complex(y.clone(), -0.4)]);
let projected = matvec(product.clone(), input_vector);
let expression = dot(projected.clone(), vector([complex(0.25, 0.3), c.clone()]))
+ product.matrix_element(1, 0)
+ projected.component(1);
let model = CompiledModel::from_expr(&expression).unwrap();
let params = Arc::new(model.params().clone()).default_values();
let plan = CpuBackend.prepare(&model);
let result = plan.evaluate_with_gradient(¶ms).unwrap();
for (parameter, derivative) in result.gradient().iter().enumerate() {
let expected = finite_difference(&plan, ¶ms, parameter);
assert!(
(derivative - expected).norm() < 1.0e-7,
"{derivative} != {expected}"
);
}
}
#[test]
fn solve_gradients_match_finite_differences_for_matrix_and_rhs_parameters() {
let a = laddu_expr::Expr::from(parameter!("a", initial: 2.0));
let b = laddu_expr::Expr::from(parameter!("b", initial: 0.3));
let r = laddu_expr::Expr::from(parameter!("r", initial: 1.2));
let solution = solve(
matrix([[a, b], [0.2.into(), 1.7.into()]]),
vector([r, complex(0.5, -0.1)]),
);
let expression = dot(solution, vector([complex(1.0, 0.2), (-0.4).into()]));
let model = CompiledModel::from_expr(&expression).unwrap();
let params = Arc::new(model.params().clone()).default_values();
let plan = CpuBackend.prepare(&model);
let result = plan.evaluate_with_gradient(¶ms).unwrap();
for (parameter, derivative) in result.gradient().iter().enumerate() {
let expected = finite_difference(&plan, ¶ms, parameter);
assert!((derivative - expected).norm() < 1.0e-8);
}
}
#[test]
fn cpu_f32_direct_event_gradient_matches_cached_event_gradient() {
let scale = laddu_expr::Expr::from(parameter!("scale", initial: 1.5));
let offset = laddu_expr::Expr::from(parameter!("offset", initial: -0.25));
let x = event_scalar("x");
let expression =
(x.clone().sin() * scale.clone() + offset.clone()).exp() + complex(scale, x).norm_sqr();
let model = CompiledModel::from_expr(&expression).unwrap();
let execution = Execution::local(crate::ExecutionOptions {
device: crate::Device::Cpu(crate::CpuOptions::default()),
precision: Precision::F32,
..crate::ExecutionOptions::default()
})
.unwrap();
let plan = CpuBackend
.prepare_for_execution(&model, &execution)
.unwrap();
let params = model.params().default_values();
let event = HashMap::from([("x".to_owned(), 0.75)]);
let batch = EventBatch::from_events(
Arc::new(Schema::new(std::iter::empty::<&str>(), ["x"], false).unwrap()),
[OwnedEvent::new(vec![], vec![0.75])],
)
.unwrap();
let cache = plan.cache_event_batch(&batch).unwrap();
let direct_value = plan.evaluate_with_event(¶ms, &event).unwrap();
let direct = plan
.evaluate_with_event_and_gradient(¶ms, &event)
.unwrap();
let cached_value = plan.evaluate_cache_row(¶ms, &cache, 0).unwrap();
let cached = plan
.evaluate_cache_row_with_gradient(¶ms, &cache, 0)
.unwrap();
assert_eq!(direct_value, cached_value);
assert_eq!(direct.value(), cached.value());
assert_eq!(direct.gradient(), cached.gradient());
}