physics_in_parallel 3.7.0

High-performance infrastructure for numerical simulations in physics
Documentation
use physics_in_parallel::prelude::advanced::{Dense, RowMajorLayout, Sparse};
use physics_in_parallel::prelude::basic::{ComputePool, ComputePoolError, Tensor, with_threads};

#[test]
fn explicit_compute_pools_are_fixed_size_and_opt_in() {
    assert!(matches!(
        ComputePool::new(0),
        Err(ComputePoolError::ZeroThreads)
    ));

    let pool = ComputePool::new(2).unwrap();
    assert_eq!(pool.threads(), 2);
    assert_eq!(pool.install(rayon::current_num_threads), 2);
    assert_eq!(with_threads(3, rayon::current_num_threads).unwrap(), 3);
}

#[test]
fn row_major_layout_round_trips_flat_and_wrapped_coordinates() {
    let layout = RowMajorLayout::try_new(&[2, 3, 5]).unwrap();
    assert_eq!(layout.size(), 30);
    assert_eq!(layout.strides(), [15, 5, 1]);

    let mut coordinate = [0; 3];
    for flat in 0..layout.size() {
        layout.coordinate_into(flat, &mut coordinate);
        assert_eq!(layout.index_wrapped(&coordinate), flat);
        assert_eq!(layout.coordinate(flat).unwrap(), coordinate);
    }
    assert_eq!(layout.index_wrapped(&[-1, -1, -1]), 29);
    assert_eq!(layout.index_wrapped(&[2, 3, 5]), 0);
    assert_eq!(layout.coordinate(layout.size()), None);
}

#[test]
fn dense_get_set_and_periodic_indexing_are_consistent() {
    let mut t = Tensor::<i64, Dense>::empty(&[2, 3]);
    t.set(&[0, 0], 10);
    t.set(&[0, 1], 11);
    t.set(&[0, 2], 12);
    t.set(&[1, 0], 20);
    t.set(&[1, 1], 21);
    t.set(&[1, 2], 22);

    assert_eq!(t.get(&[0, 0]), 10);
    assert_eq!(t.get(&[1, 2]), 22);
    assert_eq!(t.get(&[-1, -1]), 22);
    assert_eq!(t.get(&[2, 3]), 10);
    assert_eq!(t.get(&[-1, -1]), t.get(&[1, 2]));

    assert_eq!(t.flat_index(&[-1, -1]), 5);
    assert_eq!(t.get_flat(-1), 22);
    *t.get_flat_mut(6) = 99;
    assert_eq!(t.get(&[0, 0]), 99);
    t.set_flat(-2, 77);
    assert_eq!(t.get(&[1, 1]), 77);
}

#[test]
fn tensor_layout_rejects_shapes_outside_signed_index_space() {
    let error = RowMajorLayout::try_new(&[isize::MAX as usize, 2]).unwrap_err();
    assert!(matches!(
        error,
        physics_in_parallel::prelude::basic::TensorError::IndexSpaceOverflow { .. }
            | physics_in_parallel::prelude::basic::TensorError::ShapeProductOverflow { .. }
    ));
}

#[test]
fn dense_mutable_reference_updates_target_slot() {
    let mut t = Tensor::<i64, Dense>::empty(&[2, 2]);
    t.set(&[0, 0], 1);
    t.set(&[0, 1], 2);
    t.set(&[1, 0], 3);
    t.set(&[1, 1], 4);

    {
        let slot = t.get_mut(&[-1, -1]);
        *slot = 99;
    }

    assert_eq!(t.get(&[1, 1]), 99);
    assert_eq!(t.get(&[-1, -1]), 99);

    t.set(&[2, 2], -5);
    assert_eq!(t.get(&[0, 0]), -5);
}

