molframe-spatial 0.5.2

Deterministic spatial indices and neighbour search for molframe
Documentation
use super::*;

#[test]
fn contiguous_cells_find_only_pairs_inside_the_cutoff() {
    let positions = [[0.0, 0.0, 0.0], [0.9, 0.0, 0.0], [4.0, 0.0, 0.0]];
    let list = match CellList::build(&positions, &[0, 1, 2], 1.0, None) {
        Ok(list) => list,
        Err(error) => panic!("build failed: {error}"),
    };
    let pairs = match list.pairs(&[0, 1, 2], 1.0) {
        Ok(pairs) => pairs,
        Err(error) => panic!("query failed: {error}"),
    };
    assert_eq!(pairs.len(), 1);
    assert_eq!((pairs[0].first, pairs[0].second), (0, 1));
}

#[test]
fn a_query_cannot_exceed_the_cutoff_the_grid_was_built_for() {
    let positions = [[0.0, 0.0, 0.0]];
    let list = match CellList::build(&positions, &[0], 1.0, None) {
        Ok(list) => list,
        Err(error) => panic!("build failed: {error}"),
    };
    assert_eq!(list.pairs(&[0], 2.0), Err(SpatialError::InvalidCutoff));
}

#[test]
fn cell_grid_coarsening_obeys_the_public_memory_policy() {
    let options = CellGridOptions {
        maximum_cell_count: 1,
        edge_growth_factor: 4.0,
    };
    let Ok((_, dimensions, count)) =
        geometry::grid_geometry([0.0, 0.0, 0.0], [3.0, 3.0, 3.0], 1.0, options)
    else {
        panic!("finite test geometry must be representable");
    };
    assert_eq!(dimensions, [1, 1, 1]);
    assert_eq!(count, 1);
}

#[test]
fn query_coordinates_below_target_bounds_clamp_to_the_first_cell() {
    let positions = [[0.0, 0.0, 0.0], [0.5, 0.0, 0.0], [1.0, 0.0, 0.0]];
    let list = match CellList::build(&positions, &[2], 1.0, None) {
        Ok(list) => list,
        Err(error) => panic!("build failed: {error}"),
    };
    let pairs = match list.pairs(&[0, 1], 1.0) {
        Ok(pairs) => pairs,
        Err(error) => panic!("query failed: {error}"),
    };
    assert_eq!(pairs.len(), 2);
}

#[test]
fn arbitrary_points_include_coincident_targets_and_obey_the_radius() {
    let positions = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.01, 0.0, 0.0]];
    let list = CellList::build(&positions, &[0, 1, 2], 1.0, None).unwrap();
    let mut neighbors = Vec::new();
    list.for_each_neighbor([0.0; 3], 1.0, |atom, squared| {
        neighbors.push((atom, squared));
    })
    .unwrap();
    neighbors.sort_by_key(|(atom, _)| *atom);
    assert_eq!(neighbors, [(0, 0.0), (1, 1.0)]);
    neighbors.clear();
    list.for_each_neighbor([-0.5, 0.0, 0.0], 1.0, |atom, squared| {
        neighbors.push((atom, squared));
    })
    .unwrap();
    assert_eq!(neighbors, [(0, 0.25)]);
    assert_eq!(
        list.for_each_neighbor([f32::NAN; 3], 1.0, |_, _| {}),
        Err(SpatialError::NumericRangeExceeded)
    );
}

#[test]
fn arbitrary_periodic_points_include_wrapped_targets_once() {
    let positions = [[0.1, 0.0, 0.0], [5.0, 0.0, 0.0]];
    let periodic = PeriodicBox::from_cell(molframe_core::structure::UnitCell {
        lengths: [10.0; 3],
        angles: [90.0; 3],
    })
    .unwrap();
    let list = CellList::build(&positions, &[0, 1], 1.0, Some(periodic)).unwrap();
    let mut neighbors = Vec::new();
    list.for_each_neighbor([9.9, 0.0, 0.0], 1.0, |atom, squared| {
        neighbors.push((atom, squared));
    })
    .unwrap();
    assert_eq!(neighbors.len(), 1);
    assert_eq!(neighbors[0].0, 0);
    assert!((neighbors[0].1 - 0.04).abs() < 1.0e-5);
}