Skip to content

Commit

Permalink
Move arbitrary_with_max_volume() from GridAab to Vol.
Browse files Browse the repository at this point in the history
This is part of the plan to unrestrict `GridAab` and move all
volume-involving operations to `Vol`.
  • Loading branch information
kpreid committed Dec 18, 2023
1 parent 74b9638 commit 4357f5a
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 99 deletions.
93 changes: 2 additions & 91 deletions all-is-cubes/src/math/grid_aab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,49 +187,6 @@ impl GridAab {
}
}

/// Generate a [`GridAab`] whose volume is as specified or smaller.
#[cfg(feature = "arbitrary")]
pub(crate) fn arbitrary_with_max_volume(
u: &mut arbitrary::Unstructured<'_>,
volume: usize,
) -> arbitrary::Result<Self> {
// Pick sizes within the volume constraint.
let mut limit: GridCoordinate = volume.try_into().unwrap_or(GridCoordinate::MAX);
let size_1 = u.int_in_range(0..=limit)?;
limit /= size_1.max(1);
let size_2 = u.int_in_range(0..=limit)?;
limit /= size_2.max(1);
let size_3 = u.int_in_range(0..=limit)?;

// Shuffle the sizes to remove any bias.
let sizes = *u.choose(&[
GridVector::new(size_1, size_2, size_3),
GridVector::new(size_1, size_3, size_2),
GridVector::new(size_2, size_1, size_3),
GridVector::new(size_2, size_3, size_1),
GridVector::new(size_3, size_1, size_2),
GridVector::new(size_3, size_2, size_1),
])?;

// Compute lower bounds that are valid for the sizes.
let lower_bounds = GridPoint::new(
u.int_in_range(GridCoordinate::MIN..=GridCoordinate::MAX - sizes.x)?,
u.int_in_range(GridCoordinate::MIN..=GridCoordinate::MAX - sizes.y)?,
u.int_in_range(GridCoordinate::MIN..=GridCoordinate::MAX - sizes.z)?,
);

Ok(Self::from_lower_size(lower_bounds, sizes))
}

#[cfg(feature = "arbitrary")]
const ARBITRARY_SIZE_HINT: (usize, Option<usize>) = {
// 6 bounding coordinates plus one permutation selection.
// Depending on the volume we could *maybe* end up consuming only 1 byte each
// for the sizes.
let gc = core::mem::size_of::<GridCoordinate>();
((gc + 1) * 3 + 1, Some(gc * 6 + 1))
};

/// Compute volume with checked arithmetic. In a function solely for the convenience
/// of the `?` operator without which this is even worse.
fn checked_volume_helper(sizes: GridVector) -> Result<usize, ()> {
Expand Down Expand Up @@ -744,11 +701,11 @@ impl From<GridAab> for Aab {
#[mutants::skip]
impl<'a> arbitrary::Arbitrary<'a> for GridAab {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Self::arbitrary_with_max_volume(u, usize::MAX)
Ok(Vol::<()>::arbitrary_with_max_volume(u, usize::MAX)?.bounds())
}

fn size_hint(_depth: usize) -> (usize, Option<usize>) {
GridAab::ARBITRARY_SIZE_HINT
crate::math::vol::vol_arb::ARBITRARY_BOUNDS_SIZE_HINT
}
}

Expand Down Expand Up @@ -1113,50 +1070,4 @@ mod tests {
assert_eq!(item, Some(expected_sequence[current])); // sanity check, not what we're testing
}
}

#[cfg(feature = "arbitrary")]
#[test]
fn arbitrary_grid_aab_size_hint() {
use arbitrary::{Arbitrary, Unstructured};
let hint = GridAab::ARBITRARY_SIZE_HINT;
let most_bytes_used = (0..=255)
.map(|byte| {
// TODO: sketchy coverage; would be better to generate some random/hashed data
let data = [byte; 1000];
let mut u = Unstructured::new(&data);
GridAab::arbitrary(&mut u).unwrap();
let bytes_used = 1000 - u.len();
assert!(
bytes_used >= hint.0,
"used {}, less than {}",
bytes_used,
hint.0
);
bytes_used
})
.max();
assert_eq!(most_bytes_used, hint.1);

// TODO: Also look at the resulting Grids and see if they're good coverage.
}

#[cfg(feature = "arbitrary")]
#[test]
fn arbitrary_grid_aab_volume() {
use arbitrary::Unstructured;
use itertools::Itertools as _;
let max_volume = 100;
let minmax = (0..=255)
.map(|byte| {
// TODO: sketchy coverage; would be better to generate some random/hashed data
let data = [byte; 25];
let mut u = Unstructured::new(&data);
GridAab::arbitrary_with_max_volume(&mut u, max_volume)
.unwrap()
.volume()
})
.minmax()
.into_option();
assert_eq!(minmax, Some((0, max_volume)));
}
}
109 changes: 103 additions & 6 deletions all-is-cubes/src/math/vol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,33 +544,84 @@ mod aab_compat {
}