#[test]
fn dense_contiguous_ownership_and_allocation_reuse_are_explicit() {
    let mut target = Tensor::<i64>::try_from_vec(&[2, 3], vec![0; 6]).unwrap();
    let left = Tensor::<i64>::try_from_vec(&[2, 3], vec![1, 2, 3, 4, 5, 6]).unwrap();
    let right = Tensor::<i64>::try_from_vec(&[2, 3], vec![6, 5, 4, 3, 2, 1]).unwrap();

    target.zip_from(&left, &right, |a, b| a + b).unwrap();
    assert_eq!(target.as_slice(), &[7; 6]);
    target.as_mut_slice()[0] = 9;
    assert_eq!(target.get_flat(0), 9);

    target.copy_from(&left).unwrap();
    assert_eq!(target, left);
    assert_eq!(target.sum_serial(), 21);
}

#[test]
fn dense_fallible_construction_and_copy_report_layout_errors() {
    assert!(matches!(
        Tensor::<f64>::try_from_vec(&[2, 2], vec![1.0, 2.0, 3.0]),
        Err(
            physics_in_parallel::prelude::basic::TensorError::DataLengthMismatch {
                expected: 4,
                actual: 3
            }
        )
    ));

    let mut target = Tensor::<f64>::zeros(&[2, 2]);
    let source = Tensor::<f64>::zeros(&[4]);
    assert!(matches!(
        target.copy_from(&source),
        Err(physics_in_parallel::prelude::basic::TensorError::ShapeMismatch { .. })
    ));
}

#[test]
fn dense_adaptive_chunk_traversal_visits_each_chunk_once() {
    let mut values = Tensor::<usize>::zeros(&[5_000, 4]);
    values.for_each_chunk_mut(4, |row, chunk| {
        for (column, value) in chunk.iter_mut().enumerate() {
            *value = row * 4 + column;
        }
    });
    assert_eq!(values.as_slice(), (0..20_000).collect::<Vec<_>>());
}

#[test]
fn sparse_get_set_zero_pruning_and_wrapping_are_consistent() {
    let mut t = Tensor::<i64, Sparse>::empty(&[2, 3]);
    t.set(&[0, 0], 10);
    t.set(&[1, 2], 22);

    assert_eq!(t.nnz(), 2);
    assert_eq!(t.get(&[0, 1]), 0);
    assert_eq!(t.get(&[-1, -1]), 22);
    assert_eq!(t.get(&[2, 3]), 10);

    t.set(&[-1, -1], 0);
    assert_eq!(t.get(&[1, 2]), 0);
    assert_eq!(t.nnz(), 1);

    t.set(&[2, 3], 33);
    assert_eq!(t.get(&[0, 0]), 33);
}

#[test]
fn sparse_mutable_reference_materializes_and_prune_removes_zero() {
    let mut t = Tensor::<i64, Sparse>::empty(&[2, 2]);
    assert_eq!(t.nnz(), 0);

    {
        let slot = t.get_mut(&[1, 1]);
        assert_eq!(*slot, 0);
        *slot = 7;
    }

    assert_eq!(t.nnz(), 1);
    assert_eq!(t.get(&[1, 1]), 7);

    {
        let slot = t.get_mut(&[-1, -1]);
        *slot = 0;
    }

    assert_eq!(t.get(&[1, 1]), 0);
    assert_eq!(t.nnz(), 1);
    t.set(&[1, 1], 0);
    assert_eq!(t.nnz(), 0);
}

#[test]
fn generic_public_access_updates_backend_without_exposing_storage() {
    let mut dense = Tensor::<i64, Dense>::empty(&[1, 2]);
    dense.set(&[0, 0], 5);
    dense.set(&[0, 1], 6);
    assert_eq!(dense.shape(), &[1, 2]);
    *dense.get_mut(&[0, 1]) = 60;
    assert_eq!(dense.get(&[0, 1]), 60);

    let mut sparse = Tensor::<i64, Sparse>::empty(&[1, 2]);
    sparse.set(&[0, 1], 9);
    assert_eq!(sparse.shape(), &[1, 2]);
    assert_eq!(sparse.get(&[0, 1]), 9);
    assert_eq!(sparse.nnz(), 1);
}