sim-lib-numbers-tensor 0.2.0

Uniform n-dimensional tensor value, constructors, and specialization hooks for SIM numbers.
Documentation
use sim_kernel::{Args, DefaultFactory, Factory};

use crate::{
    Tensor, build_tensor_value, cast_symbol, cast_tensor, domains, parse_f32_literal_cell,
    parse_i64_literal_cell, tensor_value_ref,
};

use super::{number, test_cx};

#[test]
fn tensor_cast_to_f32_rounds_and_preserves_signed_zero_and_specials() {
    let mut cx = test_cx();
    let tensor = build_tensor_value(
        &mut cx,
        vec![4],
        Some(domains::f64()),
        vec![
            number("f64", "16777217"),
            number("f64", "-0"),
            number("f64", "inf"),
            number("f64", "NaN"),
        ],
    )
    .unwrap();
    let tensor = tensor_value_ref(&tensor).unwrap();
    let cast = cast_tensor(tensor, domains::f32()).unwrap();
    assert_eq!(cast.dtype(), &domains::f32());
    let cells = cast.cells().unwrap();
    assert_eq!(parse_f32_literal_cell(&cells[0]).unwrap(), 16_777_216.0);
    assert!(
        parse_f32_literal_cell(&cells[1])
            .unwrap()
            .is_sign_negative()
    );
    assert!(parse_f32_literal_cell(&cells[2]).unwrap().is_infinite());
    assert!(parse_f32_literal_cell(&cells[3]).unwrap().is_nan());
}

#[test]
fn tensor_cast_to_i64_uses_ties_even_and_checks_specials() {
    let mut cx = test_cx();
    let tensor = build_tensor_value(
        &mut cx,
        vec![3],
        Some(domains::f64()),
        vec![
            number("f64", "2.5"),
            number("f64", "3.5"),
            number("f64", "-2.5"),
        ],
    )
    .unwrap();
    let cast = cast_tensor(tensor_value_ref(&tensor).unwrap(), domains::i64()).unwrap();
    let cells = cast.cells().unwrap();
    assert_eq!(
        cells
            .iter()
            .map(parse_i64_literal_cell)
            .collect::<Option<Vec<_>>>()
            .unwrap(),
        vec![2, 4, -2]
    );

    let non_finite = build_tensor_value(
        &mut cx,
        vec![1],
        Some(domains::f64()),
        vec![number("f64", "inf")],
    )
    .unwrap();
    let err = match cast_tensor(tensor_value_ref(&non_finite).unwrap(), domains::i64()) {
        Ok(_) => panic!("non-finite cast to i64 must fail"),
        Err(err) => err,
    };
    assert!(err.to_string().contains("non-finite"));
}

#[test]
fn tensor_cast_to_half_rejects_finite_overflow() {
    let mut cx = test_cx();
    let tensor = build_tensor_value(
        &mut cx,
        vec![3],
        Some(domains::f32()),
        vec![
            number("f32", "1.5"),
            number("f32", "-0"),
            number("f32", "inf"),
        ],
    )
    .unwrap();
    let half = cast_tensor(tensor_value_ref(&tensor).unwrap(), domains::f16()).unwrap();
    assert_eq!(half.dtype(), &domains::f16());
    let widened = cast_tensor(&half, domains::f32()).unwrap();
    let cells = widened.cells().unwrap();
    assert_eq!(parse_f32_literal_cell(&cells[0]).unwrap(), 1.5);
    assert!(
        parse_f32_literal_cell(&cells[1])
            .unwrap()
            .is_sign_negative()
    );
    assert!(parse_f32_literal_cell(&cells[2]).unwrap().is_infinite());

    let overflowing = build_tensor_value(
        &mut cx,
        vec![1],
        Some(domains::f32()),
        vec![number("f32", "70000")],
    )
    .unwrap();
    let err = match cast_tensor(tensor_value_ref(&overflowing).unwrap(), domains::f16()) {
        Ok(_) => panic!("finite f16 overflow must fail"),
        Err(err) => err,
    };
    assert!(err.to_string().contains("overflows"));
}

#[test]
fn tensor_cast_function_is_explicit_and_rejects_unsupported_domains() {
    let mut cx = test_cx();
    let tensor = build_tensor_value(
        &mut cx,
        vec![2],
        Some(domains::f64()),
        vec![number("f64", "1.25"), number("f64", "2.5")],
    )
    .unwrap();
    let cast = cx
        .call_function(
            &cast_symbol(),
            Args::new(vec![tensor, DefaultFactory.symbol(domains::f32()).unwrap()]),
        )
        .unwrap();
    let cast = tensor_value_ref(&cast).unwrap();
    assert_eq!(cast.dtype(), &domains::f32());
    let cells = cast.cells().unwrap();
    assert_eq!(parse_f32_literal_cell(&cells[0]).unwrap(), 1.25);

    let complex =
        Tensor::new_exact(vec![1], domains::complex(), vec![number("complex", "1+2i")]).unwrap();
    let err = match cast_tensor(&complex, domains::f32()) {
        Ok(_) => panic!("complex source cast to f32 must fail"),
        Err(err) => err,
    };
    assert!(err.to_string().contains("source dtype"));

    let err = match cast_tensor(cast, domains::bool()) {
        Ok(_) => panic!("unsupported target dtype must fail"),
        Err(err) => err,
    };
    assert!(err.to_string().contains("target dtype"));
}