use std::sync::Arc;
use crate::vtree::Vtree;
use crate::score::VtreeScores;
pub const MAX_CANDIDATES: usize = 24;
pub const fn retains_set(keep: usize) -> bool {
keep > 1
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CandidateRankMetric {
Cost,
PeakContextWidthShow,
PeakContextWidthAll,
}
impl CandidateRankMetric {
pub fn parse(s: &str) -> Option<Self> {
match s {
"cost" => Some(CandidateRankMetric::Cost),
"peak_context_width_show" => Some(CandidateRankMetric::PeakContextWidthShow),
"peak_context_width_all" => Some(CandidateRankMetric::PeakContextWidthAll),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
CandidateRankMetric::Cost => "cost",
CandidateRankMetric::PeakContextWidthShow => "peak_context_width_show",
CandidateRankMetric::PeakContextWidthAll => "peak_context_width_all",
}
}
pub(crate) fn value(self, s: &VtreeScores) -> f64 {
match self {
CandidateRankMetric::Cost => s.cost,
CandidateRankMetric::PeakContextWidthShow => {
s.peak_context_width_show
.unwrap_or(s.peak_context_width_all) as f64
}
CandidateRankMetric::PeakContextWidthAll => s.peak_context_width_all as f64,
}
}
}
#[derive(Clone)]
pub struct VtreeCandidate {
pub built_by: Vec<String>,
pub vtree: Arc<Vtree>,
pub scores: VtreeScores,
pub selected: bool,
}
#[derive(Clone, Debug)]
pub struct CandidateSet {
pub metric: CandidateRankMetric,
pub candidates: Vec<VtreeCandidate>,
}
impl Default for CandidateSet {
fn default() -> Self {
CandidateSet {
metric: CandidateRankMetric::Cost,
candidates: Vec::new(),
}
}
}
impl CandidateSet {
pub fn is_empty(&self) -> bool {
self.candidates.is_empty()
}
}
impl std::fmt::Debug for VtreeCandidate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VtreeCandidate")
.field("built_by", &self.built_by)
.field("selected", &self.selected)
.field("scores", &self.scores)
.finish_non_exhaustive()
}
}
pub(crate) struct ScoredVtree {
pub built_by: String,
pub vtree: Arc<Vtree>,
pub scores: VtreeScores,
}
pub(crate) fn from_scored(
scored: Vec<ScoredVtree>,
selected: &Arc<Vtree>,
metric: CandidateRankMetric,
keep: usize,
) -> CandidateSet {
if !retains_set(keep) || scored.is_empty() {
return CandidateSet {
metric,
candidates: Vec::new(),
};
}
debug_assert!(
scored
.windows(2)
.all(|w| w[0].scores.peak_context_width_show.is_some()
== w[1].scores.peak_context_width_show.is_some()),
"a candidate set mixes projected and non-projected scores",
);
let winner_key = selected.to_vtree_text();
let mut keys: Vec<String> = Vec::with_capacity(scored.len());
let mut out: Vec<VtreeCandidate> = Vec::with_capacity(scored.len());
for ScoredVtree {
built_by,
vtree,
scores,
} in scored
{
let key = vtree.to_vtree_text();
if let Some(pos) = keys.iter().position(|k| *k == key) {
out[pos].built_by.push(built_by);
continue;
}
let selected = key == winner_key;
keys.push(key);
out.push(VtreeCandidate {
built_by: vec![built_by],
vtree,
scores,
selected,
});
}
out.sort_by(|a, b| {
b.selected
.cmp(&a.selected)
.then_with(|| cmp_f64(metric.value(&a.scores), metric.value(&b.scores)))
.then_with(|| cmp_f64(a.scores.clause_load_stddev, b.scores.clause_load_stddev))
.then_with(|| cmp_f64(a.scores.cost, b.scores.cost))
.then_with(|| a.built_by[0].cmp(&b.built_by[0]))
});
out.truncate(keep);
CandidateSet {
metric,
candidates: out,
}
}
fn cmp_f64(a: f64, b: f64) -> std::cmp::Ordering {
a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal)
}