converge_optimization/gate/
budgets.rs1use serde::{Deserialize, Serialize};
4use std::time::Duration;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct SolveBudgets {
9 #[serde(with = "duration_serde")]
11 pub time_limit: Duration,
12 pub iteration_limit: usize,
14 pub candidate_cap: usize,
16 pub memory_limit_bytes: usize,
18}
19
20impl Default for SolveBudgets {
21 fn default() -> Self {
22 Self {
23 time_limit: Duration::from_secs(30),
24 iteration_limit: 100_000,
25 candidate_cap: 1_000,
26 memory_limit_bytes: 0,
27 }
28 }
29}
30
31impl SolveBudgets {
32 pub fn with_time_limit(seconds: u64) -> Self {
34 Self {
35 time_limit: Duration::from_secs(seconds),
36 ..Default::default()
37 }
38 }
39
40 pub fn strict(time_seconds: u64, iterations: usize, candidates: usize) -> Self {
42 Self {
43 time_limit: Duration::from_secs(time_seconds),
44 iteration_limit: iterations,
45 candidate_cap: candidates,
46 memory_limit_bytes: 0,
47 }
48 }
49
50 pub fn validate(&self) -> crate::Result<()> {
52 if self.time_limit.is_zero() {
53 return Err(crate::Error::invalid_input("time_limit must be positive"));
54 }
55 if self.iteration_limit == 0 {
56 return Err(crate::Error::invalid_input(
57 "iteration_limit must be positive",
58 ));
59 }
60 if self.candidate_cap == 0 {
61 return Err(crate::Error::invalid_input(
62 "candidate_cap must be positive",
63 ));
64 }
65 Ok(())
66 }
67
68 pub fn to_solver_params(&self, seed: u64) -> crate::SolverParams {
70 crate::SolverParams {
71 time_limit_seconds: self.time_limit.as_secs_f64(),
72 iteration_limit: self.iteration_limit,
73 num_threads: 0,
74 random_seed: seed,
75 verbosity: 0,
76 }
77 }
78
79 pub fn has_time_remaining(&self, elapsed: Duration) -> bool {
81 elapsed < self.time_limit
82 }
83}
84
85mod duration_serde {
87 use serde::{Deserialize, Deserializer, Serialize, Serializer};
88 use std::time::Duration;
89
90 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
91 where
92 S: Serializer,
93 {
94 duration.as_secs_f64().serialize(serializer)
95 }
96
97 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
98 where
99 D: Deserializer<'de>,
100 {
101 let secs = f64::deserialize(deserializer)?;
102 Ok(Duration::from_secs_f64(secs))
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_default_budgets() {
112 let budgets = SolveBudgets::default();
113 assert_eq!(budgets.time_limit, Duration::from_secs(30));
114 assert_eq!(budgets.iteration_limit, 100_000);
115 assert_eq!(budgets.candidate_cap, 1_000);
116 }
117
118 #[test]
119 fn test_validate_budgets() {
120 let budgets = SolveBudgets::default();
121 assert!(budgets.validate().is_ok());
122
123 let invalid = SolveBudgets {
124 time_limit: Duration::ZERO,
125 ..Default::default()
126 };
127 assert!(invalid.validate().is_err());
128 }
129
130 #[test]
131 fn test_to_solver_params() {
132 let budgets = SolveBudgets::with_time_limit(60);
133 let params = budgets.to_solver_params(42);
134 assert_eq!(params.time_limit_seconds, 60.0);
135 assert_eq!(params.random_seed, 42);
136 }
137
138 #[test]
139 fn test_serde_roundtrip() {
140 let budgets = SolveBudgets::with_time_limit(45);
141 let json = serde_json::to_string(&budgets).unwrap();
142 let restored: SolveBudgets = serde_json::from_str(&json).unwrap();
143 assert_eq!(restored.time_limit, budgets.time_limit);
144 }
145}