#[cfg(feature = "arbitrary")]
mod vol_arb {
pub(crate) mod vol_arb {
use super::*;
use arbitrary::Arbitrary;

/// Let's not spend too much memory on generating arbitrary length vectors.
/// This does reduce coverage...
const MAX_VOLUME: usize = 2_usize.pow(16);

/// Size hint for [`Vol::arbitrary_with_max_volume()`].
pub(crate) const ARBITRARY_BOUNDS_SIZE_HINT: (usize, Option<usize>) = {
// 6 bounding coordinates plus one permutation selection.
// Depending on the volume we could *maybe* end up consuming only 1 byte each
// for the sizes.
let gc = core::mem::size_of::<GridCoordinate>();
((gc + 1) * 3 + 1, Some(gc * 6 + 1))
};

impl<O: Default> Vol<(), O> {
#[cfg(feature = "arbitrary")]
pub(crate) fn arbitrary_with_max_volume(
u: &mut arbitrary::Unstructured<'_>,
volume: usize,
) -> arbitrary::Result<Self> {
// Pick sizes within the volume constraint.
let mut limit: GridCoordinate = volume.try_into().unwrap_or(GridCoordinate::MAX);
let size_1 = u.int_in_range(0..=limit)?;
limit /= size_1.max(1);
let size_2 = u.int_in_range(0..=limit)?;
limit /= size_2.max(1);
let size_3 = u.int_in_range(0..=limit)?;

// Shuffle the sizes to remove any bias.
let sizes = *u.choose(&[
GridVector::new(size_1, size_2, size_3),
GridVector::new(size_1, size_3, size_2),
GridVector::new(size_2, size_1, size_3),
GridVector::new(size_2, size_3, size_1),
GridVector::new(size_3, size_1, size_2),
GridVector::new(size_3, size_2, size_1),
])?;

// Compute lower bounds that are valid for the sizes.
let lower_bounds = GridPoint::new(
u.int_in_range(GridCoordinate::MIN..=GridCoordinate::MAX - sizes.x)?,
u.int_in_range(GridCoordinate::MIN..=GridCoordinate::MAX - sizes.y)?,
u.int_in_range(GridCoordinate::MIN..=GridCoordinate::MAX - sizes.z)?,
);

Ok(GridAab::from_lower_size(lower_bounds, sizes)
.to_vol()
.unwrap())
}
}

impl<'a, V: Arbitrary<'a>, C> Arbitrary<'a> for Vol<C, ZMaj>
where
C: FromIterator<V> + Deref<Target = [V]>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let bounds = GridAab::arbitrary_with_max_volume(u, MAX_VOLUME)?;
let bounds = Vol::<()>::arbitrary_with_max_volume(u, MAX_VOLUME)?;
let contents: C = u
.arbitrary_iter()?
.take(bounds.volume())
.collect::<Result<C, _>>()?;
Vol::from_elements(bounds, contents).map_err(|_| arbitrary::Error::NotEnoughData)
bounds
.with_elements(contents)
.map_err(|_| arbitrary::Error::NotEnoughData)
}

fn size_hint(depth: usize) -> (usize, Option<usize>) {
arbitrary::size_hint::recursion_guard(depth, |depth| {
let (lower, upper) = V::size_hint(depth);
(
lower.saturating_mul(MAX_VOLUME),
upper.map(|u| u.saturating_mul(MAX_VOLUME)),
arbitrary::size_hint::and(
ARBITRARY_BOUNDS_SIZE_HINT,
(
lower.saturating_mul(MAX_VOLUME),
upper.map(|u| u.saturating_mul(MAX_VOLUME)),
),
)
})
}
Expand Down Expand Up @@ -780,4 +831,50 @@ mod tests {
Some(((1500 * 2000) + 1500) * 2000 + 1500)
);
}

#[cfg(feature = "arbitrary")]
#[test]
fn arbitrary_bounds_size_hint() {
use arbitrary::{Arbitrary, Unstructured};
let hint = vol_arb::ARBITRARY_BOUNDS_SIZE_HINT;
let most_bytes_used = (0..=255)
.map(|byte| {
// TODO: sketchy coverage; would be better to generate some random/hashed data
let data = [byte; 1000];
let mut u = Unstructured::new(&data);
GridAab::arbitrary(&mut u).unwrap();
let bytes_used = 1000 - u.len();
assert!(
bytes_used >= hint.0,
"used {}, less than {}",
bytes_used,
hint.0
);
bytes_used
})
.max();
assert_eq!(most_bytes_used, hint.1);

// TODO: Also look at the resulting Grids and see if they're good coverage.
}

#[cfg(feature = "arbitrary")]
#[test]
fn arbitrary_bounds_volume() {
use arbitrary::Unstructured;
use itertools::Itertools as _;
let max_volume = 100;
let minmax = (0..=255)
.map(|byte| {
// TODO: sketchy coverage; would be better to generate some random/hashed data
let data = [byte; 25];
let mut u = Unstructured::new(&data);
Vol::<()>::arbitrary_with_max_volume(&mut u, max_volume)
.unwrap()
.volume()
})
.minmax()
.into_option();
assert_eq!(minmax, Some((0, max_volume)));
}
}
4 changes: 2 additions & 2 deletions all-is-cubes/src/space/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,8 @@ impl<'a> arbitrary::Arbitrary<'a> for Space {

// TODO: Should be reusing Vol as Arbitrary for this.

let bounds = GridAab::arbitrary_with_max_volume(u, 2048)?;
let mut space = Space::builder(bounds)
let bounds = Vol::<()>::arbitrary_with_max_volume(u, 2048)?;
let mut space = Space::builder(bounds.bounds()) // TODO: builder should accept Vol
.physics(u.arbitrary()?)
.spawn(u.arbitrary()?)
.build();
Expand Down

0 comments on commit 4357f5a

Please sign in to comment.