vortex_btrblocks/schemes/integer/
delta.rs1use vortex_array::ArrayId;
7use vortex_array::ArrayRef;
8use vortex_array::Canonical;
9use vortex_array::ExecutionCtx;
10use vortex_array::IntoArray;
11use vortex_array::VTable;
12use vortex_array::arrays::PrimitiveArray;
13use vortex_compressor::builtins::BinaryDictScheme;
14use vortex_compressor::builtins::FloatDictScheme;
15use vortex_compressor::builtins::IntDictScheme;
16use vortex_compressor::builtins::StringDictScheme;
17use vortex_compressor::scheme::AncestorExclusion;
18use vortex_compressor::scheme::ChildSelection;
19use vortex_compressor::scheme::CompressionEstimate;
20use vortex_compressor::scheme::DeferredEstimate;
21use vortex_compressor::scheme::DescendantExclusion;
22use vortex_compressor::scheme::EstimateScore;
23use vortex_compressor::scheme::EstimateVerdict;
24use vortex_error::VortexResult;
25use vortex_fastlanes::Delta;
26use vortex_fastlanes::FL_CHUNK_SIZE;
27
28use crate::ArrayAndStats;
29use crate::CascadingCompressor;
30use crate::CompressorContext;
31use crate::GenerateStatsOptions;
32use crate::Scheme;
33use crate::SchemeExt;
34
35#[derive(Debug, Copy, Clone, PartialEq)]
44pub struct DeltaScheme {
45 min_ratio: f64,
46}
47
48impl DeltaScheme {
49 pub const fn new(min_ratio: f64) -> Self {
54 Self { min_ratio }
55 }
56}
57
58impl Default for DeltaScheme {
59 fn default() -> Self {
60 Self::new(1.25)
61 }
62}
63
64const DELTA_PENALTY: f64 = 0.95;
71
72const MIN_DELTA_LEN: usize = 1024;
74
75fn penalized_ratio(len: usize, full_width: f64, delta_bits: f64) -> f64 {
87 let padded_len = len.next_multiple_of(FL_CHUNK_SIZE) as f64;
88 (len as f64) * full_width / (padded_len * (delta_bits + 1.0)) * DELTA_PENALTY
89}
90
91impl Scheme for DeltaScheme {
92 fn scheme_name(&self) -> &'static str {
93 "vortex.int.delta"
94 }
95
96 fn matches(&self, canonical: &Canonical) -> bool {
97 canonical.dtype().is_int()
98 }
99
100 fn produced_encodings(&self) -> Vec<ArrayId> {
101 vec![Delta.id()]
102 }
103
104 fn num_children(&self) -> usize {
105 2
106 }
107
108 fn descendant_exclusions(&self) -> Vec<DescendantExclusion> {
111 vec![DescendantExclusion {
112 excluded: self.id(),
113 children: ChildSelection::All,
114 }]
115 }
116
117 fn ancestor_exclusions(&self) -> Vec<AncestorExclusion> {
120 vec![
121 AncestorExclusion {
122 ancestor: IntDictScheme.id(),
123 children: ChildSelection::One(1),
124 },
125 AncestorExclusion {
126 ancestor: FloatDictScheme.id(),
127 children: ChildSelection::One(1),
128 },
129 AncestorExclusion {
130 ancestor: StringDictScheme.id(),
131 children: ChildSelection::One(1),
132 },
133 AncestorExclusion {
134 ancestor: BinaryDictScheme.id(),
135 children: ChildSelection::One(1),
136 },
137 ]
138 }
139
140 fn expected_compression_ratio(
141 &self,
142 data: &ArrayAndStats,
143 compress_ctx: CompressorContext,
144 _exec_ctx: &mut ExecutionCtx,
145 ) -> CompressionEstimate {
146 if compress_ctx.finished_cascading() {
148 return CompressionEstimate::Verdict(EstimateVerdict::Skip);
149 }
150 if data.array_len() < MIN_DELTA_LEN {
152 return CompressionEstimate::Verdict(EstimateVerdict::Skip);
153 }
154
155 let min_ratio = self.min_ratio;
158 CompressionEstimate::Deferred(DeferredEstimate::Callback(Box::new(
159 move |_compressor, data, best_so_far, _ctx, exec_ctx| {
160 let primitive = data.array().clone().execute::<PrimitiveArray>(exec_ctx)?;
161 let full_width = primitive.ptype().bit_width() as f64;
162 let len = primitive.len();
163
164 let threshold = best_so_far.and_then(EstimateScore::finite_ratio);
167 if threshold.is_some_and(|t| penalized_ratio(len, full_width, 1.0) <= t) {
168 return Ok(EstimateVerdict::Skip);
169 }
170
171 let (_bases, deltas) = vortex_fastlanes::delta_compress(&primitive, exec_ctx)?;
175 let delta_stats =
176 ArrayAndStats::new(deltas.into_array(), GenerateStatsOptions::default());
177 let span = delta_stats.integer_stats(exec_ctx).erased().max_minus_min();
178
179 let delta_bits = match span.checked_ilog2() {
182 Some(l) => (l + 1) as f64,
183 None => return Ok(EstimateVerdict::Skip),
184 };
185
186 let ratio = penalized_ratio(len, full_width, delta_bits);
187 if ratio <= min_ratio {
188 return Ok(EstimateVerdict::Skip);
189 }
190 Ok(EstimateVerdict::Ratio(ratio))
191 },
192 )))
193 }
194
195 fn compress(
196 &self,
197 compressor: &CascadingCompressor,
198 data: &ArrayAndStats,
199 compress_ctx: CompressorContext,
200 exec_ctx: &mut ExecutionCtx,
201 ) -> VortexResult<ArrayRef> {
202 let primitive = data.array().clone().execute::<PrimitiveArray>(exec_ctx)?;
203 let len = primitive.len();
204 let (bases, deltas) = vortex_fastlanes::delta_compress(&primitive, exec_ctx)?;
205
206 let compressed_bases = compressor.compress_child(
207 &bases.into_array(),
208 &compress_ctx,
209 self.id(),
210 0,
211 exec_ctx,
212 )?;
213 let compressed_deltas = compressor.compress_child(
214 &deltas.into_array(),
215 &compress_ctx,
216 self.id(),
217 1,
218 exec_ctx,
219 )?;
220
221 Delta::try_new(
222 compressed_bases,
223 compressed_deltas,
224 primitive.validity()?,
225 0,
226 len,
227 )
228 .map(IntoArray::into_array)
229 }
230}