thrust_rl/train/a2c/
config.rs1use anyhow::{Result, anyhow};
14
15#[derive(Debug, Clone)]
23pub struct A2cConfig {
24 pub learning_rate: f64,
26
27 pub gamma: f64,
29
30 pub gae_lambda: f64,
33
34 pub value_coef: f64,
36
37 pub entropy_coef: f64,
39
40 pub n_steps: usize,
43
44 pub num_envs: usize,
46
47 pub max_grad_norm: f64,
49
50 pub normalize_advantages: bool,
53
54 pub use_vtrace: bool,
59
60 pub vtrace_rho_bar: f32,
64
65 pub vtrace_c_bar: f32,
68
69 pub seed: u64,
74}
75
76impl Default for A2cConfig {
77 fn default() -> Self {
78 Self {
79 learning_rate: 7e-4,
80 gamma: 0.99,
81 gae_lambda: 1.0,
82 value_coef: 0.5,
83 entropy_coef: 0.01,
84 n_steps: 5,
85 num_envs: 16,
86 max_grad_norm: 0.5,
87 normalize_advantages: true,
88 use_vtrace: false,
89 vtrace_rho_bar: 1.0,
90 vtrace_c_bar: 1.0,
91 seed: 0,
92 }
93 }
94}
95
96impl A2cConfig {
97 pub fn new() -> Self {
99 Self::default()
100 }
101
102 pub fn validate(&self) -> Result<()> {
106 if self.learning_rate <= 0.0 {
107 return Err(anyhow!("learning_rate must be positive, got {}", self.learning_rate));
108 }
109 if !(0.0..=1.0).contains(&self.gamma) {
110 return Err(anyhow!("gamma must be in [0, 1], got {}", self.gamma));
111 }
112 if !(0.0..=1.0).contains(&self.gae_lambda) {
113 return Err(anyhow!("gae_lambda must be in [0, 1], got {}", self.gae_lambda));
114 }
115 if self.value_coef < 0.0 {
116 return Err(anyhow!("value_coef must be non-negative, got {}", self.value_coef));
117 }
118 if self.entropy_coef < 0.0 {
119 return Err(anyhow!("entropy_coef must be non-negative, got {}", self.entropy_coef));
120 }
121 if self.n_steps == 0 {
122 return Err(anyhow!("n_steps must be positive"));
123 }
124 if self.num_envs == 0 {
125 return Err(anyhow!("num_envs must be positive"));
126 }
127 if self.max_grad_norm <= 0.0 {
128 return Err(anyhow!("max_grad_norm must be positive, got {}", self.max_grad_norm));
129 }
130 if self.vtrace_rho_bar <= 0.0 {
131 return Err(anyhow!("vtrace_rho_bar must be positive, got {}", self.vtrace_rho_bar));
132 }
133 if self.vtrace_c_bar <= 0.0 {
134 return Err(anyhow!("vtrace_c_bar must be positive, got {}", self.vtrace_c_bar));
135 }
136 Ok(())
137 }
138
139 pub fn learning_rate(mut self, lr: f64) -> Self {
143 self.learning_rate = lr;
144 self
145 }
146
147 pub fn gamma(mut self, gamma: f64) -> Self {
149 self.gamma = gamma;
150 self
151 }
152
153 pub fn gae_lambda(mut self, lambda: f64) -> Self {
155 self.gae_lambda = lambda;
156 self
157 }
158
159 pub fn value_coef(mut self, coef: f64) -> Self {
161 self.value_coef = coef;
162 self
163 }
164
165 pub fn entropy_coef(mut self, coef: f64) -> Self {
167 self.entropy_coef = coef;
168 self
169 }
170
171 pub fn n_steps(mut self, steps: usize) -> Self {
173 self.n_steps = steps;
174 self
175 }
176
177 pub fn num_envs(mut self, envs: usize) -> Self {
179 self.num_envs = envs;
180 self
181 }
182
183 pub fn max_grad_norm(mut self, norm: f64) -> Self {
185 self.max_grad_norm = norm;
186 self
187 }
188
189 pub fn normalize_advantages(mut self, enabled: bool) -> Self {
191 self.normalize_advantages = enabled;
192 self
193 }
194
195 pub fn use_vtrace(mut self, enabled: bool) -> Self {
197 self.use_vtrace = enabled;
198 self
199 }
200
201 pub fn vtrace_rho_bar(mut self, rho_bar: f32) -> Self {
203 self.vtrace_rho_bar = rho_bar;
204 self
205 }
206
207 pub fn vtrace_c_bar(mut self, c_bar: f32) -> Self {
209 self.vtrace_c_bar = c_bar;
210 self
211 }
212
213 pub fn seed(mut self, seed: u64) -> Self {
215 self.seed = seed;
216 self
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn test_default_config() {
226 let config = A2cConfig::default();
227 assert!(config.validate().is_ok());
228 assert_eq!(config.learning_rate, 7e-4);
229 assert_eq!(config.gamma, 0.99);
230 assert_eq!(config.gae_lambda, 1.0);
231 assert_eq!(config.value_coef, 0.5);
232 assert_eq!(config.entropy_coef, 0.01);
233 assert_eq!(config.n_steps, 5);
234 assert_eq!(config.num_envs, 16);
235 assert_eq!(config.max_grad_norm, 0.5);
236 assert!(config.normalize_advantages);
237 assert!(!config.use_vtrace);
239 assert_eq!(config.vtrace_rho_bar, 1.0);
240 assert_eq!(config.vtrace_c_bar, 1.0);
241 assert_eq!(config.seed, 0);
242 }
243
244 #[test]
245 fn test_config_validation() {
246 let config = A2cConfig::new();
248 assert!(config.validate().is_ok());
249
250 assert!(A2cConfig::new().learning_rate(0.0).validate().is_err());
252 assert!(A2cConfig::new().learning_rate(-1.0).validate().is_err());
253
254 assert!(A2cConfig::new().gamma(-0.1).validate().is_err());
256 assert!(A2cConfig::new().gamma(1.5).validate().is_err());
257 assert!(A2cConfig::new().gamma(0.0).validate().is_ok());
258 assert!(A2cConfig::new().gamma(1.0).validate().is_ok());
259
260 assert!(A2cConfig::new().gae_lambda(-0.1).validate().is_err());
262 assert!(A2cConfig::new().gae_lambda(1.5).validate().is_err());
263 assert!(A2cConfig::new().gae_lambda(0.0).validate().is_ok());
264 assert!(A2cConfig::new().gae_lambda(1.0).validate().is_ok());
265
266 assert!(A2cConfig::new().value_coef(-0.1).validate().is_err());
268 assert!(A2cConfig::new().value_coef(0.0).validate().is_ok());
269
270 assert!(A2cConfig::new().entropy_coef(-0.1).validate().is_err());
272 assert!(A2cConfig::new().entropy_coef(0.0).validate().is_ok());
273
274 assert!(A2cConfig::new().n_steps(0).validate().is_err());
276
277 assert!(A2cConfig::new().num_envs(0).validate().is_err());
279
280 assert!(A2cConfig::new().max_grad_norm(0.0).validate().is_err());
282 assert!(A2cConfig::new().max_grad_norm(-1.0).validate().is_err());
283
284 assert!(A2cConfig::new().vtrace_rho_bar(0.0).validate().is_err());
286 assert!(A2cConfig::new().vtrace_rho_bar(-0.5).validate().is_err());
287 assert!(A2cConfig::new().vtrace_rho_bar(1.0).validate().is_ok());
288 assert!(A2cConfig::new().vtrace_c_bar(0.0).validate().is_err());
289 assert!(A2cConfig::new().vtrace_c_bar(-0.5).validate().is_err());
290 assert!(A2cConfig::new().vtrace_c_bar(1.0).validate().is_ok());
291 assert!(
293 A2cConfig::new()
294 .use_vtrace(true)
295 .vtrace_rho_bar(0.8)
296 .vtrace_c_bar(1.2)
297 .validate()
298 .is_ok()
299 );
300 }
301
302 #[test]
303 fn test_config_builder() {
304 let config = A2cConfig::new()
305 .learning_rate(1e-3)
306 .gamma(0.95)
307 .gae_lambda(0.9)
308 .value_coef(0.25)
309 .entropy_coef(0.05)
310 .n_steps(20)
311 .num_envs(8)
312 .max_grad_norm(1.0)
313 .normalize_advantages(false)
314 .use_vtrace(true)
315 .vtrace_rho_bar(0.9)
316 .vtrace_c_bar(1.1)
317 .seed(42);
318
319 assert!(config.validate().is_ok());
320 assert_eq!(config.learning_rate, 1e-3);
321 assert_eq!(config.gamma, 0.95);
322 assert_eq!(config.gae_lambda, 0.9);
323 assert_eq!(config.value_coef, 0.25);
324 assert_eq!(config.entropy_coef, 0.05);
325 assert_eq!(config.n_steps, 20);
326 assert_eq!(config.num_envs, 8);
327 assert_eq!(config.max_grad_norm, 1.0);
328 assert!(!config.normalize_advantages);
329 assert!(config.use_vtrace);
330 assert_eq!(config.vtrace_rho_bar, 0.9);
331 assert_eq!(config.vtrace_c_bar, 1.1);
332 assert_eq!(config.seed, 42);
333 }
334}