1#![allow(clippy::cast_precision_loss)]
14
15use std::collections::HashMap;
16
17#[derive(Debug, Clone, Default)]
19pub struct ReinforcementContext {
20 pub usage_count: u64,
22 pub last_used: u64,
24 pub created_at: u64,
26 pub current_time: u64,
28 pub recent_success_rate: Option<f32>,
30 pub custom: HashMap<String, f64>,
32}
33
34impl ReinforcementContext {
35 #[must_use]
37 pub fn new() -> Self {
38 Self {
39 current_time: std::time::SystemTime::now()
40 .duration_since(std::time::UNIX_EPOCH)
41 .map_or(0, |d| d.as_secs()),
42 ..Default::default()
43 }
44 }
45
46 #[must_use]
48 pub fn with_usage_count(mut self, count: u64) -> Self {
49 self.usage_count = count;
50 self
51 }
52
53 #[must_use]
55 pub fn with_last_used(mut self, timestamp: u64) -> Self {
56 self.last_used = timestamp;
57 self
58 }
59
60 #[must_use]
62 pub fn with_created_at(mut self, timestamp: u64) -> Self {
63 self.created_at = timestamp;
64 self
65 }
66
67 #[must_use]
69 pub fn with_success_rate(mut self, rate: f32) -> Self {
70 self.recent_success_rate = Some(rate);
71 self
72 }
73
74 #[must_use]
76 pub fn with_custom(mut self, key: &str, value: f64) -> Self {
77 self.custom.insert(key.to_string(), value);
78 self
79 }
80
81 #[must_use]
83 pub fn age_seconds(&self) -> u64 {
84 self.current_time.saturating_sub(self.created_at)
85 }
86
87 #[must_use]
89 pub fn time_since_last_use(&self) -> u64 {
90 self.current_time.saturating_sub(self.last_used)
91 }
92}
93
94pub trait ReinforcementStrategy: Send + Sync {
98 fn update_confidence(
110 &self,
111 old_confidence: f32,
112 success: bool,
113 context: &ReinforcementContext,
114 ) -> f32;
115
116 fn name(&self) -> &'static str;
118}
119
120#[derive(Debug, Clone)]
125pub struct FixedRate {
126 pub success_delta: f32,
128 pub failure_delta: f32,
130}
131
132impl Default for FixedRate {
133 fn default() -> Self {
134 Self {
135 success_delta: 0.1,
136 failure_delta: 0.05,
137 }
138 }
139}
140
141impl FixedRate {
142 #[must_use]
144 pub fn new(success_delta: f32, failure_delta: f32) -> Self {
145 Self {
146 success_delta,
147 failure_delta,
148 }
149 }
150}
151
152impl ReinforcementStrategy for FixedRate {
153 fn update_confidence(
154 &self,
155 old_confidence: f32,
156 success: bool,
157 _context: &ReinforcementContext,
158 ) -> f32 {
159 let new_confidence = if success {
160 old_confidence + self.success_delta
161 } else {
162 old_confidence - self.failure_delta
163 };
164 new_confidence.clamp(0.0, 1.0)
165 }
166
167 fn name(&self) -> &'static str {
168 "FixedRate"
169 }
170}
171
172#[derive(Debug, Clone)]
178pub struct AdaptiveLearningRate {
179 pub base_success_rate: f32,
181 pub base_failure_rate: f32,
183 pub half_life_usage: u64,
185 pub min_rate_multiplier: f32,
187}
188
189impl Default for AdaptiveLearningRate {
190 fn default() -> Self {
191 Self {
192 base_success_rate: 0.2,
193 base_failure_rate: 0.1,
194 half_life_usage: 10,
195 min_rate_multiplier: 0.1,
196 }
197 }
198}
199
200impl AdaptiveLearningRate {
201 fn rate_multiplier(&self, usage_count: u64) -> f32 {
203 let half_life = self.half_life_usage.max(1);
204 let decay = 0.5_f32.powf(usage_count as f32 / half_life as f32);
205 decay.max(self.min_rate_multiplier)
206 }
207}
208
209impl ReinforcementStrategy for AdaptiveLearningRate {
210 fn update_confidence(
211 &self,
212 old_confidence: f32,
213 success: bool,
214 context: &ReinforcementContext,
215 ) -> f32 {
216 let multiplier = self.rate_multiplier(context.usage_count);
217
218 let delta = if success {
219 self.base_success_rate * multiplier
220 } else {
221 -self.base_failure_rate * multiplier
222 };
223
224 (old_confidence + delta).clamp(0.0, 1.0)
225 }
226
227 fn name(&self) -> &'static str {
228 "AdaptiveLearningRate"
229 }
230}
231
232#[derive(Debug, Clone)]
237pub struct TemporalDecay {
238 pub base: FixedRate,
240 pub decay_half_life: u64,
242 pub max_decay_per_update: f32,
244}
245
246impl Default for TemporalDecay {
247 fn default() -> Self {
248 Self {
249 base: FixedRate::default(),
250 decay_half_life: 30 * 24 * 60 * 60, max_decay_per_update: 0.1,
252 }
253 }
254}
255
256impl TemporalDecay {
257 #[must_use]
259 pub fn new(decay_half_life_days: u64) -> Self {
260 Self {
261 decay_half_life: decay_half_life_days * 24 * 60 * 60,
262 ..Default::default()
263 }
264 }
265
266 fn decay_factor(&self, time_since_last_use: u64) -> f32 {
268 let half_life = self.decay_half_life.max(1);
269 let decay = 0.5_f32.powf(time_since_last_use as f32 / half_life as f32);
270 (1.0 - decay).min(self.max_decay_per_update)
271 }
272}
273
274impl ReinforcementStrategy for TemporalDecay {
275 fn update_confidence(
276 &self,
277 old_confidence: f32,
278 success: bool,
279 context: &ReinforcementContext,
280 ) -> f32 {
281 let time_decay = self.decay_factor(context.time_since_last_use());
282 let decayed_confidence = old_confidence * (1.0 - time_decay);
283
284 let new_confidence = if success {
285 decayed_confidence + self.base.success_delta
286 } else {
287 decayed_confidence - self.base.failure_delta
288 };
289
290 new_confidence.clamp(0.0, 1.0)
291 }
292
293 fn name(&self) -> &'static str {
294 "TemporalDecay"
295 }
296}
297
298#[derive(Debug, Clone)]
305pub struct ContextualReinforcement {
306 pub success_rate_weight: f32,
308 pub usage_weight: f32,
310 pub recency_weight: f32,
312 pub base_rate: f32,
314}
315
316impl Default for ContextualReinforcement {
317 fn default() -> Self {
318 Self {
319 success_rate_weight: 0.3,
320 usage_weight: 0.3,
321 recency_weight: 0.4,
322 base_rate: 0.15,
323 }
324 }
325}
326
327impl ContextualReinforcement {
328 fn recency_factor(time_since_last_use: u64) -> f32 {
330 let hours = time_since_last_use as f32 / 3600.0;
331 (-hours / 168.0).exp() }
333
334 fn usage_factor(usage_count: u64) -> f32 {
336 let normalized = (usage_count as f32).ln_1p() / 10.0;
337 normalized.min(1.0)
338 }
339}
340
341impl ReinforcementStrategy for ContextualReinforcement {
342 fn update_confidence(
343 &self,
344 old_confidence: f32,
345 success: bool,
346 context: &ReinforcementContext,
347 ) -> f32 {
348 let recency = Self::recency_factor(context.time_since_last_use());
349 let usage = Self::usage_factor(context.usage_count);
350 let success_rate = context.recent_success_rate.unwrap_or(0.5);
351
352 let context_score = self.recency_weight * recency
353 + self.usage_weight * usage
354 + self.success_rate_weight * success_rate;
355
356 let effective_rate = self.base_rate * (0.5 + context_score);
357
358 let delta = if success {
359 effective_rate
360 } else {
361 -effective_rate * 0.5
362 };
363
364 (old_confidence + delta).clamp(0.0, 1.0)
365 }
366
367 fn name(&self) -> &'static str {
368 "ContextualReinforcement"
369 }
370}
371
372pub struct CompositeStrategy {
376 strategies: Vec<(Box<dyn ReinforcementStrategy>, f32)>,
377}
378
379impl CompositeStrategy {
380 #[must_use]
382 pub fn new() -> Self {
383 Self {
384 strategies: Vec::new(),
385 }
386 }
387
388 #[must_use]
392 pub fn add_strategy<S: ReinforcementStrategy + 'static>(
393 mut self,
394 strategy: S,
395 weight: f32,
396 ) -> Self {
397 self.strategies.push((Box::new(strategy), weight));
398 self
399 }
400}
401
402impl Default for CompositeStrategy {
403 fn default() -> Self {
404 Self::new()
405 }
406}
407
408impl ReinforcementStrategy for CompositeStrategy {
409 fn update_confidence(
410 &self,
411 old_confidence: f32,
412 success: bool,
413 context: &ReinforcementContext,
414 ) -> f32 {
415 if self.strategies.is_empty() {
416 return old_confidence;
417 }
418
419 let total_weight: f32 = self.strategies.iter().map(|(_, w)| w).sum();
420 if total_weight == 0.0 {
421 return old_confidence;
422 }
423
424 let weighted_sum: f32 = self
425 .strategies
426 .iter()
427 .map(|(strategy, weight)| {
428 strategy.update_confidence(old_confidence, success, context) * weight
429 })
430 .sum();
431
432 (weighted_sum / total_weight).clamp(0.0, 1.0)
433 }
434
435 fn name(&self) -> &'static str {
436 "CompositeStrategy"
437 }
438}
439
440#[derive(Debug, Clone)]
451pub struct DiminishingReturns {
452 pub base_success_delta: f32,
454 pub base_failure_delta: f32,
456 pub k: f32,
459}
460
461impl Default for DiminishingReturns {
462 fn default() -> Self {
463 Self {
464 base_success_delta: 0.1,
465 base_failure_delta: 0.05,
466 k: 0.1,
467 }
468 }
469}
470
471impl DiminishingReturns {
472 #[must_use]
474 pub fn new(base_success_delta: f32, base_failure_delta: f32, k: f32) -> Self {
475 Self {
476 base_success_delta,
477 base_failure_delta,
478 k,
479 }
480 }
481
482 fn effective_delta(base: f32, count: f32, k: f32) -> f32 {
483 base / (1.0 + k * count)
484 }
485}
486
487impl ReinforcementStrategy for DiminishingReturns {
488 #[allow(clippy::cast_possible_truncation)]
489 fn update_confidence(
492 &self,
493 old_confidence: f32,
494 success: bool,
495 context: &ReinforcementContext,
496 ) -> f32 {
497 let (s_count, f_count) = if let (Some(&sc), Some(&fc)) = (
500 context.custom.get("success_count"),
501 context.custom.get("failure_count"),
502 ) {
503 (sc as f32, fc as f32)
504 } else {
505 let rate = context.recent_success_rate.unwrap_or(0.5);
506 let uses = context.usage_count as f32;
507 (uses * rate, uses * (1.0 - rate))
508 };
509
510 let delta = if success {
511 Self::effective_delta(self.base_success_delta, s_count, self.k)
512 } else {
513 -Self::effective_delta(self.base_failure_delta, f_count, self.k)
514 };
515
516 (old_confidence + delta).clamp(0.0, 1.0)
517 }
518
519 fn name(&self) -> &'static str {
520 "DiminishingReturns"
521 }
522}
523
524#[must_use]
537pub fn power_law_decay(confidence: f32, time_since_last_use_secs: u64, decay_exponent: f32) -> f32 {
538 let days = time_since_last_use_secs as f32 / 86_400.0;
539 let multiplier = days.max(1.0).powf(-decay_exponent);
540 (confidence * multiplier).clamp(0.0, 1.0)
541}
542
543#[must_use]
547pub fn default_strategy() -> Box<dyn ReinforcementStrategy> {
548 Box::new(FixedRate::default())
549}