#![allow(clippy::cast_precision_loss)]
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct ReinforcementContext {
pub usage_count: u64,
pub last_used: u64,
pub created_at: u64,
pub current_time: u64,
pub recent_success_rate: Option<f32>,
pub custom: HashMap<String, f64>,
}
impl ReinforcementContext {
#[must_use]
pub fn new() -> Self {
Self {
current_time: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_secs()),
..Default::default()
}
}
#[must_use]
pub fn with_usage_count(mut self, count: u64) -> Self {
self.usage_count = count;
self
}
#[must_use]
pub fn with_last_used(mut self, timestamp: u64) -> Self {
self.last_used = timestamp;
self
}
#[must_use]
pub fn with_created_at(mut self, timestamp: u64) -> Self {
self.created_at = timestamp;
self
}
#[must_use]
pub fn with_success_rate(mut self, rate: f32) -> Self {
self.recent_success_rate = Some(rate);
self
}
#[must_use]
pub fn with_custom(mut self, key: &str, value: f64) -> Self {
self.custom.insert(key.to_string(), value);
self
}
#[must_use]
pub fn age_seconds(&self) -> u64 {
self.current_time.saturating_sub(self.created_at)
}
#[must_use]
pub fn time_since_last_use(&self) -> u64 {
self.current_time.saturating_sub(self.last_used)
}
}
pub trait ReinforcementStrategy: Send + Sync {
fn update_confidence(
&self,
old_confidence: f32,
success: bool,
context: &ReinforcementContext,
) -> f32;
fn name(&self) -> &'static str;
}
#[derive(Debug, Clone)]
pub struct FixedRate {
pub success_delta: f32,
pub failure_delta: f32,
}
impl Default for FixedRate {
fn default() -> Self {
Self {
success_delta: 0.1,
failure_delta: 0.05,
}
}
}
impl FixedRate {
#[must_use]
pub fn new(success_delta: f32, failure_delta: f32) -> Self {
Self {
success_delta,
failure_delta,
}
}
}
impl ReinforcementStrategy for FixedRate {
fn update_confidence(
&self,
old_confidence: f32,
success: bool,
_context: &ReinforcementContext,
) -> f32 {
let new_confidence = if success {
old_confidence + self.success_delta
} else {
old_confidence - self.failure_delta
};
new_confidence.clamp(0.0, 1.0)
}
fn name(&self) -> &'static str {
"FixedRate"
}
}
#[derive(Debug, Clone)]
pub struct AdaptiveLearningRate {
pub base_success_rate: f32,
pub base_failure_rate: f32,
pub half_life_usage: u64,
pub min_rate_multiplier: f32,
}
impl Default for AdaptiveLearningRate {
fn default() -> Self {
Self {
base_success_rate: 0.2,
base_failure_rate: 0.1,
half_life_usage: 10,
min_rate_multiplier: 0.1,
}
}
}
impl AdaptiveLearningRate {
fn rate_multiplier(&self, usage_count: u64) -> f32 {
let half_life = self.half_life_usage.max(1);
let decay = 0.5_f32.powf(usage_count as f32 / half_life as f32);
decay.max(self.min_rate_multiplier)
}
}
impl ReinforcementStrategy for AdaptiveLearningRate {
fn update_confidence(
&self,
old_confidence: f32,
success: bool,
context: &ReinforcementContext,
) -> f32 {
let multiplier = self.rate_multiplier(context.usage_count);
let delta = if success {
self.base_success_rate * multiplier
} else {
-self.base_failure_rate * multiplier
};
(old_confidence + delta).clamp(0.0, 1.0)
}
fn name(&self) -> &'static str {
"AdaptiveLearningRate"
}
}
#[derive(Debug, Clone)]
pub struct TemporalDecay {
pub base: FixedRate,
pub decay_half_life: u64,
pub max_decay_per_update: f32,
}
impl Default for TemporalDecay {
fn default() -> Self {
Self {
base: FixedRate::default(),
decay_half_life: 30 * 24 * 60 * 60, max_decay_per_update: 0.1,
}
}
}
impl TemporalDecay {
#[must_use]
pub fn new(decay_half_life_days: u64) -> Self {
Self {
decay_half_life: decay_half_life_days * 24 * 60 * 60,
..Default::default()
}
}
fn decay_factor(&self, time_since_last_use: u64) -> f32 {
let half_life = self.decay_half_life.max(1);
let decay = 0.5_f32.powf(time_since_last_use as f32 / half_life as f32);
(1.0 - decay).min(self.max_decay_per_update)
}
}
impl ReinforcementStrategy for TemporalDecay {
fn update_confidence(
&self,
old_confidence: f32,
success: bool,
context: &ReinforcementContext,
) -> f32 {
let time_decay = self.decay_factor(context.time_since_last_use());
let decayed_confidence = old_confidence * (1.0 - time_decay);
let new_confidence = if success {
decayed_confidence + self.base.success_delta
} else {
decayed_confidence - self.base.failure_delta
};
new_confidence.clamp(0.0, 1.0)
}
fn name(&self) -> &'static str {
"TemporalDecay"
}
}
#[derive(Debug, Clone)]
pub struct ContextualReinforcement {
pub success_rate_weight: f32,
pub usage_weight: f32,
pub recency_weight: f32,
pub base_rate: f32,
}
impl Default for ContextualReinforcement {
fn default() -> Self {
Self {
success_rate_weight: 0.3,
usage_weight: 0.3,
recency_weight: 0.4,
base_rate: 0.15,
}
}
}
impl ContextualReinforcement {
fn recency_factor(time_since_last_use: u64) -> f32 {
let hours = time_since_last_use as f32 / 3600.0;
(-hours / 168.0).exp() }
fn usage_factor(usage_count: u64) -> f32 {
let normalized = (usage_count as f32).ln_1p() / 10.0;
normalized.min(1.0)
}
}
impl ReinforcementStrategy for ContextualReinforcement {
fn update_confidence(
&self,
old_confidence: f32,
success: bool,
context: &ReinforcementContext,
) -> f32 {
let recency = Self::recency_factor(context.time_since_last_use());
let usage = Self::usage_factor(context.usage_count);
let success_rate = context.recent_success_rate.unwrap_or(0.5);
let context_score = self.recency_weight * recency
+ self.usage_weight * usage
+ self.success_rate_weight * success_rate;
let effective_rate = self.base_rate * (0.5 + context_score);
let delta = if success {
effective_rate
} else {
-effective_rate * 0.5
};
(old_confidence + delta).clamp(0.0, 1.0)
}
fn name(&self) -> &'static str {
"ContextualReinforcement"
}
}
pub struct CompositeStrategy {
strategies: Vec<(Box<dyn ReinforcementStrategy>, f32)>,
}
impl CompositeStrategy {
#[must_use]
pub fn new() -> Self {
Self {
strategies: Vec::new(),
}
}
#[must_use]
pub fn add_strategy<S: ReinforcementStrategy + 'static>(
mut self,
strategy: S,
weight: f32,
) -> Self {
self.strategies.push((Box::new(strategy), weight));
self
}
}
impl Default for CompositeStrategy {
fn default() -> Self {
Self::new()
}
}
impl ReinforcementStrategy for CompositeStrategy {
fn update_confidence(
&self,
old_confidence: f32,
success: bool,
context: &ReinforcementContext,
) -> f32 {
if self.strategies.is_empty() {
return old_confidence;
}
let total_weight: f32 = self.strategies.iter().map(|(_, w)| w).sum();
if total_weight == 0.0 {
return old_confidence;
}
let weighted_sum: f32 = self
.strategies
.iter()
.map(|(strategy, weight)| {
strategy.update_confidence(old_confidence, success, context) * weight
})
.sum();
(weighted_sum / total_weight).clamp(0.0, 1.0)
}
fn name(&self) -> &'static str {
"CompositeStrategy"
}
}
#[derive(Debug, Clone)]
pub struct DiminishingReturns {
pub base_success_delta: f32,
pub base_failure_delta: f32,
pub k: f32,
}
impl Default for DiminishingReturns {
fn default() -> Self {
Self {
base_success_delta: 0.1,
base_failure_delta: 0.05,
k: 0.1,
}
}
}
impl DiminishingReturns {
#[must_use]
pub fn new(base_success_delta: f32, base_failure_delta: f32, k: f32) -> Self {
Self {
base_success_delta,
base_failure_delta,
k,
}
}
fn effective_delta(base: f32, count: f32, k: f32) -> f32 {
base / (1.0 + k * count)
}
}
impl ReinforcementStrategy for DiminishingReturns {
#[allow(clippy::cast_possible_truncation)]
fn update_confidence(
&self,
old_confidence: f32,
success: bool,
context: &ReinforcementContext,
) -> f32 {
let (s_count, f_count) = if let (Some(&sc), Some(&fc)) = (
context.custom.get("success_count"),
context.custom.get("failure_count"),
) {
(sc as f32, fc as f32)
} else {
let rate = context.recent_success_rate.unwrap_or(0.5);
let uses = context.usage_count as f32;
(uses * rate, uses * (1.0 - rate))
};
let delta = if success {
Self::effective_delta(self.base_success_delta, s_count, self.k)
} else {
-Self::effective_delta(self.base_failure_delta, f_count, self.k)
};
(old_confidence + delta).clamp(0.0, 1.0)
}
fn name(&self) -> &'static str {
"DiminishingReturns"
}
}
#[must_use]
pub fn power_law_decay(confidence: f32, time_since_last_use_secs: u64, decay_exponent: f32) -> f32 {
let days = time_since_last_use_secs as f32 / 86_400.0;
let multiplier = days.max(1.0).powf(-decay_exponent);
(confidence * multiplier).clamp(0.0, 1.0)
}
#[must_use]
pub fn default_strategy() -> Box<dyn ReinforcementStrategy> {
Box::new(FixedRate::default())
}