vortex_btrblocks/schemes/integer/
for_.rs1use vortex_array::ArrayId;
7use vortex_array::ArrayRef;
8use vortex_array::Canonical;
9use vortex_array::ExecutionCtx;
10use vortex_array::IntoArray;
11use vortex_array::arrays::PrimitiveArray;
12use vortex_compressor::builtins::BinaryDictScheme;
13use vortex_compressor::builtins::FloatDictScheme;
14use vortex_compressor::builtins::IntDictScheme;
15use vortex_compressor::builtins::StringDictScheme;
16use vortex_compressor::scheme::AncestorExclusion;
17use vortex_compressor::scheme::ChildSelection;
18use vortex_compressor::scheme::CompressionEstimate;
19use vortex_compressor::scheme::EstimateVerdict;
20use vortex_error::VortexExpect;
21use vortex_error::VortexResult;
22use vortex_fastlanes::FoR;
23use vortex_fastlanes::FoRArrayExt;
24use vortex_fastlanes::FoRArraySlotsExt;
25use vortex_fastlanes::for_v1_id;
26use vortex_fastlanes::for_v2_id;
27
28use super::BitPackingScheme;
29use crate::ArrayAndStats;
30use crate::CascadingCompressor;
31use crate::CompressorContext;
32use crate::Scheme;
33use crate::SchemeExt;
34
35#[derive(Debug, Copy, Clone, PartialEq, Eq)]
36enum FoRSchemeMode {
37 V1,
38 V2,
39}
40
41pub(crate) static FOR_V1: FoRScheme = FoRScheme::v1();
43
44pub(crate) static FOR_V2: FoRScheme = FoRScheme::v2();
46
47#[derive(Debug, Copy, Clone, PartialEq, Eq)]
57pub struct FoRScheme {
58 mode: FoRSchemeMode,
59}
60
61impl FoRScheme {
62 pub const fn v1() -> Self {
64 Self {
65 mode: FoRSchemeMode::V1,
66 }
67 }
68
69 pub const fn v2() -> Self {
71 Self {
72 mode: FoRSchemeMode::V2,
73 }
74 }
75}
76
77impl Default for FoRScheme {
78 fn default() -> Self {
79 Self::v1()
80 }
81}
82
83impl Scheme for FoRScheme {
84 fn scheme_name(&self) -> &'static str {
85 "vortex.int.for"
86 }
87
88 fn matches(&self, canonical: &Canonical) -> bool {
89 canonical.dtype().is_int()
90 }
91
92 fn produced_encodings(&self) -> Vec<ArrayId> {
93 match self.mode {
94 FoRSchemeMode::V1 => vec![for_v1_id()],
95 FoRSchemeMode::V2 => vec![for_v1_id(), for_v2_id()],
96 }
97 }
98
99 fn refine(&self, allowed: &dyn Fn(&ArrayId) -> bool) -> &dyn Scheme {
100 if allowed(&for_v2_id()) {
101 &FOR_V2
102 } else {
103 &FOR_V1
104 }
105 }
106
107 fn num_children(&self) -> usize {
109 match self.mode {
110 FoRSchemeMode::V1 => 0,
111 FoRSchemeMode::V2 => 1,
112 }
113 }
114
115 fn ancestor_exclusions(&self) -> Vec<AncestorExclusion> {
117 vec![
118 AncestorExclusion {
119 ancestor: IntDictScheme.id(),
120 children: ChildSelection::One(1),
121 },
122 AncestorExclusion {
123 ancestor: FloatDictScheme.id(),
124 children: ChildSelection::One(1),
125 },
126 AncestorExclusion {
127 ancestor: StringDictScheme.id(),
128 children: ChildSelection::One(1),
129 },
130 AncestorExclusion {
131 ancestor: BinaryDictScheme.id(),
132 children: ChildSelection::One(1),
133 },
134 ]
135 }
136
137 fn expected_compression_ratio(
138 &self,
139 data: &ArrayAndStats,
140 compress_ctx: CompressorContext,
141 exec_ctx: &mut ExecutionCtx,
142 ) -> CompressionEstimate {
143 if compress_ctx.finished_cascading() {
146 return CompressionEstimate::Verdict(EstimateVerdict::Skip);
147 }
148
149 let stats = data.integer_stats(exec_ctx);
155
156 if stats.erased().min_is_zero() {
158 return CompressionEstimate::Verdict(EstimateVerdict::Skip);
159 }
160
161 let for_bitwidth = match stats.erased().max_minus_min().checked_ilog2() {
163 Some(l) => l + 1,
164 None => return CompressionEstimate::Verdict(EstimateVerdict::Skip),
166 };
167
168 if let Some(max_log) = stats
172 .erased()
173 .max_ilog2()
174 .filter(|_| !stats.erased().min_is_negative())
176 {
177 let bitpack_bitwidth = max_log + 1;
178 if for_bitwidth >= bitpack_bitwidth {
179 return CompressionEstimate::Verdict(EstimateVerdict::Skip);
180 }
181 }
182
183 let full_width: u32 = data
184 .array_as_primitive()
185 .ptype()
186 .bit_width()
187 .try_into()
188 .vortex_expect("bit width must fit in u32");
189
190 CompressionEstimate::Verdict(EstimateVerdict::Ratio(
191 full_width as f64 / for_bitwidth as f64,
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 for_array = match self.mode {
204 FoRSchemeMode::V1 => FoR::encode(primitive, exec_ctx)?,
205 FoRSchemeMode::V2 => FoR::encode_chunked(primitive, exec_ctx)?,
206 };
207 let biased = for_array
208 .encoded()
209 .clone()
210 .execute::<PrimitiveArray>(exec_ctx)?;
211
212 let leaf_ctx = compress_ctx.clone().as_leaf();
217 let biased_data =
218 ArrayAndStats::new(biased.into_array(), compress_ctx.merged_stats_options());
219 let compressed = BitPackingScheme.compress(compressor, &biased_data, leaf_ctx, exec_ctx)?;
220
221 let for_compressed = match for_array.constant_reference() {
223 Some(reference) => FoR::try_new(compressed, reference)?,
224 None => {
225 let references = compressor.compress_child(
226 for_array.references(),
227 &compress_ctx,
228 self.id(),
229 0,
230 exec_ctx,
231 )?;
232 FoR::try_new_chunked(compressed, references, for_array.offset())?
233 }
234 };
235 for_compressed
236 .as_ref()
237 .statistics()
238 .inherit_from(for_array.as_ref().statistics());
239
240 Ok(for_compressed.into_array())
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use rstest::rstest;
247 use vortex_array::ArrayId;
248 use vortex_fastlanes::for_v1_id;
249 use vortex_fastlanes::for_v2_id;
250
251 use super::FOR_V1;
252 use super::FOR_V2;
253 use crate::Scheme;
254 use crate::SchemeExt;
255
256 #[rstest]
258 #[case::neither(false, false, false)]
259 #[case::v1(true, false, false)]
260 #[case::v2_only(false, true, true)]
261 #[case::both(true, true, true)]
262 fn refine_picks_v2_when_v2_id_is_allowed(
263 #[case] allow_v1: bool,
264 #[case] allow_v2: bool,
265 #[case] expect_v2: bool,
266 #[values(&FOR_V1, &FOR_V2)] scheme: &'static dyn Scheme,
267 ) {
268 let allowed =
269 |id: &ArrayId| (allow_v1 && *id == for_v1_id()) || (allow_v2 && *id == for_v2_id());
270 let refined = scheme.refine(&allowed);
271 assert_eq!(refined.id(), scheme.id());
272 let expected: &dyn Scheme = if expect_v2 { &FOR_V2 } else { &FOR_V1 };
273 assert_eq!(refined.produced_encodings(), expected.produced_encodings());
274 }
275}