tenferro-runtime 0.2.0

Concrete tensor helpers, traced tensors, graph execution, and extension runtime support for tenferro.
Documentation
use crate::{DType, DotGeneralConfig, TracedTensor};

#[test]
fn dot_general_returns_error_for_invalid_config() {
    let lhs = TracedTensor::from_vec_col_major(vec![2, 3], vec![1.0_f64; 6]).unwrap();
    let rhs = TracedTensor::from_vec_col_major(vec![3, 2], vec![1.0_f64; 6]).unwrap();
    let config = DotGeneralConfig {
        lhs_contracting_dims: vec![2],
        rhs_contracting_dims: vec![0],
        lhs_batch_dims: vec![],
        rhs_batch_dims: vec![],
    };

    let Err(err) = lhs.dot_general(&rhs, config) else {
        panic!("invalid dim config should be a typed runtime error");
    };

    let message = err.to_string();
    assert!(
        message.contains("lhs_contracting_dim 2 out of bounds"),
        "{message}"
    );
}

#[test]
fn integer_scale_real_rejects_non_finite_factors() {
    let x = TracedTensor::from_vec_col_major(vec![2], vec![1_i64, 2]).unwrap();

    for factor in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
        let err = x.scale_real(factor).unwrap_err();
        assert!(
            err.to_string().contains("finite"),
            "expected finite-value error, got {err:?}"
        );
    }
}

#[test]
fn dot_general_keeps_existing_success_metadata() {
    let lhs = TracedTensor::from_vec_col_major(vec![2, 3], vec![1.0_f64; 6]).unwrap();
    let rhs = TracedTensor::from_vec_col_major(vec![3, 4], vec![1.0_f64; 12]).unwrap();
    let config = DotGeneralConfig {
        lhs_contracting_dims: vec![1],
        rhs_contracting_dims: vec![0],
        lhs_batch_dims: vec![],
        rhs_batch_dims: vec![],
    };

    let out = lhs
        .dot_general(&rhs, config)
        .expect("valid dot_general config should build a traced tensor");

    assert_eq!(out.rank, 2);
    assert_eq!(out.try_concrete_shape().unwrap(), &[2, 4]);
}

#[test]
fn reductions_reject_invalid_axes_instead_of_saturating_rank() {
    let x = TracedTensor::from_vec_col_major(vec![2, 3], vec![1.0_f64; 6]).unwrap();

    let out_of_bounds = x.reduce_max(&[2]).unwrap_err();
    assert!(
        out_of_bounds
            .to_string()
            .contains("axis 2 out of bounds for rank 2"),
        "{out_of_bounds}"
    );

    let duplicate = x.reduce_min(&[0, 0]).unwrap_err();
    assert!(
        duplicate.to_string().contains("duplicate reduction axis 0"),
        "{duplicate}"
    );

    let y = x.reduce_sum(&[1]).unwrap();
    assert_eq!(y.rank, 1);
    assert_eq!(y.try_concrete_shape().unwrap(), &[2]);
}

#[test]
fn symbolic_axis_accessors_reject_invalid_axes() {
    let x = TracedTensor::from_vec_col_major(vec![2, 3], vec![1.0_f64; 6]).unwrap();

    let sym_size_err = x.sym_size(2).unwrap_err();
    assert!(
        sym_size_err
            .to_string()
            .contains("axis 2 out of bounds for rank 2"),
        "{sym_size_err}"
    );

    let axis_err = x.axis_sym_dim(2).unwrap_err();
    assert!(
        axis_err
            .to_string()
            .contains("axis 2 out of bounds for rank 2"),
        "{axis_err}"
    );

    assert_eq!(x.axis_sym_dim(0).unwrap().constant_value(), Some(2));
}

#[test]
fn broadcast_in_dim_sym_rejects_missing_shape_reference() {
    let lhs = TracedTensor::input_symbolic_shape(DType::F64, 1).unwrap();
    let rhs = TracedTensor::input_symbolic_shape(DType::F64, 1).unwrap();
    let target_shape = [lhs.axis_sym_dim(0).unwrap(), rhs.axis_sym_dim(0).unwrap()];

    let err = lhs
        .broadcast_in_dim_sym(&target_shape, &[0], &[])
        .unwrap_err();

    let message = err.to_string();
    assert!(
        message.contains("broadcast_in_dim_sym")
            && message.contains("unresolved symbolic dimension")
            && message.contains("shape_refs"),
        "{message}"
    );
}

#[test]
fn broadcast_in_dim_rejects_invalid_dimension_mappings() {
    let x = TracedTensor::from_vec_col_major(vec![2, 3], vec![1.0_f64; 6]).unwrap();

    let rank_mismatch = x.broadcast_in_dim(&[2, 3, 4], &[0]).unwrap_err();
    assert!(
        rank_mismatch
            .to_string()
            .contains("dims length 1 must match input rank 2"),
        "{rank_mismatch}"
    );

    let out_of_bounds = x.broadcast_in_dim(&[2, 3, 4], &[0, 3]).unwrap_err();
    assert!(
        out_of_bounds
            .to_string()
            .contains("broadcast dim 3 out of bounds for output rank 3"),
        "{out_of_bounds}"
    );

    let duplicate = x.broadcast_in_dim(&[2, 3, 4], &[1, 1]).unwrap_err();
    assert!(
        duplicate.to_string().contains("duplicate broadcast dim 1"),
        "{duplicate}"
    );

    let valid = x.broadcast_in_dim(&[2, 3, 4], &[0, 1]).unwrap();
    assert_eq!(valid.rank, 3);
    assert_eq!(valid.try_concrete_shape().unwrap(), &[2, 3, 4]);
}