use vortex_array::ExecutionCtx;
use vortex_error::VortexResult;
use super::ROOT_SCHEME_ID;
use super::sample::estimate_compression_ratio_with_sampling;
use crate::CascadingCompressor;
use crate::scheme::CompressionEstimate;
use crate::scheme::CompressorContext;
use crate::scheme::DeferredEstimate;
use crate::scheme::EstimateScore;
use crate::scheme::EstimateVerdict;
use crate::scheme::Scheme;
use crate::scheme::SchemeExt;
use crate::stats::ArrayAndStats;
use crate::trace;
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) enum WinnerEstimate {
AlwaysUse,
Score(EstimateScore),
}
impl WinnerEstimate {
pub(super) fn trace_ratio(self) -> Option<f64> {
match self {
Self::AlwaysUse => None,
Self::Score(score) => score.finite_ratio(),
}
}
}
fn is_better_score(
score: EstimateScore,
best: Option<&(&'static dyn Scheme, EstimateScore)>,
) -> bool {
score.is_valid() && best.is_none_or(|(_, best_score)| score.beats(*best_score))
}
impl CascadingCompressor {
pub(super) fn choose_best_scheme(
&self,
schemes: &[&'static dyn Scheme],
data: &ArrayAndStats,
compress_ctx: CompressorContext,
exec_ctx: &mut ExecutionCtx,
) -> VortexResult<Option<(&'static dyn Scheme, WinnerEstimate)>> {
let mut best: Option<(&'static dyn Scheme, EstimateScore)> = None;
let mut deferred: Vec<(&'static dyn Scheme, DeferredEstimate)> = Vec::new();
{
let _verdict_pass = trace::verdict_pass_span().entered();
for &scheme in schemes {
match scheme.expected_compression_ratio(data, compress_ctx.clone(), exec_ctx) {
CompressionEstimate::Verdict(EstimateVerdict::Skip) => {}
CompressionEstimate::Verdict(EstimateVerdict::AlwaysUse) => {
return Ok(Some((scheme, WinnerEstimate::AlwaysUse)));
}
CompressionEstimate::Verdict(EstimateVerdict::Ratio(ratio)) => {
let score = EstimateScore::FiniteCompression(ratio);
if is_better_score(score, best.as_ref()) {
best = Some((scheme, score));
}
}
CompressionEstimate::Deferred(deferred_estimate) => {
deferred.push((scheme, deferred_estimate));
}
}
}
}
for (scheme, deferred_estimate) in deferred {
let _span = trace::scheme_eval_span(scheme.id()).entered();
let threshold: Option<EstimateScore> = best.map(|(_, score)| score);
match deferred_estimate {
DeferredEstimate::Sample => {
let score = estimate_compression_ratio_with_sampling(
self,
scheme,
data.array(),
compress_ctx.clone(),
exec_ctx,
)?;
if is_better_score(score, best.as_ref()) {
best = Some((scheme, score));
}
}
DeferredEstimate::Callback(callback) => {
match callback(self, data, threshold, compress_ctx.clone(), exec_ctx)? {
EstimateVerdict::Skip => {}
EstimateVerdict::AlwaysUse => {
return Ok(Some((scheme, WinnerEstimate::AlwaysUse)));
}
EstimateVerdict::Ratio(ratio) => {
let score = EstimateScore::FiniteCompression(ratio);
if is_better_score(score, best.as_ref()) {
best = Some((scheme, score));
}
}
}
}
}
}
Ok(best.map(|(scheme, score)| (scheme, WinnerEstimate::Score(score))))
}
pub(super) fn is_excluded(&self, candidate: &dyn Scheme, ctx: &CompressorContext) -> bool {
let id = candidate.id();
let history = ctx.cascade_history();
if history.iter().any(|&(sid, _)| sid == id) {
return true;
}
let mut iter = history.iter().copied().peekable();
if let Some((_, child_idx)) = iter.next_if(|&(sid, _)| sid == ROOT_SCHEME_ID)
&& self
.root_exclusions
.iter()
.any(|rule| rule.excluded == id && rule.children.contains(child_idx))
{
return true;
}
for (ancestor_id, child_idx) in iter {
if let Some(ancestor) = self.schemes.iter().find(|s| s.id() == ancestor_id)
&& ancestor
.descendant_exclusions()
.iter()
.any(|rule| rule.excluded == id && rule.children.contains(child_idx))
{
return true;
}
}
for rule in candidate.ancestor_exclusions() {
if history
.iter()
.any(|(sid, cidx)| *sid == rule.ancestor && rule.children.contains(*cidx))
{
return true;
}
}
false
}
}