#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PathQualityWeights {
pub alpha: f32,
pub beta: f32,
pub gamma: f32,
pub delta: f32,
}
impl Default for PathQualityWeights {
fn default() -> Self {
Self {
alpha: 0.25,
beta: 0.30,
gamma: 0.25,
delta: 0.20,
}
}
}
impl PathQualityWeights {
pub fn new(alpha: f32, beta: f32, gamma: f32, delta: f32) -> Self {
Self { alpha, beta, gamma, delta }
}
pub fn terminal_focused() -> Self {
Self {
alpha: 0.15,
beta: 0.50,
gamma: 0.20,
delta: 0.15,
}
}
pub fn coherence_focused() -> Self {
Self {
alpha: 0.20,
beta: 0.20,
gamma: 0.45,
delta: 0.15,
}
}
pub fn completion_focused() -> Self {
Self {
alpha: 0.20,
beta: 0.25,
gamma: 0.15,
delta: 0.40,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PathQualityFactors {
pub linearity: f32,
pub terminal_score: f32,
pub coherence: f32,
pub completion: f32,
}
impl PathQualityFactors {
pub fn new(linearity: f32, terminal_score: f32, coherence: f32, completion: f32) -> Self {
Self {
linearity: linearity.clamp(0.0, 1.0),
terminal_score: terminal_score.clamp(0.0, 1.0),
coherence: coherence.clamp(0.0, 1.0),
completion: completion.clamp(0.0, 1.0),
}
}
pub fn from_path_data(
depths: &[u32],
homogeneities: &[f32],
max_depth: u32,
is_terminal: bool,
terminal_quality: f32,
) -> Self {
let linearity = Self::compute_linearity(depths);
let terminal_score = if is_terminal { terminal_quality } else { 0.5 };
let coherence = Self::compute_coherence(homogeneities);
let completion = Self::compute_completion(depths.len(), max_depth as usize);
Self::new(linearity, terminal_score, coherence, completion)
}
fn compute_linearity(depths: &[u32]) -> f32 {
if depths.len() < 2 {
return 1.0; }
let changes: Vec<i32> = depths.windows(2)
.map(|w| w[1] as i32 - w[0] as i32)
.collect();
let n = changes.len() as f32;
let mean: f32 = changes.iter().map(|&c| c as f32).sum::<f32>() / n;
let variance: f32 = changes.iter()
.map(|&c| (c as f32 - mean).powi(2))
.sum::<f32>() / n;
(1.0 - variance / 5.0).clamp(0.0, 1.0)
}
fn compute_coherence(homogeneities: &[f32]) -> f32 {
if homogeneities.is_empty() {
return 0.5; }
let sum: f32 = homogeneities.iter().sum();
sum / homogeneities.len() as f32
}
fn compute_completion(path_length: usize, max_depth: usize) -> f32 {
if max_depth == 0 {
return 1.0; }
let expected_length = max_depth + 1;
(path_length as f32 / expected_length as f32).min(1.0)
}
#[inline]
pub fn compute_quality(&self, weights: &PathQualityWeights) -> f32 {
weights.alpha * self.linearity
+ weights.beta * self.terminal_score
+ weights.gamma * self.coherence
+ weights.delta * self.completion
}
#[inline]
pub fn quality(&self) -> f32 {
self.compute_quality(&PathQualityWeights::default())
}
#[inline]
pub fn is_high_quality(&self) -> bool {
self.quality() > 0.7
}
#[inline]
pub fn is_low_quality(&self) -> bool {
self.quality() < 0.4
}
}
impl Default for PathQualityFactors {
fn default() -> Self {
Self {
linearity: 0.5,
terminal_score: 0.5,
coherence: 0.5,
completion: 0.5,
}
}
}
pub struct PathQuality;
impl PathQuality {
#[inline]
pub fn enhance_salience(base_salience: f32, quality: f32, blend: f32) -> f32 {
let blend = blend.clamp(0.0, 1.0);
(1.0 - blend) * base_salience + blend * quality
}
#[inline]
pub fn terminal_boost(base_salience: f32, quality: f32, is_terminal: bool) -> f32 {
if is_terminal {
base_salience + 0.5 * quality * (1.0 - base_salience)
} else {
base_salience
}
}
pub fn compute(
depths: &[u32],
homogeneities: &[f32],
max_depth: u32,
is_terminal: bool,
terminal_quality: f32,
) -> PathQualityFactors {
PathQualityFactors::from_path_data(
depths,
homogeneities,
max_depth,
is_terminal,
terminal_quality,
)
}
pub fn terminal_quality_from_phase(phase: Option<&str>) -> f32 {
match phase {
Some("synthesis") => 0.9, Some("planning") => 0.85, Some("consolidation") => 0.7, Some("exploration") => 0.5, Some("debugging") => 0.4, None => 0.5, _ => 0.5,
}
}
pub fn terminal_quality_from_feedback(has_thumbs_up: bool, has_thumbs_down: bool) -> f32 {
if has_thumbs_up {
0.95
} else if has_thumbs_down {
0.1
} else {
0.5
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_weights() {
let w = PathQualityWeights::default();
let sum = w.alpha + w.beta + w.gamma + w.delta;
assert!((sum - 1.0).abs() < 1e-6);
}
#[test]
fn test_terminal_focused_weights() {
let w = PathQualityWeights::terminal_focused();
assert!(w.beta > w.alpha);
assert!(w.beta > w.gamma);
assert!(w.beta > w.delta);
}
#[test]
fn test_factors_new() {
let f = PathQualityFactors::new(0.8, 0.7, 0.6, 0.5);
assert!((f.linearity - 0.8).abs() < 1e-6);
assert!((f.terminal_score - 0.7).abs() < 1e-6);
assert!((f.coherence - 0.6).abs() < 1e-6);
assert!((f.completion - 0.5).abs() < 1e-6);
}
#[test]
fn test_factors_clamped() {
let f = PathQualityFactors::new(1.5, -0.5, 0.5, 0.5);
assert!((f.linearity - 1.0).abs() < 1e-6);
assert!((f.terminal_score - 0.0).abs() < 1e-6);
}
#[test]
fn test_compute_linearity_perfect() {
let depths = vec![0, 1, 2, 3, 4];
let linearity = PathQualityFactors::compute_linearity(&depths);
assert!((linearity - 1.0).abs() < 0.01);
}
#[test]
fn test_compute_linearity_erratic() {
let depths = vec![0, 5, 1, 4, 2];
let linearity = PathQualityFactors::compute_linearity(&depths);
assert!(linearity < 0.5);
}
#[test]
fn test_compute_coherence() {
let homogeneities = vec![0.9, 0.8, 0.7, 0.6];
let coherence = PathQualityFactors::compute_coherence(&homogeneities);
assert!((coherence - 0.75).abs() < 1e-6);
}
#[test]
fn test_compute_completion() {
let completion = PathQualityFactors::compute_completion(5, 4);
assert!((completion - 1.0).abs() < 1e-6);
let completion = PathQualityFactors::compute_completion(3, 4);
assert!((completion - 0.6).abs() < 1e-6);
}
#[test]
fn test_from_path_data() {
let depths = vec![0, 1, 2, 3];
let homogeneities = vec![1.0, 0.9, 0.8, 0.7];
let max_depth = 5;
let factors = PathQualityFactors::from_path_data(
&depths,
&homogeneities,
max_depth,
true,
0.9,
);
assert!(factors.linearity > 0.9);
assert!((factors.terminal_score - 0.9).abs() < 1e-6);
assert!((factors.coherence - 0.85).abs() < 1e-6);
assert!(factors.completion > 0.6);
assert!(factors.completion < 0.7);
}
#[test]
fn test_compute_quality() {
let factors = PathQualityFactors::new(0.8, 0.9, 0.7, 0.6);
let weights = PathQualityWeights::default();
let quality = factors.compute_quality(&weights);
assert!((quality - 0.765).abs() < 1e-5);
}
#[test]
fn test_is_high_quality() {
let high = PathQualityFactors::new(0.9, 0.9, 0.9, 0.9);
let low = PathQualityFactors::new(0.2, 0.2, 0.2, 0.2);
assert!(high.is_high_quality());
assert!(!low.is_high_quality());
assert!(low.is_low_quality());
assert!(!high.is_low_quality());
}
#[test]
fn test_enhance_salience() {
let base = 0.5;
let quality = 0.8;
assert!((PathQuality::enhance_salience(base, quality, 0.0) - 0.5).abs() < 1e-6);
assert!((PathQuality::enhance_salience(base, quality, 1.0) - 0.8).abs() < 1e-6);
assert!((PathQuality::enhance_salience(base, quality, 0.5) - 0.65).abs() < 1e-6);
}
#[test]
fn test_terminal_boost() {
let base = 0.6;
let quality = 0.8;
let non_terminal = PathQuality::terminal_boost(base, quality, false);
assert!((non_terminal - 0.6).abs() < 1e-6);
let terminal = PathQuality::terminal_boost(base, quality, true);
assert!((terminal - 0.76).abs() < 1e-6);
}
#[test]
fn test_terminal_quality_from_phase() {
assert!(PathQuality::terminal_quality_from_phase(Some("synthesis")) > 0.8);
assert!(PathQuality::terminal_quality_from_phase(Some("debugging")) < 0.5);
assert!((PathQuality::terminal_quality_from_phase(None) - 0.5).abs() < 1e-6);
}
#[test]
fn test_terminal_quality_from_feedback() {
assert!(PathQuality::terminal_quality_from_feedback(true, false) > 0.9);
assert!(PathQuality::terminal_quality_from_feedback(false, true) < 0.2);
assert!((PathQuality::terminal_quality_from_feedback(false, false) - 0.5).abs() < 1e-6);
}
}