use vortex_array::ArrayRef;
use vortex_array::Canonical;
use vortex_array::ExecutionCtx;
use vortex_array::IntoArray;
use vortex_array::arrays::PrimitiveArray;
use vortex_compressor::builtins::BinaryDictScheme;
use vortex_compressor::builtins::FloatDictScheme;
use vortex_compressor::builtins::IntDictScheme;
use vortex_compressor::builtins::StringDictScheme;
use vortex_compressor::estimate::CompressionEstimate;
use vortex_compressor::estimate::DeferredEstimate;
use vortex_compressor::estimate::EstimateScore;
use vortex_compressor::estimate::EstimateVerdict;
use vortex_compressor::scheme::AncestorExclusion;
use vortex_compressor::scheme::ChildSelection;
use vortex_compressor::scheme::DescendantExclusion;
use vortex_error::VortexResult;
use vortex_fastlanes::Delta;
use crate::ArrayAndStats;
use crate::CascadingCompressor;
use crate::CompressorContext;
use crate::GenerateStatsOptions;
use crate::Scheme;
use crate::SchemeExt;
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct DeltaScheme {
min_ratio: f64,
}
impl DeltaScheme {
pub const fn new(min_ratio: f64) -> Self {
Self { min_ratio }
}
}
impl Default for DeltaScheme {
fn default() -> Self {
Self::new(1.25)
}
}
const DELTA_PENALTY: f64 = 0.95;
const MIN_DELTA_LEN: usize = 1024;
impl Scheme for DeltaScheme {
fn scheme_name(&self) -> &'static str {
"vortex.int.delta"
}
fn matches(&self, canonical: &Canonical) -> bool {
canonical.dtype().is_int()
}
fn num_children(&self) -> usize {
2
}
fn descendant_exclusions(&self) -> Vec<DescendantExclusion> {
vec![DescendantExclusion {
excluded: self.id(),
children: ChildSelection::All,
}]
}
fn ancestor_exclusions(&self) -> Vec<AncestorExclusion> {
vec![
AncestorExclusion {
ancestor: IntDictScheme.id(),
children: ChildSelection::One(1),
},
AncestorExclusion {
ancestor: FloatDictScheme.id(),
children: ChildSelection::One(1),
},
AncestorExclusion {
ancestor: StringDictScheme.id(),
children: ChildSelection::One(1),
},
AncestorExclusion {
ancestor: BinaryDictScheme.id(),
children: ChildSelection::One(1),
},
]
}
fn expected_compression_ratio(
&self,
data: &ArrayAndStats,
compress_ctx: CompressorContext,
_exec_ctx: &mut ExecutionCtx,
) -> CompressionEstimate {
if compress_ctx.finished_cascading() {
return CompressionEstimate::Verdict(EstimateVerdict::Skip);
}
if data.array_len() < MIN_DELTA_LEN {
return CompressionEstimate::Verdict(EstimateVerdict::Skip);
}
let min_ratio = self.min_ratio;
CompressionEstimate::Deferred(DeferredEstimate::Callback(Box::new(
move |_compressor, data, best_so_far, _ctx, exec_ctx| {
let primitive = data.array().clone().execute::<PrimitiveArray>(exec_ctx)?;
let full_width = primitive.ptype().bit_width() as f64;
let threshold = best_so_far.and_then(EstimateScore::finite_ratio);
if threshold.is_some_and(|t| full_width * DELTA_PENALTY <= t) {
return Ok(EstimateVerdict::Skip);
}
let (_bases, deltas) = vortex_fastlanes::delta_compress(&primitive, exec_ctx)?;
let delta_stats =
ArrayAndStats::new(deltas.into_array(), GenerateStatsOptions::default());
let span = delta_stats.integer_stats(exec_ctx).erased().max_minus_min();
let delta_bits = match span.checked_ilog2() {
Some(l) => (l + 1) as f64,
None => return Ok(EstimateVerdict::Skip),
};
let ratio = full_width / delta_bits * DELTA_PENALTY;
if ratio <= min_ratio {
return Ok(EstimateVerdict::Skip);
}
Ok(EstimateVerdict::Ratio(ratio))
},
)))
}
fn compress(
&self,
compressor: &CascadingCompressor,
data: &ArrayAndStats,
compress_ctx: CompressorContext,
exec_ctx: &mut ExecutionCtx,
) -> VortexResult<ArrayRef> {
let primitive = data.array().clone().execute::<PrimitiveArray>(exec_ctx)?;
let len = primitive.len();
let (bases, deltas) = vortex_fastlanes::delta_compress(&primitive, exec_ctx)?;
let compressed_bases = compressor.compress_child(
&bases.into_array(),
&compress_ctx,
self.id(),
0,
exec_ctx,
)?;
let compressed_deltas = compressor.compress_child(
&deltas.into_array(),
&compress_ctx,
self.id(),
1,
exec_ctx,
)?;
Delta::try_new(compressed_bases, compressed_deltas, 0, len).map(IntoArray::into_array)
}
}