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