mod support;
use support::RunTraced;
use tenferro_cpu::CpuBackend;
use tenferro_runtime::error::Error;
use tenferro_runtime::{GraphCompiler, GraphExecutor, Tensor, TracedTensor};
use tenferro_tensor::DType;
#[test]
fn unexpected_binding_for_data_carrying_leaf() {
let x = TracedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
let y = x.clone();
let mut engine = GraphExecutor::new(CpuBackend::new());
let extra = Tensor::from_vec_col_major(vec![2], vec![9.0_f64, 9.0]).unwrap();
let err = y
.run_with_inputs_auto(&mut engine, &[(&x, &extra)])
.expect_err("binding a non-placeholder must fail");
assert!(
matches!(err, Error::UnexpectedBinding { binding_index: 0 }),
"got {err:?}"
);
}
#[test]
fn unbound_placeholder() {
let x = TracedTensor::input_symbolic_shape(DType::F64, 1).unwrap();
let y = x.clone();
let mut engine = GraphExecutor::new(CpuBackend::new());
let err = y
.run_with_inputs_auto(&mut engine, &[])
.expect_err("unbound placeholder must fail");
assert!(
matches!(err, Error::UnboundPlaceholder { .. }),
"got {err:?}"
);
}
#[test]
fn duplicate_binding() {
let x = TracedTensor::input_symbolic_shape(DType::F64, 1).unwrap();
let y = x.clone();
let bound = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
let mut engine = GraphExecutor::new(CpuBackend::new());
let err = y
.run_with_inputs_auto(&mut engine, &[(&x, &bound), (&x, &bound)])
.expect_err("duplicate binding must fail");
assert!(matches!(err, Error::DuplicateBinding { .. }), "got {err:?}");
}
#[test]
fn placeholder_dtype_mismatch() {
let x = TracedTensor::input_symbolic_shape(DType::F64, 1).unwrap();
let y = x.clone();
let wrong_dtype = Tensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0]).unwrap();
let mut engine = GraphExecutor::new(CpuBackend::new());
let err = y
.run_with_inputs_auto(&mut engine, &[(&x, &wrong_dtype)])
.expect_err("dtype mismatch must fail");
assert!(
matches!(
err,
Error::PlaceholderDtypeMismatch {
expected: DType::F64,
actual: DType::F32
}
),
"got {err:?}"
);
}
#[test]
fn placeholder_shape_mismatch_for_concrete_shape_placeholder() {
let x = TracedTensor::input_concrete_shape(DType::F64, &[2, 3]).unwrap();
let y = x.clone();
let wrong_shape = Tensor::from_vec_col_major(vec![3, 2], vec![1.0_f64; 6]).unwrap();
let mut engine = GraphExecutor::new(CpuBackend::new());
let err = y
.run_with_inputs_auto(&mut engine, &[(&x, &wrong_shape)])
.expect_err("shape mismatch must fail");
match err {
Error::PlaceholderShapeMismatch { expected, actual } => {
assert_eq!(expected, vec![2, 3]);
assert_eq!(actual, vec![3, 2]);
}
other => panic!("expected PlaceholderShapeMismatch, got {other:?}"),
}
}
#[test]
fn placeholder_rank_mismatch_for_symbolic_shape_placeholder() {
let x = TracedTensor::input_symbolic_shape(DType::F64, 2).unwrap();
let y = x.clone();
let wrong_rank = Tensor::from_vec_col_major(vec![4], vec![1.0_f64; 4]).unwrap();
let mut engine = GraphExecutor::new(CpuBackend::new());
let err = y
.run_with_inputs_auto(&mut engine, &[(&x, &wrong_rank)])
.expect_err("rank mismatch must fail");
assert!(
matches!(
err,
Error::PlaceholderRankMismatch {
expected: 2,
actual: 1
}
),
"got {err:?}"
);
}
#[test]
fn symbolic_shape_placeholder_accepts_any_shape_of_matching_rank() {
let x = TracedTensor::input_symbolic_shape(DType::F64, 1).unwrap();
let y = x.clone();
let mut engine = GraphExecutor::new(CpuBackend::new());
let bound = Tensor::from_vec_col_major(vec![7], vec![1.0_f64; 7]).unwrap();
let out = y
.run_with_inputs_auto(&mut engine, &[(&x, &bound)])
.expect("rank-only placeholder accepts arbitrary shape of that rank");
assert_eq!(out.shape(), &[7]);
}
#[test]
fn executor_run_with_inputs_validates_and_uses_bound_tensors() {
let x = TracedTensor::input_symbolic_shape(DType::F64, 1).unwrap();
let y = (&x + &x).unwrap();
let bound = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler
.compile_with_input_specs(&y, &[(&x, DType::F64, &[2])])
.expect("symbolic placeholder binding should compile");
assert_eq!(program.output_count(), 1);
let mut executor = GraphExecutor::new(CpuBackend::new());
let out = executor.run_with_inputs(&program, &[(&x, &bound)]).unwrap();
assert_eq!(out.shape(), &[2]);
assert_eq!(out.as_slice::<f64>().unwrap(), &[2.0, 4.0]);
let err = executor.run_with_inputs(&program, &[]).unwrap_err();
assert!(matches!(err, Error::UnboundPlaceholder { .. }));
let err = executor
.run_with_inputs(&program, &[(&x, &bound), (&x, &bound)])
.unwrap_err();
assert!(matches!(err, Error::DuplicateBinding { .. }));
let concrete = TracedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
let err = executor
.run_with_inputs(&program, &[(&concrete, &bound)])
.unwrap_err();
assert!(matches!(err, Error::UnexpectedBinding { binding_index: 0 }));
}
#[test]
fn compile_with_input_specs_validates_specs_without_tensor_values() {
let x = TracedTensor::input_symbolic_shape(DType::F64, 1).unwrap();
let y = (&x + &x).unwrap();
let shape = [2usize];
let mut compiler = GraphCompiler::new();
let program = compiler
.compile_with_input_specs(&y, &[(&x, DType::F64, shape.as_slice())])
.expect("symbolic placeholder spec should compile");
assert_eq!(program.output_count(), 1);
let err = compiler
.compile_with_input_specs(&y, &[(&x, DType::F32, shape.as_slice())])
.unwrap_err();
assert!(matches!(
err,
Error::PlaceholderDtypeMismatch {
expected: DType::F64,
actual: DType::F32
}
));
let wrong_rank = [2usize, 1usize];
let err = compiler
.compile_with_input_specs(&y, &[(&x, DType::F64, wrong_rank.as_slice())])
.unwrap_err();
assert!(matches!(
err,
Error::PlaceholderRankMismatch {
expected: 1,
actual: 2
}
));
let concrete = TracedTensor::input_concrete_shape(DType::F64, &[2]).unwrap();
let concrete_y = concrete.clone();
let wrong_shape = [3usize];
let err = compiler
.compile_with_input_specs(
&concrete_y,
&[(&concrete, DType::F64, wrong_shape.as_slice())],
)
.unwrap_err();
assert!(matches!(err, Error::PlaceholderShapeMismatch { .. }));
let data_leaf = TracedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
let err = compiler
.compile_with_input_specs(&data_leaf, &[(&data_leaf, DType::F64, shape.as_slice())])
.unwrap_err();
assert!(matches!(err, Error::UnexpectedBinding { binding_index: 0 }));
let err = compiler
.compile_with_input_specs(
&y,
&[
(&x, DType::F64, shape.as_slice()),
(&x, DType::F64, shape.as_slice()),
],
)
.unwrap_err();
assert!(matches!(err, Error::DuplicateBinding { .. }));
let err = compiler.compile_with_input_specs(&y, &[]).unwrap_err();
assert!(matches!(err, Error::UnboundPlaceholder { .. }));
}