use super::*;
#[test]
fn cpu_f32_evaluates_computed_event_cache_entries() {
let scale = laddu_expr::Expr::from(parameter!("scale", initial: 2.0));
let model =
CompiledModel::from_expr(&(event_scalar("x").sin() * scale + 16_777_216.0)).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 dataset = Dataset::from_batch(
EventBatch::from_events(
Arc::new(Schema::new(std::iter::empty::<&str>(), ["x"], false).unwrap()),
[OwnedEvent::new(vec![], vec![1.0])],
)
.unwrap(),
);
let prepared = plan.prepare_dataset(&execution, &dataset).unwrap();
let reduction = plan
.reduce_with_gradient(
&execution,
¶ms,
&prepared,
ReductionPlan::weighted_real(),
)
.unwrap();
assert_eq!(reduction.value(), 16_777_218.0);
assert_eq!(reduction.gradient(), &[(1.0_f32.sin() as f64)]);
}
#[test]
fn batch_cache_evaluates_without_original_event_batch() {
let expr = event_scalar("x").real().sin() * parameter!("scale", initial: 2.0);
let model = CompiledModel::from_expr(&expr).unwrap();
let layout = Arc::new(model.params().clone());
let mut params = layout.default_values();
let plan = CpuBackend.prepare_with_execution_mode(&model, CpuExecutionMode::Interpreter);
let batch = EventBatch::from_events(
Arc::new(Schema::new(std::iter::empty::<&str>(), ["x"], false).unwrap()),
[
OwnedEvent::new(vec![], vec![0.5]),
OwnedEvent::new(vec![], vec![1.0]),
],
)
.unwrap();
let cache = plan.cache_event_batch(&batch).unwrap();
assert_eq!(cache.weights(), &[1.0, 1.0]);
assert!(matches!(&cache.slots[0], CachedSlot::Real(values) if values.len() == 2));
assert_eq!(cache.slots[0].resident_bytes(), 2 * size_of::<f64>());
assert_eq!(
plan.evaluate_cache(¶ms, &cache).unwrap(),
vec![
Complex64::from(2.0 * 0.5_f64.sin()),
Complex64::from(2.0 * 1.0_f64.sin())
]
);
let scale = layout
.free_id(layout.id("scale").unwrap())
.unwrap()
.unwrap();
params.set_free(scale, 3.0).unwrap();
assert_eq!(
plan.evaluate_cache(¶ms, &cache).unwrap(),
vec![
Complex64::from(3.0 * 0.5_f64.sin()),
Complex64::from(3.0 * 1.0_f64.sin())
]
);
}
#[test]
fn real_cache_slots_use_half_the_scalar_payload_of_complex_slots() {
let real_model =
CompiledModel::from_expr(&(parameter!("scale") * event_scalar("x").real().sin())).unwrap();
let x = event_scalar("x");
let complex_model =
CompiledModel::from_expr(&(parameter!("scale") * complex(x.clone().sin(), x.cos())))
.unwrap();
let batch = EventBatch::from_events(
Arc::new(Schema::new(std::iter::empty::<&str>(), ["x"], false).unwrap()),
[
OwnedEvent::new(vec![], vec![0.5]),
OwnedEvent::new(vec![], vec![1.0]),
],
)
.unwrap();
let real_cache = CpuBackend
.prepare(&real_model)
.cache_event_batch(&batch)
.unwrap();
let complex_cache = CpuBackend
.prepare(&complex_model)
.cache_event_batch(&batch)
.unwrap();
assert!(matches!(&real_cache.slots[0], CachedSlot::Real(_)));
assert!(matches!(&complex_cache.slots[0], CachedSlot::Complex(_)));
assert_eq!(
complex_cache.slots[0].resident_bytes(),
2 * real_cache.slots[0].resident_bytes()
);
}
#[test]
fn selected_event_only_solve_components_cache_inverse_rows() {
let expression = solve(
matrix([[event_scalar("x") + 2.0]]),
vector([parameter!("rhs", initial: 3.0)]),
)
.component(0);
let model = CompiledModel::from_expr(&expression).unwrap();
let params = Arc::new(model.params().clone()).default_values();
let plan = CpuBackend.prepare_with_execution_mode(&model, CpuExecutionMode::Interpreter);
let scalar_plan = plan.scalar_interpreter_plan().unwrap();
assert!(!scalar_plan.invariant_instructions.is_empty());
assert!(!scalar_plan.event_instructions.is_empty());
let batch = EventBatch::from_events(
Arc::new(Schema::new(std::iter::empty::<&str>(), ["x"], false).unwrap()),
[
OwnedEvent::new(vec![], vec![0.0]),
OwnedEvent::new(vec![], vec![1.0]),
],
)
.unwrap();
let cache = plan.cache_event_batch(&batch).unwrap();
assert!(cache.factor_slots.is_empty());
assert_eq!(cache.solve_row_slots.len(), 1);
assert_eq!(cache.solve_row_slots[0].values.len(), 2);
assert!(cache.resident_bytes() > 0);
let first = plan
.evaluate_cache_row_with_gradient(¶ms, &cache, 0)
.unwrap();
let second = plan
.evaluate_cache_row_with_gradient(¶ms, &cache, 1)
.unwrap();
assert_eq!(first.value(), Complex64::from(1.5));
assert_eq!(first.gradient(), &[Complex64::from(0.5)]);
assert_eq!(second.value(), Complex64::from(1.0));
assert_eq!(second.gradient(), &[Complex64::from(1.0 / 3.0)]);
}
#[test]
fn cached_solve_component_matches_general_complex_nonsymmetric_solve() {
let expression = solve(
matrix([
[event_scalar("x") + 2.0, Complex64::I.into()],
[Complex64::new(2.0, -1.0).into(), 3.0.into()],
]),
vector([
parameter!("p", initial: 1.5),
parameter!("q", initial: -0.25),
]),
)
.component(1);
let model = CompiledModel::from_expr(&expression).unwrap();
let params = Arc::new(model.params().clone()).default_values();
let plan = CpuBackend.prepare(&model);
assert!(plan.solve_components.iter().any(Option::is_some));
let event = HashMap::from([("x".to_owned(), 0.75)]);
let direct = plan
.evaluate_with_event_and_gradient(¶ms, &event)
.unwrap();
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 cached = plan
.evaluate_cache_row_with_gradient(¶ms, &cache, 0)
.unwrap();
let ir_gradient = gradient_interpreter::GradientInterpreter::new(
plan.scalar_kernel.as_ref().unwrap(),
model.params().free_params(),
)
.unwrap()
.evaluate(¶ms, Some((&cache, 0)))
.unwrap()
.1;
assert!((cached.value() - direct.value()).norm() < 1.0e-12);
for (cached, direct) in cached.gradient().iter().zip(direct.gradient()) {
assert!((cached - direct).norm() < 1.0e-12);
}
for (actual, expected) in ir_gradient.iter().zip(cached.gradient()) {
assert!((actual - expected).norm() < 1.0e-12);
}
}
#[test]
fn reverse_cached_solve_component_matches_forward_nonsymmetric_solve() {
let expression = solve(
matrix([
[event_scalar("x") + 2.0, Complex64::I.into()],
[Complex64::new(2.0, -1.0).into(), 3.0.into()],
]),
vector([
parameter!("p", initial: 1.5),
parameter!("q", initial: -0.25),
]),
)
.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();
assert!(reverse.solve_components.iter().any(Option::is_some));
let batch = EventBatch::from_events(
Arc::new(Schema::new(std::iter::empty::<&str>(), ["x"], false).unwrap()),
[OwnedEvent::new(vec![], vec![0.75])],
)
.unwrap();
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!((actual.value() - expected.value()).norm() < 1.0e-12);
assert_gradient_close(actual.gradient(), expected.gradient(), 1.0e-12);
}
#[test]
fn batch_cache_reports_missing_event_columns() {
let expr = event_scalar("missing");
let model = CompiledModel::from_expr(&expr).unwrap();
let plan = CpuBackend.prepare(&model);
let batch = EventBatch::from_events(
Arc::new(Schema::new(std::iter::empty::<&str>(), ["x"], false).unwrap()),
[OwnedEvent::new(vec![], vec![0.5])],
)
.unwrap();
assert!(matches!(
plan.cache_event_batch(&batch),
Err(RuntimeError::MissingEventColumn(name)) if name == "missing"
));
}
#[test]
fn cached_dataset_preserves_transformed_batches_and_weights() {
let expr = event_scalar("x") * parameter!("scale", initial: 2.0);
let model = CompiledModel::from_expr(&expr).unwrap();
let params = Arc::new(model.params().clone()).default_values();
let plan = CpuBackend.prepare(&model);
let schema = Arc::new(Schema::new(std::iter::empty::<&str>(), ["x"], true).unwrap());
let batch = EventBatch::from_events(
schema,
[
OwnedEvent::weighted(vec![], vec![0.5], 2.0),
OwnedEvent::weighted(vec![], vec![1.0], 3.0),
],
)
.unwrap();
let dataset = Dataset::from_batch(batch).filter(|event| event.scalar(0) > 0.75);
let cached = plan.cache_dataset(&dataset).unwrap();
assert_eq!(cached.len(), 1);
assert_eq!(cached.batches()[0].weights(), &[3.0]);
assert_eq!(cached.batches()[0].sum_weights(), 3.0);
assert_eq!(
plan.evaluate_cached_dataset(¶ms, &cached).unwrap(),
vec![Complex64::from(2.0)]
);
}
#[test]
fn cached_dataset_weighted_reductions_match_dataset_path() {
let expr = event_scalar("x") * parameter!("scale", initial: 2.0);
let model = CompiledModel::from_expr(&expr).unwrap();
let params = Arc::new(model.params().clone()).default_values();
let plan = CpuBackend.prepare(&model);
let schema = Arc::new(Schema::new(std::iter::empty::<&str>(), ["x"], true).unwrap());
let first = EventBatch::from_events(
Arc::clone(&schema),
[
OwnedEvent::weighted(vec![], vec![1.0], 2.0),
OwnedEvent::weighted(vec![], vec![2.0], 3.0),
],
)
.unwrap();
let second =
EventBatch::from_events(schema, [OwnedEvent::weighted(vec![], vec![3.0], 4.0)]).unwrap();
let dataset = Dataset::from_batches(vec![first, second]).unwrap();
let cached = plan.cache_dataset(&dataset).unwrap();
let expected = dataset.weighted_sum(|event| 2.0 * event.scalar(0)).unwrap();
assert_eq!(cached.sum_weights(), dataset.sum_weights().unwrap());
assert_eq!(
plan.weighted_sum_cached(¶ms, &cached, |value| value.re)
.unwrap(),
expected
);
assert_eq!(
plan.weighted_complex_sum_cached(¶ms, &cached, |value| value * Complex64::I)
.unwrap(),
Complex64::I * expected
);
assert_eq!(
plan.par_weighted_sum_cached(¶ms, &cached, |value| value.re)
.unwrap(),
expected
);
assert_eq!(
plan.par_weighted_complex_sum_cached(¶ms, &cached, |value| value * Complex64::I)
.unwrap(),
Complex64::I * expected
);
let serial_gradient = plan
.try_weighted_real_sum_with_gradient_cached(¶ms, &cached, |value| {
Ok::<_, RuntimeError>((value.re.powi(2), 2.0 * value.re))
})
.unwrap();
let parallel_gradient = plan
.par_try_weighted_real_sum_with_gradient_cached(¶ms, &cached, |value| {
Ok::<_, RuntimeError>((value.re.powi(2), 2.0 * value.re))
})
.unwrap();
assert_eq!(serial_gradient, parallel_gradient);
}