use super::*;
fn reshape_via_context(
backend: &mut CpuEngine,
input: &Tensor,
shape: &[usize],
) -> tenferro_tensor::Result<Tensor> {
with_cpu_session(backend, |cpu| {
cpu.with_linalg_pool(|exec, buffers| crate::reshape_tensor(exec, buffers, input, shape))
})
}
fn assert_exact<T>(threads: usize, from_shape: &[usize], to_shape: &[usize], values: Vec<T>)
where
T: tenferro_tensor::TensorScalar + PartialEq + core::fmt::Debug,
{
let mut backend = crate::test_support::cpu_engine(threads).unwrap();
let input = Tensor::from_typed::<T>(
TypedTensor::from_vec_col_major(from_shape.to_vec(), values.clone()).unwrap(),
);
let out = reshape_via_context(&mut backend, &input, to_shape)
.unwrap_or_else(|error| panic!("threads={threads}: {error}"));
assert_eq!(out.shape(), to_shape, "threads={threads}");
assert_eq!(out.dtype(), input.dtype(), "threads={threads}");
assert_eq!(
out.as_slice::<T>().unwrap(),
values.as_slice(),
"threads={threads}"
);
}
fn patterned<T>(len: usize, make: impl Fn(usize) -> T) -> Vec<T> {
(0..len).map(make).collect()
}
#[test]
fn reshape_tensor_is_exact_for_every_preset_dtype_above_the_threshold() {
const N: usize = 256;
let from = [N, N];
let to = [2 * N, N / 2];
let len = N * N;
for threads in [1, 4] {
assert_exact::<f32>(threads, &from, &to, patterned(len, |i| i as f32));
assert_exact::<f64>(threads, &from, &to, patterned(len, |i| i as f64));
assert_exact::<i32>(threads, &from, &to, patterned(len, |i| i as i32 - 1000));
assert_exact::<i64>(threads, &from, &to, patterned(len, |i| i as i64 - 1000));
assert_exact::<bool>(threads, &from, &to, patterned(len, |i| i % 3 == 0));
assert_exact::<Complex32>(
threads,
&from,
&to,
patterned(len, |i| Complex32::new(i as f32, -(i as f32) / 2.0)),
);
assert_exact::<Complex64>(
threads,
&from,
&to,
patterned(len, |i| Complex64::new(i as f64, -(i as f64) / 2.0)),
);
}
}
#[test]
fn reshape_tensor_recycles_a_pool_buffer_only_on_the_parallel_branch() {
const R: usize = 2048;
const C: usize = 1024;
let values: Vec<f64> = (0..R * C).map(|i| i as f64).collect();
let input =
Tensor::from_typed::<f64>(TypedTensor::from_vec_col_major(vec![R, C], values).unwrap());
{
let pool = threadteam::Pool::builder().threads(4).build().unwrap();
let token = threadteam::Exec::new(&pool, 4).unwrap();
assert!(
crate::structural::reshape_uses_pool(&input, &token).unwrap(),
"the payload must pay for a four-thread fork"
);
}
for (threads, expect_checkout) in [(1usize, false), (4usize, true)] {
let mut backend = crate::test_support::cpu_engine(threads).unwrap();
assert_eq!(
backend.buffer_pool_len(),
0,
"threads={threads}: a fresh engine pool starts empty"
);
let out = reshape_via_context(&mut backend, &input, &[C, R]).unwrap();
assert_eq!(out.as_slice::<f64>().unwrap().len(), R * C);
drop(out);
let retained = backend.buffer_pool_len();
assert_eq!(
retained == 1,
expect_checkout,
"threads={threads}: retained={retained}; the pooled branch returns one buffer and \
the serial branch returns none"
);
}
}
#[test]
fn reshape_read_reads_a_non_contiguous_view_in_logical_order() {
const N: usize = 256;
let storage: Vec<f64> = (0..N * N).map(|i| i as f64).collect();
let expected: Vec<f64> = (0..N * N).map(|k| ((k / N) + N * (k % N)) as f64).collect();
for threads in [1, 4] {
let mut backend = crate::test_support::cpu_engine(threads).unwrap();
let view = TypedTensorView::from_col_major(&[N, N], &storage)
.unwrap()
.transpose_view([1, 0])
.unwrap();
let out = crate::test_support::on_engine(&mut backend, |session| {
session.reshape_read(TensorRead::from_view(TensorView::F64(view)), &[N * N])
})
.unwrap()
.unwrap();
assert_eq!(out.shape(), &[N * N], "threads={threads}");
assert_eq!(
out.as_slice::<f64>().unwrap(),
expected.as_slice(),
"threads={threads}"
);
}
}
#[cfg(feature = "ledger")]
#[test]
fn a_small_compact_reshape_on_a_multi_thread_engine_takes_no_engine_entry() {
let input = Tensor::from_typed::<f64>(
TypedTensor::from_vec_col_major(vec![4, 6], (0..24).map(f64::from).collect()).unwrap(),
);
let mut backend = crate::test_support::cpu_engine(4).unwrap();
let _ = backend.take_ledger_roots();
let out = crate::test_support::on_engine(&mut backend, |session| {
session.reshape_read(TensorRead::from_tensor(&input), &[6, 4])
})
.unwrap()
.unwrap();
assert_eq!(out.shape(), &[6, 4]);
assert_eq!(
out.as_slice::<f64>().unwrap(),
input.as_slice::<f64>().unwrap()
);
assert!(
backend.take_ledger_roots().is_empty(),
"a small compact reshape must not mint a token"
);
drop(out);
assert_eq!(backend.buffer_pool_len(), 0);
let big = Tensor::from_typed::<f64>(
TypedTensor::from_vec_col_major(vec![2048, 1024], vec![1.0; 2048 * 1024]).unwrap(),
);
let _ = crate::test_support::on_engine(&mut backend, |session| {
session.reshape_read(TensorRead::from_tensor(&big), &[1024, 2048])
})
.unwrap()
.unwrap();
assert!(!backend.take_ledger_roots().is_empty());
}