1 //! Tests for proptest-related functionality.
2 
3 #![allow(dead_code)]
4 
5 use nalgebra::allocator::Allocator;
6 use nalgebra::base::dimension::*;
7 use nalgebra::proptest::{DimRange, MatrixStrategy};
8 use nalgebra::{
9     DMatrix, DVector, DefaultAllocator, Dim, DualQuaternion, Isometry2, Isometry3, Matrix3,
10     OMatrix, Point2, Point3, Quaternion, Rotation2, Rotation3, Scalar, Similarity3, Translation2,
11     Translation3, UnitComplex, UnitDualQuaternion, UnitQuaternion, Vector3, U3, U4,
12 };
13 use num_complex::Complex;
14 use proptest::prelude::*;
15 use proptest::strategy::{Strategy, ValueTree};
16 use proptest::test_runner::TestRunner;
17 use std::ops::RangeInclusive;
18 
19 pub const PROPTEST_MATRIX_DIM: RangeInclusive<usize> = 1..=20;
20 pub const PROPTEST_F64: RangeInclusive<f64> = -100.0..=100.0;
21 
22 pub use nalgebra::proptest::{matrix, vector};
23 
point2() -> impl Strategy<Value = Point2<f64>>24 pub fn point2() -> impl Strategy<Value = Point2<f64>> {
25     vector2().prop_map(|v| Point2::from(v))
26 }
27 
point3() -> impl Strategy<Value = Point3<f64>>28 pub fn point3() -> impl Strategy<Value = Point3<f64>> {
29     vector3().prop_map(|v| Point3::from(v))
30 }
31 
translation2() -> impl Strategy<Value = Translation2<f64>>32 pub fn translation2() -> impl Strategy<Value = Translation2<f64>> {
33     vector2().prop_map(|v| Translation2::from(v))
34 }
35 
translation3() -> impl Strategy<Value = Translation3<f64>>36 pub fn translation3() -> impl Strategy<Value = Translation3<f64>> {
37     vector3().prop_map(|v| Translation3::from(v))
38 }
39 
rotation2() -> impl Strategy<Value = Rotation2<f64>>40 pub fn rotation2() -> impl Strategy<Value = Rotation2<f64>> {
41     PROPTEST_F64.prop_map(|v| Rotation2::new(v))
42 }
43 
rotation3() -> impl Strategy<Value = Rotation3<f64>>44 pub fn rotation3() -> impl Strategy<Value = Rotation3<f64>> {
45     vector3().prop_map(|v| Rotation3::new(v))
46 }
47 
unit_complex() -> impl Strategy<Value = UnitComplex<f64>>48 pub fn unit_complex() -> impl Strategy<Value = UnitComplex<f64>> {
49     PROPTEST_F64.prop_map(|v| UnitComplex::new(v))
50 }
51 
isometry2() -> impl Strategy<Value = Isometry2<f64>>52 pub fn isometry2() -> impl Strategy<Value = Isometry2<f64>> {
53     vector3().prop_map(|v| Isometry2::new(v.xy(), v.z))
54 }
55 
isometry3() -> impl Strategy<Value = Isometry3<f64>>56 pub fn isometry3() -> impl Strategy<Value = Isometry3<f64>> {
57     vector6().prop_map(|v| Isometry3::new(v.xyz(), Vector3::new(v.w, v.a, v.b)))
58 }
59 
60 // pub fn similarity2() -> impl Strategy<Value = Similarity2<f64>> {
61 //     vector4().prop_map(|v| Similarity2::new(v.xy(), v.z, v.w))
62 // }
63 
similarity3() -> impl Strategy<Value = Similarity3<f64>>64 pub fn similarity3() -> impl Strategy<Value = Similarity3<f64>> {
65     vector(PROPTEST_F64, Const::<7>)
66         .prop_map(|v| Similarity3::new(v.xyz(), Vector3::new(v[3], v[4], v[5]), v[6]))
67 }
68 
unit_dual_quaternion() -> impl Strategy<Value = UnitDualQuaternion<f64>>69 pub fn unit_dual_quaternion() -> impl Strategy<Value = UnitDualQuaternion<f64>> {
70     isometry3().prop_map(|iso| UnitDualQuaternion::from_isometry(&iso))
71 }
72 
dual_quaternion() -> impl Strategy<Value = DualQuaternion<f64>>73 pub fn dual_quaternion() -> impl Strategy<Value = DualQuaternion<f64>> {
74     vector(PROPTEST_F64, Const::<8>).prop_map(|v| {
75         DualQuaternion::from_real_and_dual(
76             Quaternion::new(v[0], v[1], v[2], v[3]),
77             Quaternion::new(v[4], v[5], v[6], v[7]),
78         )
79     })
80 }
81 
quaternion() -> impl Strategy<Value = Quaternion<f64>>82 pub fn quaternion() -> impl Strategy<Value = Quaternion<f64>> {
83     vector4().prop_map(|v| Quaternion::from(v))
84 }
85 
unit_quaternion() -> impl Strategy<Value = UnitQuaternion<f64>>86 pub fn unit_quaternion() -> impl Strategy<Value = UnitQuaternion<f64>> {
87     vector3().prop_map(|v| UnitQuaternion::new(v))
88 }
89 
complex_f64() -> impl Strategy<Value = Complex<f64>> + Clone90 pub fn complex_f64() -> impl Strategy<Value = Complex<f64>> + Clone {
91     vector(PROPTEST_F64, Const::<2>).prop_map(|v| Complex::new(v.x, v.y))
92 }
93 
dmatrix() -> impl Strategy<Value = DMatrix<f64>>94 pub fn dmatrix() -> impl Strategy<Value = DMatrix<f64>> {
95     matrix(PROPTEST_F64, PROPTEST_MATRIX_DIM, PROPTEST_MATRIX_DIM)
96 }
97 
dvector() -> impl Strategy<Value = DVector<f64>>98 pub fn dvector() -> impl Strategy<Value = DVector<f64>> {
99     vector(PROPTEST_F64, PROPTEST_MATRIX_DIM)
100 }
101 
dmatrix_<ScalarStrategy>( scalar_strategy: ScalarStrategy, ) -> impl Strategy<Value = OMatrix<ScalarStrategy::Value, Dynamic, Dynamic>> where ScalarStrategy: Strategy + Clone + 'static, ScalarStrategy::Value: Scalar, DefaultAllocator: Allocator<ScalarStrategy::Value, Dynamic, Dynamic>,102 pub fn dmatrix_<ScalarStrategy>(
103     scalar_strategy: ScalarStrategy,
104 ) -> impl Strategy<Value = OMatrix<ScalarStrategy::Value, Dynamic, Dynamic>>
105 where
106     ScalarStrategy: Strategy + Clone + 'static,
107     ScalarStrategy::Value: Scalar,
108     DefaultAllocator: Allocator<ScalarStrategy::Value, Dynamic, Dynamic>,
109 {
110     matrix(scalar_strategy, PROPTEST_MATRIX_DIM, PROPTEST_MATRIX_DIM)
111 }
112 
113 // pub fn dvector_<T>(range: RangeInclusive<T>) -> impl Strategy<Value = DVector<T>>
114 // where
115 //     RangeInclusive<T>: Strategy<Value = T>,
116 //     T: Scalar + PartialEq + Copy,
117 //     DefaultAllocator: Allocator<T, Dynamic>,
118 // {
119 //     vector(range, PROPTEST_MATRIX_DIM)
120 // }
121 
122 macro_rules! define_strategies(
123     ($($strategy_: ident $strategy: ident<$nrows: literal, $ncols: literal>),*) => {$(
124         pub fn $strategy() -> impl Strategy<Value = OMatrix<f64, Const<$nrows>, Const<$ncols>>> {
125             matrix(PROPTEST_F64, Const::<$nrows>, Const::<$ncols>)
126         }
127 
128         pub fn $strategy_<ScalarStrategy>(scalar_strategy: ScalarStrategy) -> impl Strategy<Value = OMatrix<ScalarStrategy::Value, Const<$nrows>, Const<$ncols>>>
129             where
130                 ScalarStrategy: Strategy + Clone + 'static,
131                 ScalarStrategy::Value: Scalar, {
132             matrix(scalar_strategy, Const::<$nrows>, Const::<$ncols>)
133         }
134     )*}
135 );
136 
137 define_strategies!(
138     matrix1_ matrix1<1, 1>,
139     matrix2_ matrix2<2, 2>,
140     matrix3_ matrix3<3, 3>,
141     matrix4_ matrix4<4, 4>,
142     matrix5_ matrix5<5, 5>,
143     matrix6_ matrix6<6, 6>,
144 
145     matrix5x2_ matrix5x2<5, 2>,
146     matrix2x5_ matrix2x5<2, 5>,
147     matrix5x3_ matrix5x3<5, 3>,
148     matrix3x5_ matrix3x5<3, 5>,
149     matrix5x4_ matrix5x4<5, 4>,
150     matrix4x5_ matrix4x5<4, 5>,
151 
152     vector1_ vector1<1, 1>,
153     vector2_ vector2<2, 1>,
154     vector3_ vector3<3, 1>,
155     vector4_ vector4<4, 1>,
156     vector5_ vector5<5, 1>,
157     vector6_ vector6<6, 1>
158 );
159 
160 /// Generate a proptest that tests that all matrices generated with the
161 /// provided rows and columns conform to the constraints defined by the
162 /// input.
163 macro_rules! generate_matrix_sanity_test {
164     ($test_name:ident, $rows:expr, $cols:expr) => {
165         proptest! {
166             #[test]
167             fn $test_name(a in matrix(-5 ..= 5i32, $rows, $cols)) {
168                 // let a: OMatrix<_, $rows, $cols> = a;
169                 let rows_range = DimRange::from($rows);
170                 let cols_range = DimRange::from($cols);
171                 prop_assert!(a.nrows() >= rows_range.lower_bound().value()
172                           && a.nrows() <= rows_range.upper_bound().value());
173                 prop_assert!(a.ncols() >= cols_range.lower_bound().value()
174                           && a.ncols() <= cols_range.upper_bound().value());
175                 prop_assert!(a.iter().all(|x_ij| *x_ij >= -5 && *x_ij <= 5));
176             }
177         }
178     };
179 }
180 
181 // Test all fixed-size matrices with row/col dimensions up to 3
182 generate_matrix_sanity_test!(test_matrix_u0_u0, Const::<0>, Const::<0>);
183 generate_matrix_sanity_test!(test_matrix_u1_u0, Const::<1>, Const::<0>);
184 generate_matrix_sanity_test!(test_matrix_u0_u1, Const::<0>, Const::<1>);
185 generate_matrix_sanity_test!(test_matrix_u1_u1, Const::<1>, Const::<1>);
186 generate_matrix_sanity_test!(test_matrix_u2_u1, Const::<2>, Const::<1>);
187 generate_matrix_sanity_test!(test_matrix_u1_u2, Const::<1>, Const::<2>);
188 generate_matrix_sanity_test!(test_matrix_u2_u2, Const::<2>, Const::<2>);
189 generate_matrix_sanity_test!(test_matrix_u3_u2, Const::<3>, Const::<2>);
190 generate_matrix_sanity_test!(test_matrix_u2_u3, Const::<2>, Const::<3>);
191 generate_matrix_sanity_test!(test_matrix_u3_u3, Const::<3>, Const::<3>);
192 
193 // Similarly test all heap-allocated but fixed dim ranges
194 generate_matrix_sanity_test!(test_matrix_0_0, 0, 0);
195 generate_matrix_sanity_test!(test_matrix_0_1, 0, 1);
196 generate_matrix_sanity_test!(test_matrix_1_0, 1, 0);
197 generate_matrix_sanity_test!(test_matrix_1_1, 1, 1);
198 generate_matrix_sanity_test!(test_matrix_2_1, 2, 1);
199 generate_matrix_sanity_test!(test_matrix_1_2, 1, 2);
200 generate_matrix_sanity_test!(test_matrix_2_2, 2, 2);
201 generate_matrix_sanity_test!(test_matrix_3_2, 3, 2);
202 generate_matrix_sanity_test!(test_matrix_2_3, 2, 3);
203 generate_matrix_sanity_test!(test_matrix_3_3, 3, 3);
204 
205 // Test arbitrary inputs
206 generate_matrix_sanity_test!(test_matrix_input_1, Const::<5>, 1..=5);
207 generate_matrix_sanity_test!(test_matrix_input_2, 3..=4, 1..=5);
208 generate_matrix_sanity_test!(test_matrix_input_3, 1..=2, Const::<3>);
209 generate_matrix_sanity_test!(test_matrix_input_4, 3, Const::<4>);
210 
211 #[test]
test_matrix_output_types()212 fn test_matrix_output_types() {
213     // Test that the dimension types are correct for the given inputs
214     let _: MatrixStrategy<_, U3, U4> = matrix(-5..5, Const::<3>, Const::<4>);
215     let _: MatrixStrategy<_, U3, U3> = matrix(-5..5, Const::<3>, Const::<3>);
216     let _: MatrixStrategy<_, U3, Dynamic> = matrix(-5..5, Const::<3>, 1..=5);
217     let _: MatrixStrategy<_, Dynamic, U3> = matrix(-5..5, 1..=5, Const::<3>);
218     let _: MatrixStrategy<_, Dynamic, Dynamic> = matrix(-5..5, 1..=5, 1..=5);
219 }
220 
221 // Below we have some tests to ensure that specific instances of OMatrix are usable
222 // in a typical proptest scenario where we (implicitly) use the `Arbitrary` trait
223 proptest! {
224     #[test]
225     fn ensure_arbitrary_test_compiles_matrix3(_: Matrix3<i32>) {}
226 
227     #[test]
228     fn ensure_arbitrary_test_compiles_matrixmn_u3_dynamic(_: OMatrix<i32, U3, Dynamic>) {}
229 
230     #[test]
231     fn ensure_arbitrary_test_compiles_matrixmn_dynamic_u3(_: OMatrix<i32, Dynamic, U3>) {}
232 
233     #[test]
234     fn ensure_arbitrary_test_compiles_dmatrix(_: DMatrix<i32>) {}
235 
236     #[test]
237     fn ensure_arbitrary_test_compiles_vector3(_: Vector3<i32>) {}
238 
239     #[test]
240     fn ensure_arbitrary_test_compiles_dvector(_: DVector<i32>) {}
241 }
242 
243 #[test]
matrix_shrinking_satisfies_constraints()244 fn matrix_shrinking_satisfies_constraints() {
245     // We use a deterministic test runner to make the test "stable".
246     let mut runner = TestRunner::deterministic();
247 
248     let strategy = matrix(-1..=2, 1..=3, 2..=4);
249 
250     let num_matrices = 25;
251 
252     macro_rules! maybeprintln {
253         ($($arg:tt)*) => {
254             // Uncomment the below line to enable printing of matrix sequences. This is handy
255             // for manually inspecting the sequences of simplified matrices.
256             // println!($($arg)*)
257         };
258     }
259 
260     maybeprintln!("========================== (begin generation process)");
261 
262     for _ in 0..num_matrices {
263         let mut tree = strategy
264             .new_tree(&mut runner)
265             .expect("Tree generation should not fail.");
266 
267         let mut current = Some(tree.current());
268 
269         maybeprintln!("------------------");
270 
271         while let Some(matrix) = current {
272             maybeprintln!("{}", matrix);
273 
274             assert!(
275                 matrix.iter().all(|&v| v >= -1 && v <= 2),
276                 "All matrix elements must satisfy constraints"
277             );
278             assert!(
279                 matrix.nrows() >= 1 && matrix.nrows() <= 3,
280                 "Number of rows in matrix must satisfy constraints."
281             );
282             assert!(
283                 matrix.ncols() >= 2 && matrix.ncols() <= 4,
284                 "Number of columns in matrix must satisfy constraints."
285             );
286 
287             current = if tree.simplify() {
288                 Some(tree.current())
289             } else {
290                 None
291             }
292         }
293     }
294 
295     maybeprintln!("========================== (end of generation process)");
296 }
297 
298 #[cfg(feature = "slow-tests")]
299 mod slow {
300     use super::*;
301     use itertools::Itertools;
302     use std::collections::HashSet;
303     use std::iter::repeat;
304 
305     #[cfg(feature = "slow-tests")]
306     #[test]
matrix_samples_all_possible_outputs()307     fn matrix_samples_all_possible_outputs() {
308         // Test that the proptest generation covers all possible outputs for a small space of inputs
309         // given enough samples.
310 
311         // We use a deterministic test runner to make the test "stable".
312         let mut runner = TestRunner::deterministic();
313 
314         // This number needs to be high enough so that we with high probability sample
315         // all possible cases
316         let num_generated_matrices = 200000;
317 
318         let values = -1..=1;
319         let rows = 0..=2;
320         let cols = 0..=3;
321         let strategy = matrix(values.clone(), rows.clone(), cols.clone());
322 
323         // Enumerate all possible combinations
324         let mut all_combinations = HashSet::new();
325         for nrows in rows {
326             for ncols in cols.clone() {
327                 // For the given number of rows and columns
328                 let n_values = nrows * ncols;
329 
330                 if n_values == 0 {
331                     // If we have zero rows or columns, the set of matrices with the given
332                     // rows and columns is a single element: an empty matrix
333                     all_combinations.insert(DMatrix::from_row_slice(nrows, ncols, &[]));
334                 } else {
335                     // Otherwise, we need to sample all possible matrices.
336                     // To do this, we generate the values as the (multi) Cartesian product
337                     // of the value sets. For example, for a 2x2 matrices, we consider
338                     // all possible 4-element arrays that the matrices can take by
339                     // considering all elements in the cartesian product
340                     //  V x V x V x V
341                     // where V is the set of eligible values, e.g. V := -1 ..= 1
342                     for matrix_values in repeat(values.clone())
343                         .take(n_values)
344                         .multi_cartesian_product()
345                     {
346                         all_combinations.insert(DMatrix::from_row_slice(
347                             nrows,
348                             ncols,
349                             &matrix_values,
350                         ));
351                     }
352                 }
353             }
354         }
355 
356         let mut visited_combinations = HashSet::new();
357         for _ in 0..num_generated_matrices {
358             let tree = strategy
359                 .new_tree(&mut runner)
360                 .expect("Tree generation should not fail");
361             let matrix = tree.current();
362             visited_combinations.insert(matrix.clone());
363         }
364 
365         assert_eq!(
366             visited_combinations, all_combinations,
367             "Did not sample all possible values."
368         );
369     }
370 }
371