Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proptest strategy to generate unit vectors does not always yield unit vectors #1486

Open
werner291 opened this issue Feb 14, 2025 · 2 comments

Comments

@werner291
Copy link

werner291 commented Feb 14, 2025

Hi,

I made the following proptest strategy:


use nalgebra::Unit;
use nalgebra::proptest::vector;
use proptest::prelude::*;

fn arbitrary_unit_vector() -> impl Strategy<Value = Unit<Vector3<f64>>> {
        any::<Vector3<f64>>()
            .prop_filter("Non-zero vector", |v| v.norm() > 0.5)
            .prop_map(|v| Unit::new_normalize(v))
    }

Which I then use as follows:

#[test]
        fn test_parametric_line_intersection(
            intersection_point in arbitrary_point(),
            v1 in arbitrary_unit_vector(),
            v2 in arbitrary_unit_vector(),
            min1 in -10.0 .. 0.0,
            max1 in 0.0 .. 10.0,
            min2 in -10.0 .. 0.0,
            max2 in 0.0 .. 10.0,
        )

Resulting in the error:

Test failed: assertion failed: v2.norm() > 0.5.
minimal failing input: intersection_point = [
    0.0,
    0.0,
    0.0,
], v1 = [
    [
        0.0,
        0.0,
        -1.0,
    ],
], v2 = [
    [
        -0.0,
        0.0,
        0.0,
    ],
], min1 = 0.0, max1 = 0.0, min2 = 0.0, max2 = 0.0
	successes: 0
	local rejects: 0
	global rejects: 0
thread 'geometry::intersection::tests::test_parametric_line_intersection' panicked at src/geometry/intersection.rs:178:13:
assertion failed: v1.norm() > 0.5

Note how v2 has 0 norm, and a negative x-component, despite being wrapped in Unit.

@werner291
Copy link
Author

Ah, it seems that proptest generates silly gigantic float inputs which then get normalized:

fn arbitrary_unit_vector() -> impl Strategy<Value = Unit<Vector3<f64>>> {
        any::<Vector3<f64>>()
            .prop_filter("Non-zero vector", |v| v.norm() > 0.5)
            .prop_map(|v| {
                println!("v: ({} {} {})", v.x, v.y, v.z);
                let n = Unit::new_normalize(v);
                println!("n: ({} {} {})", n.x, n.y, n.z);
                n
            })
    }

Output:

v: (0 0 -1654413742099681300000000000000000000000000000000000000000000000000000000000000000000)
n: (0 0 -1)
v: (-0 -415162601244421300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 77371037383597210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000)
n: (-0 -0 0)

I guess this won't be an issue on realistic inputs, but Unit here does seem a bit eager to contain zeros like this 😅

@werner291
Copy link
Author

werner291 commented Feb 14, 2025

If we'd like to keep "sensible" inputs, maybe something like this?

/// A Strategy to generate f64 values within the range [-1000.0, 1000.0],
    /// though it is set up to be likely to produce zeros, round numbers, and edge cases.
    fn scalar_strategy() -> impl Strategy<Value = f64> {
        prop_oneof![
            prop_oneof![
                Just(0.0),
                Just(1.0),
                Just(-1.0),
                Just(1000.0),
                Just(-1000.0)
            ],
            -1000.0..1000.0
        ]
    }

(doesn't take away that Unit accepted a 0-norm result, but still)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant