thrust_rl/train/ppo/
config.rs1use anyhow::{Result, anyhow};
7
8#[derive(Debug, Clone)]
14pub struct PPOConfig {
15 pub learning_rate: f64,
17
18 pub n_epochs: usize,
20
21 pub batch_size: usize,
23
24 pub gamma: f64,
26
27 pub gae_lambda: f64,
29
30 pub clip_range: f64,
32
33 pub clip_range_vf: f64,
35
36 pub vf_coef: f64,
38
39 pub ent_coef: f64,
41
42 pub max_grad_norm: f64,
55
56 pub target_kl: f64,
58
59 pub seed: u64,
67}
68
69impl Default for PPOConfig {
70 fn default() -> Self {
71 Self {
72 learning_rate: 3e-4,
73 n_epochs: 10,
74 batch_size: 64,
75 gamma: 0.99,
76 gae_lambda: 0.95,
77 clip_range: 0.2,
78 clip_range_vf: 0.2,
79 vf_coef: 0.5,
80 ent_coef: 0.01,
81 max_grad_norm: 0.5,
82 target_kl: 0.01,
83 seed: 0,
84 }
85 }
86}
87
88impl PPOConfig {
89 pub fn new() -> Self {
91 Self::default()
92 }
93
94 pub fn validate(&self) -> Result<()> {
96 if self.learning_rate <= 0.0 {
97 return Err(anyhow!("learning_rate must be positive"));
98 }
99 if self.n_epochs == 0 {
100 return Err(anyhow!("n_epochs must be positive"));
101 }
102 if self.batch_size == 0 {
103 return Err(anyhow!("batch_size must be positive"));
104 }
105 if !(0.0..=1.0).contains(&self.gamma) {
106 return Err(anyhow!("gamma must be in [0, 1]"));
107 }
108 if !(0.0..=1.0).contains(&self.gae_lambda) {
109 return Err(anyhow!("gae_lambda must be in [0, 1]"));
110 }
111 if self.clip_range <= 0.0 {
112 return Err(anyhow!("clip_range must be positive"));
113 }
114 if self.clip_range_vf <= 0.0 {
115 return Err(anyhow!("clip_range_vf must be positive"));
116 }
117 if self.vf_coef < 0.0 {
118 return Err(anyhow!("vf_coef must be non-negative"));
119 }
120 if self.ent_coef < 0.0 {
121 return Err(anyhow!("ent_coef must be non-negative"));
122 }
123 if self.max_grad_norm <= 0.0 {
124 return Err(anyhow!("max_grad_norm must be positive"));
125 }
126 if self.target_kl < 0.0 {
127 return Err(anyhow!("target_kl must be non-negative"));
128 }
129 Ok(())
130 }
131
132 pub fn learning_rate(mut self, lr: f64) -> Self {
134 self.learning_rate = lr;
135 self
136 }
137
138 pub fn n_epochs(mut self, epochs: usize) -> Self {
140 self.n_epochs = epochs;
141 self
142 }
143
144 pub fn batch_size(mut self, size: usize) -> Self {
146 self.batch_size = size;
147 self
148 }
149
150 pub fn gamma(mut self, gamma: f64) -> Self {
152 self.gamma = gamma;
153 self
154 }
155
156 pub fn gae_lambda(mut self, lambda: f64) -> Self {
158 self.gae_lambda = lambda;
159 self
160 }
161
162 pub fn clip_range(mut self, clip: f64) -> Self {
164 self.clip_range = clip;
165 self
166 }
167
168 pub fn clip_range_vf(mut self, clip: f64) -> Self {
170 self.clip_range_vf = clip;
171 self
172 }
173
174 pub fn vf_coef(mut self, coef: f64) -> Self {
176 self.vf_coef = coef;
177 self
178 }
179
180 pub fn ent_coef(mut self, coef: f64) -> Self {
182 self.ent_coef = coef;
183 self
184 }
185
186 pub fn max_grad_norm(mut self, norm: f64) -> Self {
188 self.max_grad_norm = norm;
189 self
190 }
191
192 pub fn target_kl(mut self, kl: f64) -> Self {
194 self.target_kl = kl;
195 self
196 }
197
198 pub fn seed(mut self, seed: u64) -> Self {
202 self.seed = seed;
203 self
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_default_config() {
213 let config = PPOConfig::default();
214 assert!(config.validate().is_ok());
215 assert_eq!(config.learning_rate, 3e-4);
216 assert_eq!(config.n_epochs, 10);
217 assert_eq!(config.batch_size, 64);
218 }
219
220 #[test]
221 fn test_config_validation() {
222 let config = PPOConfig::new();
224 assert!(config.validate().is_ok());
225
226 let config = PPOConfig::new().learning_rate(-1.0);
228 assert!(config.validate().is_err());
229
230 let config = PPOConfig::new().gamma(1.5);
232 assert!(config.validate().is_err());
233
234 let config = PPOConfig::new().n_epochs(0);
236 assert!(config.validate().is_err());
237
238 let config = PPOConfig::new().batch_size(0);
240 assert!(config.validate().is_err());
241
242 let config = PPOConfig::new().clip_range(-0.1);
244 assert!(config.validate().is_err());
245
246 let config = PPOConfig::new().vf_coef(-0.1);
248 assert!(config.validate().is_err());
249
250 let config = PPOConfig::new().vf_coef(0.0);
252 assert!(config.validate().is_ok());
253 }
254
255 #[test]
256 fn test_config_builder() {
257 let config = PPOConfig::new()
258 .learning_rate(1e-3)
259 .n_epochs(5)
260 .batch_size(128)
261 .gamma(0.95)
262 .clip_range(0.1);
263
264 assert_eq!(config.learning_rate, 1e-3);
265 assert_eq!(config.n_epochs, 5);
266 assert_eq!(config.batch_size, 128);
267 assert_eq!(config.gamma, 0.95);
268 assert_eq!(config.clip_range, 0.1);
269
270 assert_eq!(config.gae_lambda, 0.95);
272 assert_eq!(config.vf_coef, 0.5);
273 }
274}