Skip to main content

burn_dataset/transform/
partial.rs

1use crate::Dataset;
2use std::error::Error;
3use std::{marker::PhantomData, sync::Arc};
4
5/// Only use a fraction of an existing dataset lazily.
6#[derive(new, Clone)]
7pub struct PartialDataset<D, I> {
8    dataset: D,
9    start_index: usize,
10    end_index: usize,
11    input: PhantomData<I>,
12}
13
14impl<D, I> PartialDataset<D, I>
15where
16    D: Dataset<I>,
17{
18    /// Splits a dataset into multiple partial datasets.
19    pub fn split(dataset: D, num: usize) -> Vec<PartialDataset<Arc<D>, I>> {
20        let dataset = Arc::new(dataset); // cheap cloning.
21
22        let mut current = 0;
23        let mut datasets = Vec::with_capacity(num);
24
25        let batch_size = dataset.len() / num;
26
27        for i in 0..num {
28            let start = current;
29            let mut end = current + batch_size;
30
31            if i == (num - 1) {
32                end = dataset.len();
33            }
34
35            let dataset = PartialDataset::new(dataset.clone(), start, end);
36
37            current += batch_size;
38            datasets.push(dataset);
39        }
40
41        datasets
42    }
43
44    /// Splits a dataset by distributing complete chunks/batches across multiple partial datasets.
45    pub fn split_chunks(
46        dataset: D,
47        num: usize,
48        batch_size: usize,
49    ) -> Vec<PartialDataset<Arc<D>, I>> {
50        let dataset = Arc::new(dataset); // cheap cloning.
51        let total_items = dataset.len();
52
53        // Total number of complete batches
54        let total_batches = total_items.div_ceil(batch_size);
55        let batches_per_split = total_batches / num;
56        let extra_batches = total_batches % num;
57
58        let mut datasets = Vec::with_capacity(num);
59        let mut current_batch = 0;
60
61        for i in 0..num {
62            // Extra batches distributed across first splits
63            let split_batches = if i < extra_batches {
64                batches_per_split + 1
65            } else {
66                batches_per_split
67            };
68
69            let start_batch = current_batch;
70            let end_batch = start_batch + split_batches;
71
72            let start_index = start_batch * batch_size;
73            let end_index = core::cmp::min(end_batch * batch_size, total_items);
74
75            if start_index < total_items {
76                datasets.push(PartialDataset::new(dataset.clone(), start_index, end_index));
77            }
78
79            current_batch = end_batch;
80        }
81
82        datasets
83    }
84}
85
86impl<D, I, E> Dataset<I, E> for PartialDataset<D, I>
87where
88    D: Dataset<I, E>,
89    I: Clone + Send + Sync,
90    E: Error + Send + Sync + 'static,
91{
92    fn get(&self, index: usize) -> Result<I, E> {
93        let index = index + self.start_index;
94        assert!(
95            index >= self.start_index && index < self.end_index,
96            "Index out of bounds for PartialDataset: {} >= {}",
97            index,
98            self.end_index
99        );
100        self.dataset.get(index)
101    }
102
103    fn get_many(&self, indexes: Vec<usize>) -> Result<Vec<I>, E> {
104        let translated = indexes
105            .into_iter()
106            .map(|index| {
107                let index = index + self.start_index;
108                assert!(
109                    index >= self.start_index && index < self.end_index,
110                    "Index out of bounds for PartialDataset: {} >= {}",
111                    index,
112                    self.end_index
113                );
114                index
115            })
116            .collect();
117        self.dataset.get_many(translated)
118    }
119
120    fn len(&self) -> usize {
121        usize::min(self.end_index - self.start_index, self.dataset.len())
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use crate::FakeDataset;
129    use std::collections::HashSet;
130
131    #[test]
132    fn test_start_from_beginning() {
133        let dataset_original = FakeDataset::<String>::new(27);
134        let mut items_original_1 = HashSet::new();
135        let mut items_original_2 = HashSet::new();
136        let mut items_partial = HashSet::new();
137        dataset_original
138            .iter()
139            .map(Result::unwrap)
140            .enumerate()
141            .for_each(|(i, item)| {
142                match i >= 10 {
143                    true => items_original_2.insert(item),
144                    false => items_original_1.insert(item),
145                };
146            });
147
148        let dataset_partial = PartialDataset::new(dataset_original, 0, 10);
149
150        for item in dataset_partial.iter().map(Result::unwrap) {
151            items_partial.insert(item);
152        }
153
154        assert_eq!(dataset_partial.len(), 10);
155        assert_eq!(items_original_1, items_partial);
156        for item in items_original_2 {
157            assert!(!items_partial.contains(&item));
158        }
159    }
160
161    #[test]
162    fn test_start_inside() {
163        let dataset_original = FakeDataset::<String>::new(27);
164        let mut items_original_1 = HashSet::new();
165        let mut items_original_2 = HashSet::new();
166        let mut items_partial = HashSet::new();
167
168        dataset_original
169            .iter()
170            .map(Result::unwrap)
171            .enumerate()
172            .for_each(|(i, item)| {
173                match !(10..20).contains(&i) {
174                    true => items_original_2.insert(item),
175                    false => items_original_1.insert(item),
176                };
177            });
178
179        let dataset_partial = PartialDataset::new(dataset_original, 10, 20);
180        for item in dataset_partial.iter().map(Result::unwrap) {
181            items_partial.insert(item);
182        }
183
184        assert_eq!(dataset_partial.len(), 10);
185        assert_eq!(items_original_1, items_partial);
186        for item in items_original_2 {
187            assert!(!items_partial.contains(&item));
188        }
189    }
190
191    #[test]
192    fn test_get_many() {
193        let dataset_original = FakeDataset::<String>::new(27);
194        let source_items: Vec<String> = dataset_original.iter().map(Result::unwrap).collect();
195
196        let dataset_partial = PartialDataset::new(dataset_original, 10, 20);
197
198        // Local indices, out of order and with a duplicate.
199        let requested = vec![5, 0, 3, 0];
200        let expected: Vec<String> = requested
201            .iter()
202            .map(|&i| source_items[10 + i].clone())
203            .collect();
204
205        let items = dataset_partial.get_many(requested).unwrap();
206
207        assert_eq!(items, expected);
208    }
209
210    #[test]
211    #[should_panic(expected = "Index out of bounds for PartialDataset: 20 >= 20")]
212    fn test_get_many_out_of_bounds() {
213        let dataset_original = FakeDataset::<String>::new(27);
214        let dataset_partial = PartialDataset::new(dataset_original, 10, 20);
215
216        let _ = dataset_partial.get_many(vec![0, 10]);
217    }
218
219    #[test]
220    fn test_split_contains_all_items_without_duplicates() {
221        let dataset_original = FakeDataset::<String>::new(27);
222        let mut items_original = Vec::new();
223        let mut items_partial = Vec::new();
224        for item in dataset_original.iter().map(Result::unwrap) {
225            items_original.push(item);
226        }
227
228        let dataset_partials = PartialDataset::split(dataset_original, 4);
229        let expected_len = [6, 6, 6, 9];
230
231        for (i, dataset) in dataset_partials.iter().enumerate() {
232            assert_eq!(dataset.len(), expected_len[i]);
233            for item in dataset.iter().map(Result::unwrap) {
234                items_partial.push(item);
235            }
236        }
237
238        assert_eq!(items_original, items_partial);
239    }
240
241    #[test]
242    fn test_split_chunks_contains_all_items_without_duplicates() {
243        let dataset_original = FakeDataset::<String>::new(27);
244        let mut items_original = Vec::new();
245        let mut items_partial = Vec::new();
246        for item in dataset_original.iter().map(Result::unwrap) {
247            items_original.push(item);
248        }
249
250        let dataset_partials = PartialDataset::split_chunks(dataset_original, 4, 5);
251        // [(2 * 5), (2 * 5), 5, 2] -> 5 complete chunks + 1 incomplete with 2 remaining items
252        // OTOH, `split(dataset, 4)` would yield [6, 6, 6, 9] -> 4 incomplete chunks + 4 incomplete with [1, 1, 1, 4]
253        let expected_len = [10, 10, 5, 2];
254
255        for (i, dataset) in dataset_partials.iter().enumerate() {
256            assert_eq!(dataset.len(), expected_len[i]);
257            for item in dataset.iter().map(Result::unwrap) {
258                items_partial.push(item);
259            }
260        }
261
262        assert_eq!(items_original, items_partial);
263    }
264}