rustsim-spaces 0.0.1

Space implementations (grid, continuous, graph, hybrid) for rustsim
Documentation
use rand::rngs::StdRng;
use rand::SeedableRng;
use rustsim_core::interaction::{PositionedAgent, SpaceInteraction};
use rustsim_core::prelude::*;
use rustsim_spaces::continuous3d::{ContinuousPos3D, ContinuousSpace3D};

#[derive(Debug, Clone)]
struct Bot3D {
    id: AgentId,
    pos: ContinuousPos3D,
}

impl Agent for Bot3D {
    fn id(&self) -> AgentId {
        self.id
    }
}

impl PositionedAgent for Bot3D {
    type Position = ContinuousPos3D;
    fn position(&self) -> &ContinuousPos3D {
        &self.pos
    }
    fn set_position(&mut self, p: ContinuousPos3D) {
        self.pos = p;
    }
}

#[test]
fn add_and_nearby_3d() {
    let mut space = ContinuousSpace3D::new(100.0, 100.0, 100.0, false, 10.0).unwrap();

    let a = Bot3D {
        id: 1,
        pos: ContinuousPos3D::new(50.0, 50.0, 50.0),
    };
    let b = Bot3D {
        id: 2,
        pos: ContinuousPos3D::new(52.0, 50.0, 50.0),
    };
    let c = Bot3D {
        id: 3,
        pos: ContinuousPos3D::new(90.0, 90.0, 90.0),
    };

    SpaceInteraction::<Bot3D>::add_agent(&mut space, &a).unwrap();
    SpaceInteraction::<Bot3D>::add_agent(&mut space, &b).unwrap();
    SpaceInteraction::<Bot3D>::add_agent(&mut space, &c).unwrap();

    let near = space.nearby_ids_euclidean(&ContinuousPos3D::new(50.0, 50.0, 50.0), 5.0);
    assert!(near.contains(&1));
    assert!(near.contains(&2));
    assert!(!near.contains(&3));
}

#[test]
fn remove_3d() {
    let mut space = ContinuousSpace3D::new(100.0, 100.0, 100.0, false, 10.0).unwrap();

    let a = Bot3D {
        id: 1,
        pos: ContinuousPos3D::new(10.0, 10.0, 10.0),
    };
    SpaceInteraction::<Bot3D>::add_agent(&mut space, &a).unwrap();

    let near = space.nearby_ids_euclidean(&ContinuousPos3D::new(10.0, 10.0, 10.0), 5.0);
    assert_eq!(near.len(), 1);

    SpaceInteraction::<Bot3D>::remove_agent(&mut space, &a).unwrap();

    let near = space.nearby_ids_euclidean(&ContinuousPos3D::new(10.0, 10.0, 10.0), 5.0);
    assert!(near.is_empty());
}

#[test]
fn move_updates_grid_3d() {
    let mut space = ContinuousSpace3D::new(100.0, 100.0, 100.0, false, 10.0).unwrap();

    let a = Bot3D {
        id: 1,
        pos: ContinuousPos3D::new(5.0, 5.0, 5.0),
    };
    SpaceInteraction::<Bot3D>::add_agent(&mut space, &a).unwrap();

    // Move far away
    space
        .move_agent_pos(1, ContinuousPos3D::new(95.0, 95.0, 95.0))
        .unwrap();

    // No longer near origin
    let near_origin = space.nearby_ids_euclidean(&ContinuousPos3D::new(5.0, 5.0, 5.0), 5.0);
    assert!(!near_origin.contains(&1));

    // Now near new position
    let near_new = space.nearby_ids_euclidean(&ContinuousPos3D::new(95.0, 95.0, 95.0), 5.0);
    assert!(near_new.contains(&1));
}

#[test]
fn periodic_wrapping_3d() {
    let mut space = ContinuousSpace3D::new(100.0, 100.0, 100.0, true, 10.0).unwrap();

    let a = Bot3D {
        id: 1,
        pos: ContinuousPos3D::new(1.0, 1.0, 1.0),
    };
    SpaceInteraction::<Bot3D>::add_agent(&mut space, &a).unwrap();

    // Query from near the opposite edge; with wrapping, distance should be ~3*sqrt(2) < 5
    let near = space.nearby_ids_euclidean(&ContinuousPos3D::new(99.0, 99.0, 99.0), 5.0);
    assert!(
        near.contains(&1),
        "periodic wrapping: agent at (1,1,1) should be near (99,99,99)"
    );
}

#[test]
fn random_position_3d() {
    let space = ContinuousSpace3D::new(50.0, 60.0, 70.0, false, 5.0).unwrap();
    let mut rng = StdRng::seed_from_u64(42);

    for _ in 0..100 {
        let pos = SpaceInteraction::<Bot3D>::random_position(&space, &mut rng);
        assert!(pos.x >= 0.0 && pos.x < 50.0);
        assert!(pos.y >= 0.0 && pos.y < 60.0);
        assert!(pos.z >= 0.0 && pos.z < 70.0);
    }
}

#[test]
fn distance_and_ops() {
    let a = ContinuousPos3D::new(1.0, 2.0, 3.0);
    let b = ContinuousPos3D::new(4.0, 6.0, 3.0);

    assert!((a.distance_to(&b) - 5.0).abs() < 1e-10);

    let sum = a + b;
    assert!((sum.x - 5.0).abs() < 1e-10);
    assert!((sum.y - 8.0).abs() < 1e-10);
    assert!((sum.z - 6.0).abs() < 1e-10);

    let diff = b - a;
    assert!((diff.x - 3.0).abs() < 1e-10);
    assert!((diff.y - 4.0).abs() < 1e-10);
    assert!((diff.z - 0.0).abs() < 1e-10);

    let scaled = a * 2.0;
    assert!((scaled.x - 2.0).abs() < 1e-10);
    assert!((scaled.y - 4.0).abs() < 1e-10);
    assert!((scaled.z - 6.0).abs() < 1e-10);

    let norm = ContinuousPos3D::new(0.0, 3.0, 4.0).normalized();
    assert!((norm.length() - 1.0).abs() < 1e-10);
}