1#[allow(unused_imports)]
13use crate::prelude::*;
14
15#[derive(Debug, Clone)]
17pub struct Parameter {
18 pub name: String,
20 pub value: f64,
22 pub min: f64,
24 pub max: f64,
26 pub step: f64,
28}
29
30impl Parameter {
31 #[must_use]
33 pub fn new(name: String, value: f64, min: f64, max: f64, step: f64) -> Self {
34 Self {
35 name,
36 value: value.clamp(min, max),
37 min,
38 max,
39 step,
40 }
41 }
42
43 pub fn increase(&mut self) {
45 self.value = (self.value + self.step).min(self.max);
46 }
47
48 pub fn decrease(&mut self) {
50 self.value = (self.value - self.step).max(self.min);
51 }
52
53 pub fn set(&mut self, value: f64) {
55 self.value = value.clamp(self.min, self.max);
56 }
57
58 #[must_use]
60 pub fn get(&self) -> f64 {
61 self.value
62 }
63}
64
65#[derive(Debug, Clone, Default)]
67pub struct PerformanceMetrics {
68 pub conflicts: u64,
70 pub decisions: u64,
72 pub propagations: u64,
74 pub time_ms: u64,
76 pub solved: bool,
78 pub satisfiable: Option<bool>,
80}
81
82impl PerformanceMetrics {
83 #[must_use]
87 pub fn score(&self) -> f64 {
88 if !self.solved {
89 return f64::MAX;
90 }
91
92 let mut score = self.time_ms as f64;
94
95 let conflict_rate = self.conflicts as f64 / self.time_ms.max(1) as f64;
97 if conflict_rate > 100.0 {
98 score *= 1.0 + (conflict_rate - 100.0) / 100.0;
99 }
100
101 let prop_rate = self.propagations as f64 / self.time_ms.max(1) as f64;
103 if prop_rate > 1000.0 {
104 score *= 0.95;
105 }
106
107 score
108 }
109}
110
111#[derive(Debug, Clone)]
113pub struct Configuration {
114 pub parameters: HashMap<String, f64>,
116 pub metrics: Option<PerformanceMetrics>,
118}
119
120impl Configuration {
121 #[must_use]
123 pub fn new(parameters: HashMap<String, f64>) -> Self {
124 Self {
125 parameters,
126 metrics: None,
127 }
128 }
129
130 #[must_use]
132 pub fn score(&self) -> f64 {
133 self.metrics.as_ref().map_or(f64::MAX, |m| m.score())
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub enum TuningStrategy {
140 GridSearch,
142 RandomSearch,
144 HillClimbing,
146 Adaptive,
148}
149
150#[derive(Debug, Default, Clone)]
152pub struct AutotuningStats {
153 pub configurations_tried: u64,
155 pub best_score: f64,
157 pub improvements: u64,
159}
160
161pub struct Autotuner {
163 parameters: HashMap<String, Parameter>,
165 strategy: TuningStrategy,
167 history: Vec<Configuration>,
169 best_config: Option<Configuration>,
171 stats: AutotuningStats,
173}
174
175impl Autotuner {
176 #[must_use]
178 pub fn new(strategy: TuningStrategy) -> Self {
179 Self {
180 parameters: HashMap::new(),
181 strategy,
182 history: Vec::new(),
183 best_config: None,
184 stats: AutotuningStats::default(),
185 }
186 }
187
188 pub fn add_parameter(&mut self, param: Parameter) {
190 self.parameters.insert(param.name.clone(), param);
191 }
192
193 #[must_use]
195 pub fn get_parameters(&self) -> HashMap<String, f64> {
196 self.parameters
197 .iter()
198 .map(|(name, param)| (name.clone(), param.value))
199 .collect()
200 }
201
202 #[must_use]
204 pub fn stats(&self) -> &AutotuningStats {
205 &self.stats
206 }
207
208 pub fn reset_stats(&mut self) {
210 self.stats = AutotuningStats::default();
211 self.stats.best_score = f64::MAX;
212 }
213
214 pub fn record_performance(&mut self, metrics: PerformanceMetrics) {
216 let config = Configuration {
217 parameters: self.get_parameters(),
218 metrics: Some(metrics),
219 };
220
221 let score = config.score();
222 self.stats.configurations_tried += 1;
223
224 if score < self.stats.best_score || self.best_config.is_none() {
226 self.stats.best_score = score;
227 self.stats.improvements += 1;
228 self.best_config = Some(config.clone());
229 }
230
231 self.history.push(config);
232
233 self.adjust_parameters();
235 }
236
237 #[must_use]
239 pub fn best_configuration(&self) -> Option<&Configuration> {
240 self.best_config.as_ref()
241 }
242
243 pub fn apply_best(&mut self) {
245 if let Some(best) = &self.best_config {
246 for (name, &value) in &best.parameters {
247 if let Some(param) = self.parameters.get_mut(name) {
248 param.set(value);
249 }
250 }
251 }
252 }
253
254 #[must_use]
256 pub fn next_configuration(&mut self) -> HashMap<String, f64> {
257 match self.strategy {
258 TuningStrategy::GridSearch => self.grid_search_next(),
259 TuningStrategy::RandomSearch => self.random_search_next(),
260 _ => self.get_parameters(),
261 }
262 }
263
264 fn adjust_parameters(&mut self) {
266 match self.strategy {
267 TuningStrategy::HillClimbing => self.hill_climbing_adjust(),
268 TuningStrategy::Adaptive => self.adaptive_adjust(),
269 _ => {}
270 }
271 }
272
273 fn hill_climbing_adjust(&mut self) {
275 if self.history.len() < 2 {
276 return;
277 }
278
279 let current = &self.history[self.history.len() - 1];
280 let previous = &self.history[self.history.len() - 2];
281
282 let current_score = current.score();
283 let previous_score = previous.score();
284
285 for (name, param) in &mut self.parameters {
288 let current_val = current.parameters.get(name).copied().unwrap_or(param.value);
289 let previous_val = previous
290 .parameters
291 .get(name)
292 .copied()
293 .unwrap_or(param.value);
294
295 if current_score < previous_score {
296 if current_val > previous_val {
298 param.increase();
299 } else if current_val < previous_val {
300 param.decrease();
301 }
302 } else {
303 if current_val > previous_val {
305 param.decrease();
306 } else if current_val < previous_val {
307 param.increase();
308 }
309 }
310 }
311 }
312
313 fn adaptive_adjust(&mut self) {
315 if self.history.is_empty() {
316 return;
317 }
318
319 let recent_window = 5.min(self.history.len());
320 let recent_configs: Vec<_> = self.history.iter().rev().take(recent_window).collect();
321
322 let mut total_conflicts = 0;
324 let mut total_decisions = 0;
325 let mut count = 0;
326
327 for config in &recent_configs {
328 if let Some(ref metrics) = config.metrics {
329 total_conflicts += metrics.conflicts;
330 total_decisions += metrics.decisions;
331 count += 1;
332 }
333 }
334
335 if count == 0 {
336 return;
337 }
338
339 let avg_conflicts = total_conflicts / count;
340 let avg_decisions = total_decisions / count;
341
342 if let Some(restart_param) = self.parameters.get_mut("restart_factor") {
348 if avg_conflicts > 10000 {
349 restart_param.increase();
350 } else if avg_conflicts < 1000 {
351 restart_param.decrease();
352 }
353 }
354
355 if let Some(decay_param) = self.parameters.get_mut("variable_decay") {
357 if avg_decisions > 5000 {
358 decay_param.increase();
359 } else if avg_decisions < 500 {
360 decay_param.decrease();
361 }
362 }
363 }
364
365 fn grid_search_next(&self) -> HashMap<String, f64> {
367 self.get_parameters()
370 }
371
372 fn random_search_next(&mut self) -> HashMap<String, f64> {
374 use core::hash::{BuildHasher, Hash, Hasher};
375
376 let mut result = HashMap::new();
377 let state = core::hash::BuildHasherDefault::<rustc_hash::FxHasher>::default();
378
379 for (name, param) in &self.parameters {
380 let mut hasher = state.build_hasher();
382 name.hash(&mut hasher);
383 self.history.len().hash(&mut hasher);
384 let hash = hasher.finish();
385
386 let range = param.max - param.min;
387 let random_factor = (hash % 1000) as f64 / 1000.0;
388 let value = param.min + range * random_factor;
389
390 result.insert(name.clone(), value);
391 }
392
393 result
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn test_parameter_creation() {
403 let param = Parameter::new("test".to_string(), 0.5, 0.0, 1.0, 0.1);
404 assert_eq!(param.name, "test");
405 assert_eq!(param.value, 0.5);
406 }
407
408 #[test]
409 fn test_parameter_increase_decrease() {
410 let mut param = Parameter::new("test".to_string(), 0.5, 0.0, 1.0, 0.1);
411
412 param.increase();
413 assert_eq!(param.value, 0.6);
414
415 param.decrease();
416 assert_eq!(param.value, 0.5);
417 }
418
419 #[test]
420 fn test_parameter_bounds() {
421 let mut param = Parameter::new("test".to_string(), 0.9, 0.0, 1.0, 0.2);
422
423 param.increase();
424 assert_eq!(param.value, 1.0); param.decrease();
427 param.decrease();
428 param.decrease();
429 param.decrease();
430 param.decrease();
431 param.decrease();
432 assert_eq!(param.value, 0.0); }
434
435 #[test]
436 fn test_autotuner_creation() {
437 let tuner = Autotuner::new(TuningStrategy::HillClimbing);
438 assert_eq!(tuner.stats().configurations_tried, 0);
439 }
440
441 #[test]
442 fn test_add_parameter() {
443 let mut tuner = Autotuner::new(TuningStrategy::HillClimbing);
444 let param = Parameter::new("test".to_string(), 0.5, 0.0, 1.0, 0.1);
445
446 tuner.add_parameter(param);
447 let params = tuner.get_parameters();
448
449 assert_eq!(params.get("test"), Some(&0.5));
450 }
451
452 #[test]
453 fn test_record_performance() {
454 let mut tuner = Autotuner::new(TuningStrategy::HillClimbing);
455 let param = Parameter::new("test".to_string(), 0.5, 0.0, 1.0, 0.1);
456 tuner.add_parameter(param);
457
458 let metrics = PerformanceMetrics {
459 conflicts: 100,
460 decisions: 200,
461 propagations: 1000,
462 time_ms: 50,
463 solved: true,
464 satisfiable: Some(true),
465 };
466
467 tuner.record_performance(metrics);
468 assert_eq!(tuner.stats().configurations_tried, 1);
469 }
470
471 #[test]
472 fn test_best_configuration() {
473 let mut tuner = Autotuner::new(TuningStrategy::HillClimbing);
474 let param = Parameter::new("test".to_string(), 0.5, 0.0, 1.0, 0.1);
475 tuner.add_parameter(param);
476
477 let metrics1 = PerformanceMetrics {
478 conflicts: 100,
479 decisions: 200,
480 propagations: 1000,
481 time_ms: 100,
482 solved: true,
483 satisfiable: Some(true),
484 };
485
486 tuner.record_performance(metrics1);
487
488 let metrics2 = PerformanceMetrics {
489 conflicts: 50,
490 decisions: 100,
491 propagations: 500,
492 time_ms: 50,
493 solved: true,
494 satisfiable: Some(true),
495 };
496
497 tuner.record_performance(metrics2);
498
499 assert_eq!(tuner.stats().improvements, 2);
500 assert!(tuner.best_configuration().is_some());
501 }
502
503 #[test]
504 fn test_performance_score() {
505 let metrics = PerformanceMetrics {
506 conflicts: 100,
507 decisions: 200,
508 propagations: 1000,
509 time_ms: 50,
510 solved: true,
511 satisfiable: Some(true),
512 };
513
514 let score = metrics.score();
515 assert!(score > 0.0);
516 assert!(score < f64::MAX);
517 }
518
519 #[test]
520 fn test_unsolved_score() {
521 let metrics = PerformanceMetrics {
522 conflicts: 100,
523 decisions: 200,
524 propagations: 1000,
525 time_ms: 50,
526 solved: false,
527 satisfiable: None,
528 };
529
530 let score = metrics.score();
531 assert_eq!(score, f64::MAX);
532 }
533
534 #[test]
535 fn test_apply_best() {
536 let mut tuner = Autotuner::new(TuningStrategy::HillClimbing);
537 let param = Parameter::new("test".to_string(), 0.5, 0.0, 1.0, 0.1);
538 tuner.add_parameter(param);
539
540 let metrics = PerformanceMetrics {
541 conflicts: 100,
542 decisions: 200,
543 propagations: 1000,
544 time_ms: 50,
545 solved: true,
546 satisfiable: Some(true),
547 };
548
549 tuner.record_performance(metrics);
550
551 if let Some(param) = tuner.parameters.get_mut("test") {
553 param.set(0.8);
554 }
555
556 tuner.apply_best();
558 let params = tuner.get_parameters();
559 assert_eq!(params.get("test"), Some(&0.5));
560 }
561
562 #[test]
563 fn test_different_strategies() {
564 let strategies = vec![
565 TuningStrategy::GridSearch,
566 TuningStrategy::RandomSearch,
567 TuningStrategy::HillClimbing,
568 TuningStrategy::Adaptive,
569 ];
570
571 for strategy in strategies {
572 let tuner = Autotuner::new(strategy);
573 assert_eq!(tuner.strategy, strategy);
574 }
575 }
576}