Add some tests for walking

This commit is contained in:
Joshua Coles 2023-03-17 19:46:15 +00:00
parent c8fcce839b
commit 81562d6b28
2 changed files with 127 additions and 42 deletions

View File

@ -3,7 +3,7 @@ use num_integer::Integer;
use crate::system::{GriddedPosition, Position}; use crate::system::{GriddedPosition, Position};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct Grid2D { pub struct Grid2D {
pub x: i32, pub x: i32,
pub y: i32, pub y: i32,

View File

@ -1,6 +1,10 @@
use std::hash::Hash;
use bevy::render::render_resource::encase::private::RuntimeSizedArray;
use itertools::Itertools;
use rand::distributions::Slice;
use rand::prelude::Rng; use rand::prelude::Rng;
use crate::system::{GriddedPosition, Position}; use crate::system::{GriddedPosition, Position};
use crate::system::spaces::square_grid::Grid3D; use crate::system::spaces::square_grid::{Grid2D, Grid3D};
pub trait Walker<P: Position> { pub trait Walker<P: Position> {
fn walk<R: Rng>(&self, rng: &mut R, position: &P) -> P; fn walk<R: Rng>(&self, rng: &mut R, position: &P) -> P;
@ -18,14 +22,131 @@ pub struct DiagonalRandomWalker;
impl Walker<Grid3D> for DiagonalRandomWalker { impl Walker<Grid3D> for DiagonalRandomWalker {
fn walk<R: Rng>(&self, rng: &mut R, position: &Grid3D) -> Grid3D { fn walk<R: Rng>(&self, rng: &mut R, position: &Grid3D) -> Grid3D {
let a: Vec<i32> = (0..3) static OFFSETS: [Grid3D; 26] = [
.map(|r| rng.gen_range(-1..=1)) Grid3D { x: 1, y: 0, z: 0 },
Grid3D { x: 1, y: 1, z: 0 },
Grid3D { x: 1, y: -1, z: 0 },
Grid3D { x: -1, y: 0, z: 0 },
Grid3D { x: -1, y: 1, z: 0 },
Grid3D { x: -1, y: -1, z: 0 },
Grid3D { x: 0, y: 1, z: 0 },
Grid3D { x: 0, y: -1, z: 0 },
Grid3D { x: 1, y: 0, z: -1 },
Grid3D { x: 1, y: 1, z: -1 },
Grid3D { x: 1, y: -1, z: -1 },
Grid3D { x: -1, y: 0, z: -1 },
Grid3D { x: -1, y: 1, z: -1 },
Grid3D { x: -1, y: -1, z: -1 },
Grid3D { x: 0, y: 1, z: -1 },
Grid3D { x: 0, y: -1, z: -1 },
Grid3D { x: 0, y: 0, z: -1 },
Grid3D { x: 1, y: 0, z: 1 },
Grid3D { x: 1, y: 1, z: 1 },
Grid3D { x: 1, y: -1, z: 1 },
Grid3D { x: -1, y: 0, z: 1 },
Grid3D { x: -1, y: 1, z: 1 },
Grid3D { x: -1, y: -1, z: 1 },
Grid3D { x: 0, y: 1, z: 1 },
Grid3D { x: 0, y: -1, z: 1 },
Grid3D { x: 0, y: 0, z: 1 },
];
position.clone() + OFFSETS[rng.gen_range(0..OFFSETS.len())].clone()
}
}
impl Walker<Grid2D> for DiagonalRandomWalker {
fn walk<R: Rng>(&self, rng: &mut R, position: &Grid2D) -> Grid2D {
static OFFSETS: [Grid2D; 8] = [
Grid2D { x: 1, y: 0 },
Grid2D { x: 1, y: 1 },
Grid2D { x: 1, y: -1 },
Grid2D { x: -1, y: 0 },
Grid2D { x: -1, y: 1 },
Grid2D { x: -1, y: -1 },
Grid2D { x: 0, y: 1 },
Grid2D { x: 0, y: -1 },
];
position.clone() + OFFSETS[rng.gen_range(0..OFFSETS.len())].clone()
}
}
fn test_uniformity_and_range<W: Walker<P>, P>(walker: W, expected: &[P], n: usize, tolerance: f32) where P: GriddedPosition + Hash + Eq {
use rand::thread_rng;
let mut rng = thread_rng();
let origin = &P::zero();
let results: Vec<P> = (0..n)
.map(|_| walker.walk(&mut rng, origin))
.collect(); .collect();
position.clone() + Grid3D::from_cartesian([a[0] as f32, a[1] as f32, a[2] as f32]) let groups = results.iter()
.into_group_map_by(|a| (*a).clone());
assert_eq!(groups.len(), expected.len(), "Wrong number of walk positions generated");
assert!(results.iter().unique().all(|a| expected.contains(a)), "Contains unexpected walk position");
for group in groups.values() {
let proportion = group.len() as f32 / n as f32;
assert!((proportion - (1.0 / expected.len() as f32)).abs() < tolerance, "Failed tolerance check");
} }
} }
#[test]
fn uniformity_direct_grid2d() {
test_uniformity_and_range(LocalRandomWalker, &[
Grid2D { x: 1, y: 0 },
Grid2D { x: -1, y: 0 },
Grid2D { x: 0, y: 1 },
Grid2D { x: 0, y: -1 },
], 1_000_000, 0.001);
}
#[test]
fn uniformity_direct_grid3d() {
test_uniformity_and_range(LocalRandomWalker, &[
Grid3D { x: 1, y: 0, z: 0 },
Grid3D { x: -1, y: 0, z: 0 },
Grid3D { x: 0, y: 1, z: 0 },
Grid3D { x: 0, y: -1, z: 0 },
Grid3D { x: 0, y: 0, z: 1 },
Grid3D { x: 0, y: 0, z: -1 },
], 1_000_000, 0.001);
}
#[test]
fn diagonal_grid2d() {
let mut expected = Vec::new();
for x in -1..=1 {
for y in -1..=1 {
if !(x == 0 && y == 0) {
expected.push(Grid2D { x, y })
}
}
}
test_uniformity_and_range(DiagonalRandomWalker, &expected, 1_000_000, 0.001);
}
#[test]
fn diagonal_grid3d() {
let mut expected = Vec::new();
for x in -1..=1 {
for y in -1..=1 {
for z in -1..=1 {
if !(x == 0 && y == 0 && z == 0) {
expected.push(Grid3D { x, y, z })
}
}
}
}
test_uniformity_and_range(DiagonalRandomWalker, &expected, 1_000_000, 0.001);
}
mod test { mod test {
use rand::rngs::SmallRng; use rand::rngs::SmallRng;
use rand::{SeedableRng, thread_rng}; use rand::{SeedableRng, thread_rng};
@ -33,42 +154,6 @@ mod test {
use crate::system::spaces::square_grid::{Grid2D, Grid3D}; use crate::system::spaces::square_grid::{Grid2D, Grid3D};
use crate::system::walker::{DiagonalRandomWalker, LocalRandomWalker, Walker}; use crate::system::walker::{DiagonalRandomWalker, LocalRandomWalker, Walker};
#[test]
fn uniformity() {
let walker = LocalRandomWalker;
let mut rng = SmallRng::from_rng(thread_rng()).unwrap();
let mut results: Vec<Grid2D> = vec![];
let origin = &Grid2D::zero();
let x: u32 = (1_000_000);
for i in 0..x {
results.push(walker.walk(&mut rng, origin));
}
let a = results
.iter()
.filter(|a| **a == Grid2D { x: 0, y: 1 })
.count();
let b = results
.iter()
.filter(|a| **a == Grid2D { x: 0, y: -1 })
.count();
let c = results
.iter()
.filter(|a| **a == Grid2D { x: 1, y: 0 })
.count();
let d = results
.iter()
.filter(|a| **a == Grid2D { x: -1, y: 0 })
.count();
println!("{} {} {} {}", a as f32 / x as f32, b as f32 / x as f32, c as f32 / x as f32, d as f32 / x as f32);
}
#[test] #[test]
fn diagonal() { fn diagonal() {
let drw = DiagonalRandomWalker; let drw = DiagonalRandomWalker;