roaring_graphs/
delta_debugging_bitmap.rs1use proptest::{
2 strategy::{Strategy, ValueTree},
3 test_runner::Reason,
4};
5use rand::{distributions::Uniform, prelude::Distribution, Rng};
6use roaring::RoaringBitmap;
7use std::{
8 cmp::{max, min},
9 ops::Range,
10};
11
12#[derive(Debug, Clone)]
13pub struct UniformBitProbabilityStrategy {
14 size: Range<u32>,
15 ones_probability: f64,
16}
17
18pub fn arb_bitmap_uniform(
19 size: impl Into<Range<u32>>,
20 ones_probability: f64,
21) -> UniformBitProbabilityStrategy {
22 UniformBitProbabilityStrategy {
23 size: size.into(),
24 ones_probability,
25 }
26}
27
28impl Strategy for UniformBitProbabilityStrategy {
29 type Tree = DeltaDebuggingBitmapValueTree;
30
31 type Value = RoaringBitmap;
32
33 fn new_tree(
34 &self,
35 runner: &mut proptest::test_runner::TestRunner,
36 ) -> proptest::strategy::NewTree<Self> {
37 if self.size.is_empty() {
39 panic!(
40 "Invalid use of empty size range. (hint: did you \
41 accidentally write {}..{} where you meant {}..={} \
42 somewhere?)",
43 self.size.start, self.size.end, self.size.start, self.size.end
44 );
45 }
46 if self.ones_probability < 0.0 || self.ones_probability > 1.0 {
47 panic!(
48 "Invalid propability set for generating ones. \
49 Needs to be a number between 0 and 1, but got {}",
50 self.ones_probability
51 );
52 }
53 let size = Uniform::new(self.size.start, self.size.end - 1).sample(runner.rng());
54 let iter = (0..size as u32).filter(|_| runner.rng().gen_bool(self.ones_probability));
55 let bitmap =
56 RoaringBitmap::from_sorted_iter(iter).map_err(|e| Reason::from(e.to_string()))?;
57 Ok(DeltaDebuggingBitmapValueTree::new(bitmap))
58 }
59}
60
61#[derive(Debug)]
72pub struct DeltaDebuggingBitmapValueTree {
73 upper: RoaringBitmap,
74 split_into: u64,
75 split_index: u64,
76 subset: SubsetState,
77}
78
79#[derive(Debug, Eq, PartialEq)]
80enum SubsetState {
81 CheckSubset,
82 CheckSubsetComplement,
83}
84
85impl SubsetState {
86 fn is_inside(&self, index: u64, split_index: u64, split_width: u64) -> bool {
87 let split_start = split_width * split_index;
88 let split_end = split_start + split_width;
89 let inside_split = split_start <= index && index < split_end;
90
91 match self {
92 Self::CheckSubset => inside_split,
93 Self::CheckSubsetComplement => !inside_split,
94 }
95 }
96}
97
98impl DeltaDebuggingBitmapValueTree {
99 pub fn new(bitmap: RoaringBitmap) -> Self {
100 Self {
103 upper: bitmap,
104 split_into: 1,
105 split_index: 0,
106 subset: SubsetState::CheckSubset,
107 }
108 }
109}
110
111impl ValueTree for DeltaDebuggingBitmapValueTree {
112 type Value = RoaringBitmap;
113
114 fn current(&self) -> Self::Value {
115 let mut current = RoaringBitmap::new();
116 let num_items = self.upper.len();
117 let split_width = num_items / self.split_into;
118
119 for (i, n) in self.upper.iter().enumerate() {
120 let idx = i as u64;
121
122 if self.subset.is_inside(idx, self.split_index, split_width) {
123 current.insert(n);
124 }
125 }
126
127 current
128 }
129
130 fn simplify(&mut self) -> bool {
131 let num_elems = self.upper.len();
134 if num_elems == 0 || num_elems == 1 {
135 return false;
136 }
137
138 self.upper = self.current();
139
140 match self.subset {
141 SubsetState::CheckSubset => {
142 if self.split_into == 1 {
143 self.subset = SubsetState::CheckSubsetComplement;
145 } else {
146 self.split_into = 2;
148 }
149 }
150 SubsetState::CheckSubsetComplement => {
151 self.split_into = max(self.split_into - 1, 2);
153 self.subset = SubsetState::CheckSubset;
154 }
155 }
156 self.split_into = max(1, min(self.split_into, self.upper.len()));
157 self.split_index = 0;
158 true
159 }
160
161 fn complicate(&mut self) -> bool {
162 let num_elems = self.upper.len();
165 if num_elems == 0 {
166 return false;
167 }
168
169 self.split_index += 1;
170 if self.split_index < self.split_into {
171 return true;
172 }
173
174 match self.subset {
176 SubsetState::CheckSubset => {
177 self.subset = SubsetState::CheckSubsetComplement;
180 self.split_index = 0;
181 true
182 }
183 SubsetState::CheckSubsetComplement => {
184 if self.split_into < num_elems {
188 self.split_into = min(num_elems, 2 * self.split_into);
191 self.subset = SubsetState::CheckSubset;
192 self.split_index = 0;
193 true
194 } else {
195 false
198 }
199 }
200 }
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::DeltaDebuggingBitmapValueTree;
207 use proptest::{
208 collection::vec,
209 prelude::*,
210 strategy::ValueTree,
211 test_runner::{TestError, TestRunner},
212 };
213 use roaring::RoaringBitmap;
214
215 fn best_case_complexity(elems: u32) -> u32 {
216 2 * ((elems as f64).log2() as u32)
217 }
218
219 fn worst_case_complexity(elems: u32) -> u32 {
220 elems * elems + 3 * elems
221 }
222
223 fn runner_with_shrink_iters(max_shrink_iters: u32) -> TestRunner {
224 TestRunner::new(ProptestConfig {
225 max_shrink_iters,
226 ..Default::default()
227 })
228 }
229
230 #[test]
231 fn shrinks_to_nothing_immediately() {
232 let mut runner = runner_with_shrink_iters(2);
234
235 let bitmap = RoaringBitmap::from_iter(0..100);
236 let tree = DeltaDebuggingBitmapValueTree::new(bitmap);
237
238 let result = runner.run_one(tree, |_| {
239 Err(TestCaseError::Fail("just because".into()))
241 });
242
243 assert_eq!(
244 result,
245 Err(TestError::Fail("just because".into(), RoaringBitmap::new()))
246 )
247 }
248
249 #[test]
250 fn initial_current_is_the_full_set() {
251 let bitmap = RoaringBitmap::from_iter(0..100);
252 let tree = DeltaDebuggingBitmapValueTree::new(bitmap.clone());
253 assert_eq!(tree.current(), bitmap);
254 }
255
256 const SMALL_SIZE: u32 = 1000;
257 const BIG_SIZE: u32 = 100_000;
258
259 proptest! {
260 #[test]
261 fn should_shrink_to_minimal_elements(error_bits in vec(0..SMALL_SIZE, 1..10)) {
262 let bitmap = RoaringBitmap::from_iter(0..SMALL_SIZE);
263 prop_assert!(error_bits.iter().all(|e| bitmap.contains(*e)));
264 let tree = DeltaDebuggingBitmapValueTree::new(bitmap);
265
266 let mut runner = runner_with_shrink_iters(2 * worst_case_complexity(SMALL_SIZE));
268 let result = runner.run_one(tree, |bitmap| {
269 if error_bits.iter().all(|err_bit| bitmap.contains(*err_bit)) {
270 Err(TestCaseError::Fail("contains all error bits".into()))
271 } else {
272 Ok(())
273 }
274 });
275
276 let minimal_bitmap = RoaringBitmap::from_iter(error_bits.iter());
277 prop_assert_eq!(
278 result,
279 Err(TestError::Fail(
280 "contains all error bits".into(),
281 minimal_bitmap
282 ))
283 );
284 }
285
286 #[test]
287 fn test_best_case_complexity(error_bit in 0..BIG_SIZE) {
288 let bitmap = RoaringBitmap::from_iter(0..BIG_SIZE);
289 prop_assert!(bitmap.contains(error_bit));
290 let tree = DeltaDebuggingBitmapValueTree::new(bitmap);
291
292 let mut runner = runner_with_shrink_iters(2 * best_case_complexity(BIG_SIZE));
294 let result = runner.run_one(tree, move |bitmap| {
295 if bitmap.contains(error_bit) {
296 Err(TestCaseError::Fail("contains error bit".into()))
297 } else {
298 Ok(())
299 }
300 });
301
302 prop_assert_eq!(
303 result,
304 Err(TestError::Fail("contains error bit".into(), RoaringBitmap::from([error_bit])))
305 );
306 }
307 }
308}