use crate::config::QueryConfig;
use crate::types::*;
pub struct AdaptiveController {
config: QueryConfig,
}
impl AdaptiveController {
pub fn new(config: QueryConfig) -> Self {
Self { config }
}
pub fn compute_confidence(&self, results: &[ScoredCandidate], k: usize) -> ConfidenceSignals {
if results.is_empty() {
return ConfidenceSignals {
score_gap: 0.0,
entropy: 0.0,
coverage: 0.0,
confidence: 0.0,
};
}
let score_gap = if results.len() > k {
let k_score = results.get(k - 1).map(|c| c.score).unwrap_or(0.0);
let two_k_score = results.get(2 * k - 1).map(|c| c.score).unwrap_or(0.0);
(k_score - two_k_score).abs()
} else {
0.0
};
let entropy = self.compute_entropy(&results[..k.min(results.len())]);
let coverage = (results.len() as f32) / (k as f32).max(1.0);
let confidence = self.combine_signals(score_gap, entropy, coverage);
ConfidenceSignals {
score_gap,
entropy,
coverage,
confidence,
}
}
fn compute_entropy(&self, results: &[ScoredCandidate]) -> f32 {
if results.is_empty() {
return 0.0;
}
let scores: Vec<f32> = results.iter().map(|c| c.score.max(0.001)).collect();
let sum: f32 = scores.iter().sum();
if sum <= 0.0 {
return 0.0;
}
let probs: Vec<f32> = scores.iter().map(|s| s / sum).collect();
let entropy: f32 = probs
.iter()
.filter(|&&p| p > 0.0)
.map(|&p| -p * p.ln())
.sum();
let max_entropy = (results.len() as f32).ln();
if max_entropy > 0.0 {
entropy / max_entropy
} else {
0.0
}
}
fn combine_signals(&self, score_gap: f32, entropy: f32, coverage: f32) -> f32 {
let gap_signal = (score_gap / self.config.score_gap_threshold).min(1.0);
let entropy_signal = 1.0 - entropy;
let coverage_signal = coverage.min(1.0);
0.4 * gap_signal + 0.3 * entropy_signal + 0.3 * coverage_signal
}
pub fn should_widen(&self, confidence: f32) -> bool {
confidence < 0.5 }
pub fn compute_widening(
&self,
signals: &ConfidenceSignals,
_params: &QueryParams,
) -> WideningParams {
if signals.confidence >= 0.5 {
return WideningParams::none();
}
let factor = self.config.widening_factor;
if signals.confidence < 0.2 {
WideningParams {
l_a_factor: factor * 2.0,
l_b_factor: factor * 2.0,
r_factor: factor,
router_probes_factor: 2.0,
}
} else if signals.confidence < 0.35 {
WideningParams {
l_a_factor: factor,
l_b_factor: factor * 1.5,
r_factor: 1.0,
router_probes_factor: 1.5,
}
} else {
WideningParams {
l_a_factor: 1.0,
l_b_factor: factor,
r_factor: 1.0,
router_probes_factor: 1.0,
}
}
}
pub fn apply_filter_widening(
&self,
params: &mut QueryParams,
selectivity: f32,
max_factor: f32,
) {
if selectivity >= 1.0 || selectivity <= 0.0 {
return;
}
let factor = (1.0 / selectivity).min(max_factor);
params.l_a = ((params.l_a as f32) * factor) as usize;
params.l_b = ((params.l_b as f32) * factor) as usize;
}
}
#[derive(Debug, Clone)]
pub struct ConfidenceSignals {
pub score_gap: f32,
pub entropy: f32,
pub coverage: f32,
pub confidence: f32,
}
#[derive(Debug, Clone)]
pub struct WideningParams {
pub l_a_factor: f32,
pub l_b_factor: f32,
pub r_factor: f32,
pub router_probes_factor: f32,
}
impl WideningParams {
pub fn none() -> Self {
Self {
l_a_factor: 1.0,
l_b_factor: 1.0,
r_factor: 1.0,
router_probes_factor: 1.0,
}
}
pub fn is_identity(&self) -> bool {
self.l_a_factor == 1.0
&& self.l_b_factor == 1.0
&& self.r_factor == 1.0
&& self.router_probes_factor == 1.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_confidence_high() {
let config = QueryConfig::default();
let controller = AdaptiveController::new(config);
let results: Vec<ScoredCandidate> = (0..20)
.map(|i| ScoredCandidate {
id: i as u32,
score: 1.0 - (i as f32) * 0.05,
})
.collect();
let signals = controller.compute_confidence(&results, 10);
assert!(
signals.confidence > 0.5,
"Expected high confidence: {}",
signals.confidence
);
assert!(!controller.should_widen(signals.confidence));
}
#[test]
fn test_confidence_low() {
let config = QueryConfig::default();
let controller = AdaptiveController::new(config);
let results: Vec<ScoredCandidate> = (0..20)
.map(|i| ScoredCandidate {
id: i as u32,
score: 1.0, })
.collect();
let signals = controller.compute_confidence(&results, 10);
assert!(
signals.entropy > 0.8,
"Expected high entropy: {}",
signals.entropy
);
}
#[test]
fn test_widening_params() {
let config = QueryConfig::default();
let controller = AdaptiveController::new(config);
let params = QueryParams::default();
let low_confidence = ConfidenceSignals {
score_gap: 0.01,
entropy: 0.9,
coverage: 0.5,
confidence: 0.2,
};
let widening = controller.compute_widening(&low_confidence, ¶ms);
assert!(widening.l_b_factor > 1.0);
assert!(widening.l_a_factor > 1.0);
}
#[test]
fn test_filter_widening() {
let config = QueryConfig::default();
let controller = AdaptiveController::new(config);
let mut params = QueryParams {
k: 10,
l_a: 1000,
l_b: 2000,
..Default::default()
};
controller.apply_filter_widening(&mut params, 0.1, 5.0);
assert_eq!(params.l_a, 5000);
assert_eq!(params.l_b, 10000);
}
}