1use std::io::{BufWriter, Write};
7
8use byteorder::{LittleEndian, WriteBytesExt};
9use diskann_providers::storage::StorageWriteProvider;
10use diskann_utils::{io::Metadata, sampling::random::WithApproximateNorm};
11use diskann_vector::Half;
12
13use crate::utils::{CMDResult, CMDToolError, DataType};
14
15type WriteVectorMethodType<T> = Box<dyn Fn(&mut BufWriter<T>, &Vec<f32>) -> CMDResult<bool>>;
16
17#[allow(clippy::panic)]
23pub fn write_random_data<StorageProvider: StorageWriteProvider>(
24 storage_provider: &StorageProvider,
25 output_file: &str,
26 data_type: DataType,
27 number_of_dimensions: usize,
28 number_of_vectors: u64,
29 radius: f32,
30) -> CMDResult<()> {
31 if (data_type == DataType::Int8 || data_type == DataType::Uint8)
32 && (radius > 127.0 || radius <= 0.0)
33 {
34 return Err(CMDToolError {
35 details:
36 "Error: for int8/uint8 datatypes, radius (L2 norm) cannot be greater than 127 and less than or equal to 0"
37 .to_string(),
38 });
39 }
40
41 let file = storage_provider.create_for_write(output_file)?;
42 let writer = BufWriter::new(file);
43
44 write_random_data_writer(
45 writer,
46 data_type,
47 number_of_dimensions,
48 number_of_vectors,
49 radius,
50 )
51}
52
53#[allow(clippy::panic)]
59fn write_random_data_writer<T: Sized + Write>(
60 mut writer: BufWriter<T>,
61 data_type: DataType,
62 number_of_dimensions: usize,
63 number_of_vectors: u64,
64 radius: f32,
65) -> CMDResult<()> {
66 if (data_type == DataType::Int8 || data_type == DataType::Uint8)
67 && (radius > 127.0 || radius <= 0.0)
68 {
69 return Err(CMDToolError {
70 details:
71 "Error: for int8/uint8 datatypes, radius (L2 norm) cannot be greater than 127 and less than or equal to 0"
72 .to_string(),
73 });
74 }
75
76 Metadata::new(number_of_vectors, number_of_dimensions)?.write(&mut writer)?;
77
78 let block_size = 131072;
79 let nblks = u64::div_ceil(number_of_vectors, block_size);
80 println!("# blks: {}", nblks);
81
82 for i in 0..nblks {
83 let cblk_size = std::cmp::min(number_of_vectors - i * block_size, block_size);
84
85 let write_method: WriteVectorMethodType<T> = match data_type {
88 DataType::Float => Box::new(
89 |writer: &mut BufWriter<T>, vector: &Vec<f32>| -> CMDResult<bool> {
90 let mut found_nonzero = false;
91 for value in vector {
92 writer.write_f32::<LittleEndian>(*value)?;
93 found_nonzero = found_nonzero || ((*value != 0f32) && value.is_finite());
94 }
95 Ok(found_nonzero)
96 },
97 ),
98 DataType::Uint8 => Box::new(
99 |writer: &mut BufWriter<T>, vector: &Vec<f32>| -> CMDResult<bool> {
100 let mut found_nonempty = false;
101 for value in vector.iter().map(|&item| (item + 128.0).round() as u8) {
104 writer.write_u8(value)?;
105
106 found_nonempty = found_nonempty || (value != 128u8);
109 }
110 Ok(found_nonempty)
111 },
112 ),
113 DataType::Int8 => Box::new(
114 |writer: &mut BufWriter<T>, vector: &Vec<f32>| -> CMDResult<bool> {
115 let mut found_nonzero = false;
116 for value in vector.iter().map(|&item| item.round() as i8) {
117 writer.write_i8(value)?;
118 found_nonzero = found_nonzero || (value != 0i8);
119 }
120 Ok(found_nonzero)
121 },
122 ),
123 DataType::Fp16 => Box::new(
124 |writer: &mut BufWriter<T>, vector: &Vec<f32>| -> CMDResult<bool> {
125 let mut found_nonzero = false;
126 for value in vector.iter().map(|&item| Half::from_f32(item)) {
127 let mut buf = [0; 2];
128 buf.clone_from_slice(value.to_le_bytes().as_slice());
129 writer.write_all(&buf)?;
130 found_nonzero =
131 found_nonzero || (value != Half::from_f32(0.0) && value.is_finite());
132 }
133 Ok(found_nonzero)
134 },
135 ),
136 };
137
138 write_random_vector_block(
140 write_method,
141 &mut writer,
142 number_of_dimensions,
143 cblk_size,
144 radius,
145 )?;
146 }
147
148 writer.flush()?;
151
152 Ok(())
153}
154
155fn write_random_vector_block<
163 F: Sized + Write,
164 T: FnMut(&mut BufWriter<F>, &Vec<f32>) -> CMDResult<bool>,
165>(
166 mut write_method: T,
167 writer: &mut BufWriter<F>,
168 number_of_dimensions: usize,
169 number_of_points: u64,
170 radius: f32,
171) -> CMDResult<()> {
172 let mut found_nonzero = false;
173
174 let mut rng = diskann_providers::utils::create_rnd_from_seed(0);
175 for _ in 0..number_of_points {
176 let vector = f32::with_approximate_norm(number_of_dimensions, radius, &mut rng);
177 found_nonzero |= write_method(writer, &vector)?;
180 }
181
182 if found_nonzero {
183 Ok(())
184 } else {
185 Err(CMDToolError {
186 details: format!(
187 "Generated all-zero vectors with radius {}. Try increasing radius",
188 radius
189 ),
190 })
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use diskann_providers::storage::VirtualStorageProvider;
197 use rstest::rstest;
198
199 use super::*;
200
201 #[cfg(not(miri))]
204 const TEST_DATASET_SIZE_SMALL: u64 = 101;
205
206 #[cfg(not(miri))]
210 const TEST_NUM_DIMENSIONS_RECOMMENDED: usize = 59;
211
212 #[cfg(miri)]
215 const TEST_DATASET_SIZE_SMALL: u64 = 3;
216
217 #[cfg(miri)]
221 const TEST_NUM_DIMENSIONS_RECOMMENDED: usize = 13;
222
223 #[rstest]
224 fn random_data_write_success(
225 #[values(DataType::Float, DataType::Uint8, DataType::Int8)] data_type: DataType,
226 #[values(100.0, 127.0)] norm: f32,
227 ) {
228 let random_data_path = "/mydatafile.bin";
229 let num_dimensions = TEST_NUM_DIMENSIONS_RECOMMENDED;
230
231 let storage_provider = VirtualStorageProvider::new_overlay(".");
232 let result = write_random_data(
233 &storage_provider,
234 random_data_path,
235 data_type,
236 num_dimensions,
237 10000,
238 norm,
239 );
240
241 assert!(result.is_ok(), "write_random_data should succeed");
242 assert!(
243 storage_provider.exists(random_data_path),
244 "Random data file should exist"
245 );
246 }
247
248 #[rstest]
251 #[case(DataType::Float, 0.0)]
252 #[case(DataType::Int8, 0.0)]
253 #[case(DataType::Int8, 0.1)]
254 #[case(DataType::Int8, 1.0)]
255 #[case(DataType::Uint8, 0.0)]
256 #[case(DataType::Uint8, 0.1)]
257 #[case(DataType::Uint8, 1.0)]
258 fn random_data_write_too_low_norm(#[case] data_type: DataType, #[case] radius: f32) {
259 let random_data_path = "/mydatafile.bin";
260 let num_dimensions = TEST_NUM_DIMENSIONS_RECOMMENDED;
261
262 let expected = if (data_type == DataType::Int8 || data_type == DataType::Uint8)
263 && radius <= 0.0
264 {
265 Err(CMDToolError {
266 details:
267 "Error: for int8/uint8 datatypes, radius (L2 norm) cannot be greater than 127 and less than or equal to 0"
268 .to_string(),
269 })
270 } else {
271 Err(CMDToolError {
272 details: format!(
273 "Generated all-zero vectors with radius {}. Try increasing radius",
274 radius
275 ),
276 })
277 };
278
279 let storage_provider = VirtualStorageProvider::new_overlay(".");
280 let result = write_random_data(
281 &storage_provider,
282 random_data_path,
283 data_type,
284 num_dimensions,
285 TEST_DATASET_SIZE_SMALL,
286 radius,
287 );
288
289 assert_eq!(expected, result);
290 }
291
292 #[test]
293 fn test_fp16_data_type() {
294 let random_data_path = "/fp16_data.bin";
295 let num_dimensions = TEST_NUM_DIMENSIONS_RECOMMENDED;
296
297 let storage_provider = VirtualStorageProvider::new_overlay(".");
298 let result = write_random_data(
299 &storage_provider,
300 random_data_path,
301 DataType::Fp16,
302 num_dimensions,
303 100,
304 50.0,
305 );
306
307 assert!(result.is_ok(), "write_random_data with Fp16 should succeed");
308 assert!(storage_provider.exists(random_data_path));
309 }
310
311 #[test]
312 fn test_invalid_radius_for_int8() {
313 let random_data_path = "/invalid_int8.bin";
314 let storage_provider = VirtualStorageProvider::new_overlay(".");
315
316 let expected = Err(CMDToolError {
317 details:
318 "Error: for int8/uint8 datatypes, radius (L2 norm) cannot be greater than 127 and less than or equal to 0"
319 .to_string(),
320 });
321 let result = write_random_data(
322 &storage_provider,
323 random_data_path,
324 DataType::Int8,
325 10,
326 100,
327 128.0,
328 );
329
330 assert_eq!(expected, result);
331 }
332
333 #[test]
334 fn test_invalid_radius_for_uint8() {
335 let random_data_path = "/invalid_uint8.bin";
336 let storage_provider = VirtualStorageProvider::new_overlay(".");
337
338 let expected = Err(CMDToolError {
339 details:
340 "Error: for int8/uint8 datatypes, radius (L2 norm) cannot be greater than 127 and less than or equal to 0"
341 .to_string(),
342 });
343 let result = write_random_data(
344 &storage_provider,
345 random_data_path,
346 DataType::Uint8,
347 10,
348 100,
349 150.0,
350 );
351
352 assert_eq!(expected, result);
353 }
354
355 #[test]
356 fn test_small_dataset() {
357 let random_data_path = "/small_data.bin";
358 let storage_provider = VirtualStorageProvider::new_overlay(".");
359
360 let result = write_random_data(
362 &storage_provider,
363 random_data_path,
364 DataType::Float,
365 5,
366 10,
367 100.0,
368 );
369
370 assert!(result.is_ok());
371 assert!(storage_provider.exists(random_data_path));
372 }
373
374 #[test]
375 fn test_large_block_size() {
376 let random_data_path = "/large_blocks.bin";
377 let storage_provider = VirtualStorageProvider::new_overlay(".");
378
379 let result = write_random_data(
381 &storage_provider,
382 random_data_path,
383 DataType::Float,
384 10,
385 200000, 100.0,
387 );
388
389 assert!(result.is_ok());
390 assert!(storage_provider.exists(random_data_path));
391 }
392}