1mod block;
27mod config;
28pub(crate) mod dimensions;
29mod weights;
30pub(crate) use weights::decode_weight_at as decode_sparse_weight_at;
31#[cfg(any(feature = "native", feature = "wasm", test))]
32pub(crate) use weights::encode_weights as encode_sparse_weights;
33mod partitioner;
34
35pub use block::{BlockSparsePostingIterator, BlockSparsePostingList, SparseBlock};
36pub use config::{
37 IndexSize, QueryWeighting, SeismicConfig, SparseEntry, SparseFormat, SparseQueryConfig,
38 SparseVector, SparseVectorConfig, WeightQuantization,
39};
40pub use partitioner::optimal_partition;
41
42use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
43use std::io::{self, Read, Write};
44
45use super::posting_common::{read_vint, write_vint};
46use crate::DocId;
47
48#[derive(Debug, Clone, Copy)]
50pub struct SparsePosting {
51 pub doc_id: DocId,
52 pub weight: f32,
53}
54
55pub const SPARSE_BLOCK_SIZE: usize = 128;
57
58#[derive(Debug, Clone, Copy, PartialEq)]
63pub struct SparseSkipEntry {
64 pub first_doc: DocId,
66 pub last_doc: DocId,
68 pub offset: u64,
70 pub length: u32,
72 pub max_weight: f32,
74}
75
76impl SparseSkipEntry {
77 pub const SIZE: usize = 24; pub fn new(
81 first_doc: DocId,
82 last_doc: DocId,
83 offset: u64,
84 length: u32,
85 max_weight: f32,
86 ) -> Self {
87 Self {
88 first_doc,
89 last_doc,
90 offset,
91 length,
92 max_weight,
93 }
94 }
95
96 #[inline]
101 pub fn block_max_contribution(&self, query_weight: f32) -> f32 {
102 query_weight * self.max_weight
103 }
104
105 #[inline]
107 pub fn from_bytes(b: &[u8]) -> Self {
108 Self {
109 first_doc: u32::from_le_bytes(b[0..4].try_into().unwrap()),
110 last_doc: u32::from_le_bytes(b[4..8].try_into().unwrap()),
111 offset: u64::from_le_bytes(b[8..16].try_into().unwrap()),
112 length: u32::from_le_bytes(b[16..20].try_into().unwrap()),
113 max_weight: f32::from_le_bytes(b[20..24].try_into().unwrap()),
114 }
115 }
116
117 #[inline]
119 pub fn write_to_vec(&self, buf: &mut Vec<u8>) {
120 buf.extend_from_slice(&self.first_doc.to_le_bytes());
121 buf.extend_from_slice(&self.last_doc.to_le_bytes());
122 buf.extend_from_slice(&self.offset.to_le_bytes());
123 buf.extend_from_slice(&self.length.to_le_bytes());
124 buf.extend_from_slice(&self.max_weight.to_le_bytes());
125 }
126
127 #[inline]
129 pub fn read_at(skip_bytes: &[u8], idx: usize) -> Self {
130 let off = idx * Self::SIZE;
131 Self::from_bytes(&skip_bytes[off..off + Self::SIZE])
132 }
133
134 pub fn write<W: Write + ?Sized>(&self, writer: &mut W) -> io::Result<()> {
136 writer.write_u32::<LittleEndian>(self.first_doc)?;
137 writer.write_u32::<LittleEndian>(self.last_doc)?;
138 writer.write_u64::<LittleEndian>(self.offset)?;
139 writer.write_u32::<LittleEndian>(self.length)?;
140 writer.write_f32::<LittleEndian>(self.max_weight)?;
141 Ok(())
142 }
143
144 pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
146 let first_doc = reader.read_u32::<LittleEndian>()?;
147 let last_doc = reader.read_u32::<LittleEndian>()?;
148 let offset = reader.read_u64::<LittleEndian>()?;
149 let length = reader.read_u32::<LittleEndian>()?;
150 let max_weight = reader.read_f32::<LittleEndian>()?;
151 Ok(Self {
152 first_doc,
153 last_doc,
154 offset,
155 length,
156 max_weight,
157 })
158 }
159}
160
161#[derive(Debug, Clone, Default)]
163pub struct SparseSkipList {
164 entries: Vec<SparseSkipEntry>,
165 global_max_weight: f32,
167}
168
169impl SparseSkipList {
170 pub fn new() -> Self {
171 Self::default()
172 }
173
174 pub fn push(
176 &mut self,
177 first_doc: DocId,
178 last_doc: DocId,
179 offset: u64,
180 length: u32,
181 max_weight: f32,
182 ) {
183 self.global_max_weight = self.global_max_weight.max(max_weight);
184 self.entries.push(SparseSkipEntry::new(
185 first_doc, last_doc, offset, length, max_weight,
186 ));
187 }
188
189 pub fn len(&self) -> usize {
191 self.entries.len()
192 }
193
194 pub fn is_empty(&self) -> bool {
195 self.entries.is_empty()
196 }
197
198 pub fn get(&self, index: usize) -> Option<&SparseSkipEntry> {
200 self.entries.get(index)
201 }
202
203 pub fn global_max_weight(&self) -> f32 {
205 self.global_max_weight
206 }
207
208 pub fn find_block(&self, target: DocId) -> Option<usize> {
210 if self.entries.is_empty() {
211 return None;
212 }
213 let idx = self.entries.partition_point(|e| e.last_doc < target);
215 if idx < self.entries.len() {
216 Some(idx)
217 } else {
218 None
219 }
220 }
221
222 pub fn iter(&self) -> impl Iterator<Item = &SparseSkipEntry> {
224 self.entries.iter()
225 }
226
227 pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
229 writer.write_u32::<LittleEndian>(self.entries.len() as u32)?;
230 writer.write_f32::<LittleEndian>(self.global_max_weight)?;
231 for entry in &self.entries {
232 entry.write(writer)?;
233 }
234 Ok(())
235 }
236
237 pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
239 let count = reader.read_u32::<LittleEndian>()? as usize;
240 let global_max_weight = reader.read_f32::<LittleEndian>()?;
241 let mut entries = Vec::with_capacity(count);
242 for _ in 0..count {
243 entries.push(SparseSkipEntry::read(reader)?);
244 }
245 Ok(Self {
246 entries,
247 global_max_weight,
248 })
249 }
250}
251
252#[derive(Debug, Clone)]
258pub struct SparsePostingList {
259 quantization: WeightQuantization,
261 scale: f32,
263 min_val: f32,
265 doc_count: u32,
267 data: Vec<u8>,
269}
270
271impl SparsePostingList {
272 pub fn from_postings(
274 postings: &[(DocId, f32)],
275 quantization: WeightQuantization,
276 ) -> io::Result<Self> {
277 if postings.is_empty() {
278 return Ok(Self {
279 quantization,
280 scale: 1.0,
281 min_val: 0.0,
282 doc_count: 0,
283 data: Vec::new(),
284 });
285 }
286
287 let weights: Vec<f32> = postings.iter().map(|(_, w)| *w).collect();
289 let min_val = weights.iter().cloned().fold(f32::INFINITY, f32::min);
290 let max_val = weights.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
291
292 let (scale, adjusted_min) = match quantization {
293 WeightQuantization::Float32 | WeightQuantization::Float16 => (1.0, 0.0),
294 WeightQuantization::UInt8 => {
295 let range = max_val - min_val;
296 if range < f32::EPSILON {
297 (1.0, min_val)
298 } else {
299 (range / 255.0, min_val)
300 }
301 }
302 WeightQuantization::UInt4 => {
303 let range = max_val - min_val;
304 if range < f32::EPSILON {
305 (1.0, min_val)
306 } else {
307 (range / 15.0, min_val)
308 }
309 }
310 };
311
312 let mut data = Vec::new();
313
314 let mut prev_doc_id = 0u32;
316 for (doc_id, _) in postings {
317 let delta = doc_id - prev_doc_id;
318 write_vint(&mut data, delta as u64)?;
319 prev_doc_id = *doc_id;
320 }
321
322 match quantization {
324 WeightQuantization::Float32 => {
325 for (_, weight) in postings {
326 data.write_f32::<LittleEndian>(*weight)?;
327 }
328 }
329 WeightQuantization::Float16 => {
330 use half::slice::HalfFloatSliceExt;
332 let weights: Vec<f32> = postings.iter().map(|(_, w)| *w).collect();
333 let mut f16_slice: Vec<half::f16> = vec![half::f16::ZERO; weights.len()];
334 f16_slice.convert_from_f32_slice(&weights);
335 for h in f16_slice {
336 data.write_u16::<LittleEndian>(h.to_bits())?;
337 }
338 }
339 WeightQuantization::UInt8 => {
340 for (_, weight) in postings {
341 let quantized = ((*weight - adjusted_min) / scale).round() as u8;
342 data.write_u8(quantized)?;
343 }
344 }
345 WeightQuantization::UInt4 => {
346 let mut i = 0;
348 while i < postings.len() {
349 let q1 = ((postings[i].1 - adjusted_min) / scale).round() as u8 & 0x0F;
350 let q2 = if i + 1 < postings.len() {
351 ((postings[i + 1].1 - adjusted_min) / scale).round() as u8 & 0x0F
352 } else {
353 0
354 };
355 data.write_u8((q2 << 4) | q1)?;
356 i += 2;
357 }
358 }
359 }
360
361 Ok(Self {
362 quantization,
363 scale,
364 min_val: adjusted_min,
365 doc_count: postings.len() as u32,
366 data,
367 })
368 }
369
370 pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
372 writer.write_u8(self.quantization as u8)?;
373 writer.write_f32::<LittleEndian>(self.scale)?;
374 writer.write_f32::<LittleEndian>(self.min_val)?;
375 writer.write_u32::<LittleEndian>(self.doc_count)?;
376 writer.write_u32::<LittleEndian>(self.data.len() as u32)?;
377 writer.write_all(&self.data)?;
378 Ok(())
379 }
380
381 pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
383 let quant_byte = reader.read_u8()?;
384 let quantization = WeightQuantization::from_u8(quant_byte).ok_or_else(|| {
385 io::Error::new(io::ErrorKind::InvalidData, "Invalid quantization type")
386 })?;
387 let scale = reader.read_f32::<LittleEndian>()?;
388 let min_val = reader.read_f32::<LittleEndian>()?;
389 let doc_count = reader.read_u32::<LittleEndian>()?;
390 let data_len = reader.read_u32::<LittleEndian>()? as usize;
391 let mut data = vec![0u8; data_len];
392 reader.read_exact(&mut data)?;
393
394 Ok(Self {
395 quantization,
396 scale,
397 min_val,
398 doc_count,
399 data,
400 })
401 }
402
403 pub fn doc_count(&self) -> u32 {
405 self.doc_count
406 }
407
408 pub fn quantization(&self) -> WeightQuantization {
410 self.quantization
411 }
412
413 pub fn iterator(&self) -> SparsePostingIterator<'_> {
415 SparsePostingIterator::new(self)
416 }
417
418 pub fn decode_all(&self) -> io::Result<Vec<(DocId, f32)>> {
420 let mut result = Vec::with_capacity(self.doc_count as usize);
421 let mut iter = self.iterator();
422
423 while !iter.exhausted {
424 result.push((iter.doc_id, iter.weight));
425 iter.advance();
426 }
427
428 Ok(result)
429 }
430}
431
432pub struct SparsePostingIterator<'a> {
434 posting_list: &'a SparsePostingList,
435 doc_id_offset: usize,
437 weight_offset: usize,
439 index: usize,
441 doc_id: DocId,
443 weight: f32,
445 exhausted: bool,
447}
448
449impl<'a> SparsePostingIterator<'a> {
450 fn new(posting_list: &'a SparsePostingList) -> Self {
451 let mut iter = Self {
452 posting_list,
453 doc_id_offset: 0,
454 weight_offset: 0,
455 index: 0,
456 doc_id: 0,
457 weight: 0.0,
458 exhausted: posting_list.doc_count == 0,
459 };
460
461 if !iter.exhausted {
462 iter.weight_offset = iter.calculate_weight_offset();
464 iter.load_current();
465 }
466
467 iter
468 }
469
470 fn calculate_weight_offset(&self) -> usize {
471 let mut offset = 0;
473 let mut reader = &self.posting_list.data[..];
474
475 for _ in 0..self.posting_list.doc_count {
476 if read_vint(&mut reader).is_ok() {
477 offset = self.posting_list.data.len() - reader.len();
478 }
479 }
480
481 offset
482 }
483
484 fn load_current(&mut self) {
485 if self.index >= self.posting_list.doc_count as usize {
486 self.exhausted = true;
487 return;
488 }
489
490 let mut reader = &self.posting_list.data[self.doc_id_offset..];
492 if let Ok(delta) = read_vint(&mut reader) {
493 self.doc_id = self.doc_id.wrapping_add(delta as u32);
494 self.doc_id_offset = self.posting_list.data.len() - reader.len();
495 }
496
497 let weight_idx = self.index;
499 let pl = self.posting_list;
500
501 self.weight = match pl.quantization {
502 WeightQuantization::Float32 => {
503 let offset = self.weight_offset + weight_idx * 4;
504 if offset + 4 <= pl.data.len() {
505 let bytes = &pl.data[offset..offset + 4];
506 f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
507 } else {
508 0.0
509 }
510 }
511 WeightQuantization::Float16 => {
512 let offset = self.weight_offset + weight_idx * 2;
513 if offset + 2 <= pl.data.len() {
514 let bits = u16::from_le_bytes([pl.data[offset], pl.data[offset + 1]]);
515 half::f16::from_bits(bits).to_f32()
516 } else {
517 0.0
518 }
519 }
520 WeightQuantization::UInt8 => {
521 let offset = self.weight_offset + weight_idx;
522 if offset < pl.data.len() {
523 let quantized = pl.data[offset];
524 quantized as f32 * pl.scale + pl.min_val
525 } else {
526 0.0
527 }
528 }
529 WeightQuantization::UInt4 => {
530 let byte_offset = self.weight_offset + weight_idx / 2;
531 if byte_offset < pl.data.len() {
532 let byte = pl.data[byte_offset];
533 let quantized = if weight_idx.is_multiple_of(2) {
534 byte & 0x0F
535 } else {
536 (byte >> 4) & 0x0F
537 };
538 quantized as f32 * pl.scale + pl.min_val
539 } else {
540 0.0
541 }
542 }
543 };
544 }
545
546 pub fn doc(&self) -> DocId {
548 if self.exhausted {
549 super::TERMINATED
550 } else {
551 self.doc_id
552 }
553 }
554
555 pub fn weight(&self) -> f32 {
557 if self.exhausted { 0.0 } else { self.weight }
558 }
559
560 pub fn advance(&mut self) -> DocId {
562 if self.exhausted {
563 return super::TERMINATED;
564 }
565
566 self.index += 1;
567 if self.index >= self.posting_list.doc_count as usize {
568 self.exhausted = true;
569 return super::TERMINATED;
570 }
571
572 self.load_current();
573 self.doc_id
574 }
575
576 pub fn seek(&mut self, target: DocId) -> DocId {
578 while !self.exhausted && self.doc_id < target {
579 self.advance();
580 }
581 self.doc()
582 }
583}
584
585#[cfg(test)]
586mod tests {
587 use super::*;
588
589 #[test]
590 fn test_sparse_vector_dot_product() {
591 let v1 = SparseVector::from_entries(&[0, 2, 5], &[1.0, 2.0, 3.0]);
592 let v2 = SparseVector::from_entries(&[1, 2, 5], &[1.0, 4.0, 2.0]);
593
594 assert!((v1.dot(&v2) - 14.0).abs() < 1e-6);
596 }
597
598 #[test]
599 fn test_sparse_posting_list_float32() {
600 let postings = vec![(0, 1.5), (5, 2.3), (10, 0.8), (100, 3.15)];
601 let pl = SparsePostingList::from_postings(&postings, WeightQuantization::Float32).unwrap();
602
603 assert_eq!(pl.doc_count(), 4);
604
605 let mut iter = pl.iterator();
606 assert_eq!(iter.doc(), 0);
607 assert!((iter.weight() - 1.5).abs() < 1e-6);
608
609 iter.advance();
610 assert_eq!(iter.doc(), 5);
611 assert!((iter.weight() - 2.3).abs() < 1e-6);
612
613 iter.advance();
614 assert_eq!(iter.doc(), 10);
615
616 iter.advance();
617 assert_eq!(iter.doc(), 100);
618 assert!((iter.weight() - 3.15).abs() < 1e-6);
619
620 iter.advance();
621 assert_eq!(iter.doc(), super::super::TERMINATED);
622 }
623
624 #[test]
625 fn test_sparse_posting_list_uint8() {
626 let postings = vec![(0, 0.0), (5, 0.5), (10, 1.0)];
627 let pl = SparsePostingList::from_postings(&postings, WeightQuantization::UInt8).unwrap();
628
629 let decoded = pl.decode_all().unwrap();
630 assert_eq!(decoded.len(), 3);
631
632 assert!(decoded[0].1 < decoded[1].1);
634 assert!(decoded[1].1 < decoded[2].1);
635 }
636
637 #[test]
638 fn test_block_sparse_posting_list() {
639 let postings: Vec<(DocId, u16, f32)> =
641 (0..300).map(|i| (i * 2, 0, (i as f32) * 0.1)).collect();
642
643 let pl =
644 BlockSparsePostingList::from_postings(&postings, WeightQuantization::Float32).unwrap();
645
646 assert_eq!(pl.doc_count(), 300);
647 assert!(pl.num_blocks() >= 2);
648
649 let mut iter = pl.iterator();
651 for (expected_doc, _, expected_weight) in &postings {
652 assert_eq!(iter.doc(), *expected_doc);
653 assert!((iter.weight() - expected_weight).abs() < 1e-6);
654 iter.advance();
655 }
656 assert_eq!(iter.doc(), super::super::TERMINATED);
657 }
658
659 #[test]
660 fn test_block_sparse_seek() {
661 let postings: Vec<(DocId, u16, f32)> = (0..500).map(|i| (i * 3, 0, i as f32)).collect();
662
663 let pl =
664 BlockSparsePostingList::from_postings(&postings, WeightQuantization::Float32).unwrap();
665
666 let mut iter = pl.iterator();
667
668 assert_eq!(iter.seek(300), 300);
670
671 assert_eq!(iter.seek(301), 303);
673
674 assert_eq!(iter.seek(2000), super::super::TERMINATED);
676 }
677
678 #[test]
679 fn test_serialization_roundtrip() {
680 let postings: Vec<(DocId, u16, f32)> = vec![(0, 0, 1.0), (10, 0, 2.0), (100, 0, 3.0)];
681
682 for quant in [
683 WeightQuantization::Float32,
684 WeightQuantization::Float16,
685 WeightQuantization::UInt8,
686 ] {
687 let pl = BlockSparsePostingList::from_postings(&postings, quant).unwrap();
688
689 let (block_data, skip_entries) = pl.serialize().unwrap();
690 let pl2 =
691 BlockSparsePostingList::from_parts(pl.doc_count(), &block_data, &skip_entries)
692 .unwrap();
693
694 assert_eq!(pl.doc_count(), pl2.doc_count());
695
696 let mut iter1 = pl.iterator();
698 let mut iter2 = pl2.iterator();
699
700 while iter1.doc() != super::super::TERMINATED {
701 assert_eq!(iter1.doc(), iter2.doc());
702 assert!((iter1.weight() - iter2.weight()).abs() < 0.1);
703 iter1.advance();
704 iter2.advance();
705 }
706 }
707 }
708
709 #[test]
710 fn test_concatenate() {
711 let postings1: Vec<(DocId, u16, f32)> = vec![(0, 0, 1.0), (5, 1, 2.0)];
712 let postings2: Vec<(DocId, u16, f32)> = vec![(0, 0, 3.0), (10, 1, 4.0)];
713
714 let pl1 =
715 BlockSparsePostingList::from_postings(&postings1, WeightQuantization::Float32).unwrap();
716 let pl2 =
717 BlockSparsePostingList::from_postings(&postings2, WeightQuantization::Float32).unwrap();
718
719 let mut all: Vec<(DocId, u16, f32)> = pl1.decode_all();
721 for (doc_id, ord, w) in pl2.decode_all() {
722 all.push((doc_id + 100, ord, w));
723 }
724 let merged =
725 BlockSparsePostingList::from_postings(&all, WeightQuantization::Float32).unwrap();
726
727 assert_eq!(merged.doc_count(), 4);
728
729 let decoded = merged.decode_all();
730 assert_eq!(decoded[0], (0, 0, 1.0));
731 assert_eq!(decoded[1], (5, 1, 2.0));
732 assert_eq!(decoded[2], (100, 0, 3.0));
733 assert_eq!(decoded[3], (110, 1, 4.0));
734 }
735
736 #[test]
737 fn test_sparse_vector_config() {
738 let default = SparseVectorConfig::default();
740 assert_eq!(default.index_size, IndexSize::U32);
741 assert_eq!(default.weight_quantization, WeightQuantization::Float32);
742 assert_eq!(default.bytes_per_entry(), 8.0); let splade = SparseVectorConfig::splade();
747 assert_eq!(splade.index_size, IndexSize::U16);
748 assert_eq!(splade.weight_quantization, WeightQuantization::UInt8);
749 assert_eq!(splade.bytes_per_entry(), 3.0); assert_eq!(splade.weight_threshold, 0.01);
751 assert_eq!(splade.pruning, None);
752 assert!(splade.query_config.is_some());
753 let query_cfg = splade.query_config.as_ref().unwrap();
754 assert_eq!(query_cfg.heap_factor, 1.0);
755 assert_eq!(query_cfg.max_query_dims, None);
756 assert_eq!(query_cfg.pruning, None);
757
758 let bmp = SparseVectorConfig::splade_bmp();
759 assert_eq!(bmp.format, SparseFormat::Bmp);
760 assert_eq!(bmp.pruning, None);
761 let bmp_query_cfg = bmp.query_config.as_ref().unwrap();
762 assert_eq!(bmp_query_cfg.heap_factor, 1.0);
763 assert_eq!(bmp_query_cfg.max_query_dims, None);
764 assert_eq!(bmp_query_cfg.pruning, None);
765
766 let compact = SparseVectorConfig::compact();
768 assert_eq!(compact.index_size, IndexSize::U16);
769 assert_eq!(compact.weight_quantization, WeightQuantization::UInt4);
770 assert_eq!(compact.bytes_per_entry(), 2.5); let conservative = SparseVectorConfig::conservative();
774 assert_eq!(conservative.index_size, IndexSize::U32);
775 assert_eq!(
776 conservative.weight_quantization,
777 WeightQuantization::Float16
778 );
779 assert_eq!(conservative.weight_threshold, 0.005);
780 assert_eq!(conservative.pruning, None);
781
782 let byte = splade.to_byte();
784 let restored = SparseVectorConfig::from_byte(byte).unwrap();
785 assert_eq!(restored.index_size, splade.index_size);
786 assert_eq!(restored.weight_quantization, splade.weight_quantization);
787 }
790
791 #[test]
792 fn test_index_size() {
793 assert_eq!(IndexSize::U16.bytes(), 2);
794 assert_eq!(IndexSize::U32.bytes(), 4);
795 assert_eq!(IndexSize::U16.max_value(), 65535);
796 assert_eq!(IndexSize::U32.max_value(), u32::MAX);
797 }
798
799 #[test]
800 fn test_block_max_weight() {
801 let postings: Vec<(DocId, u16, f32)> = (0..300)
802 .map(|i| (i as DocId, 0, (i as f32) * 0.1))
803 .collect();
804
805 let pl =
806 BlockSparsePostingList::from_postings(&postings, WeightQuantization::Float32).unwrap();
807
808 assert!((pl.global_max_weight() - 29.9).abs() < 0.01);
809 assert!(pl.num_blocks() >= 3);
810
811 let block0_max = pl.block_max_weight(0).unwrap();
812 assert!((block0_max - 12.7).abs() < 0.01);
813
814 let block1_max = pl.block_max_weight(1).unwrap();
815 assert!((block1_max - 25.5).abs() < 0.01);
816
817 let block2_max = pl.block_max_weight(2).unwrap();
818 assert!((block2_max - 29.9).abs() < 0.01);
819
820 let query_weight = 2.0;
822 let mut iter = pl.iterator();
823 assert!((iter.current_block_max_weight() - 12.7).abs() < 0.01);
824 assert!((iter.current_block_max_contribution(query_weight) - 25.4).abs() < 0.1);
825
826 iter.seek(128);
827 assert!((iter.current_block_max_weight() - 25.5).abs() < 0.01);
828 }
829
830 #[test]
831 fn test_sparse_skip_list_serialization() {
832 let mut skip_list = SparseSkipList::new();
833 skip_list.push(0, 127, 0, 50, 12.7);
834 skip_list.push(128, 255, 100, 60, 25.5);
835 skip_list.push(256, 299, 200, 40, 29.9);
836
837 assert_eq!(skip_list.len(), 3);
838 assert!((skip_list.global_max_weight() - 29.9).abs() < 0.01);
839
840 let mut buffer = Vec::new();
842 skip_list.write(&mut buffer).unwrap();
843
844 let restored = SparseSkipList::read(&mut buffer.as_slice()).unwrap();
846
847 assert_eq!(restored.len(), 3);
848 assert!((restored.global_max_weight() - 29.9).abs() < 0.01);
849
850 let e0 = restored.get(0).unwrap();
852 assert_eq!(e0.first_doc, 0);
853 assert_eq!(e0.last_doc, 127);
854 assert!((e0.max_weight - 12.7).abs() < 0.01);
855
856 let e1 = restored.get(1).unwrap();
857 assert_eq!(e1.first_doc, 128);
858 assert!((e1.max_weight - 25.5).abs() < 0.01);
859 }
860}