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,
44
45 pub target_kl: f64,
47
48 pub seed: u64,
56}
57
58impl Default for PPOConfig {
59 fn default() -> Self {
60 Self {
61 learning_rate: 3e-4,
62 n_epochs: 10,
63 batch_size: 64,
64 gamma: 0.99,
65 gae_lambda: 0.95,
66 clip_range: 0.2,
67 clip_range_vf: 0.2,
68 vf_coef: 0.5,
69 ent_coef: 0.01,
70 max_grad_norm: 0.5,
71 target_kl: 0.01,
72 seed: 0,
73 }
74 }
75}
76
77impl PPOConfig {
78 pub fn new() -> Self {
80 Self::default()
81 }
82
83 pub fn validate(&self) -> Result<()> {
85 if self.learning_rate <= 0.0 {
86 return Err(anyhow!("learning_rate must be positive"));
87 }
88 if self.n_epochs == 0 {
89 return Err(anyhow!("n_epochs must be positive"));
90 }
91 if self.batch_size == 0 {
92 return Err(anyhow!("batch_size must be positive"));
93 }
94 if !(0.0..=1.0).contains(&self.gamma) {
95 return Err(anyhow!("gamma must be in [0, 1]"));
96 }
97 if !(0.0..=1.0).contains(&self.gae_lambda) {
98 return Err(anyhow!("gae_lambda must be in [0, 1]"));
99 }
100 if self.clip_range <= 0.0 {
101 return Err(anyhow!("clip_range must be positive"));
102 }
103 if self.clip_range_vf <= 0.0 {
104 return Err(anyhow!("clip_range_vf must be positive"));
105 }
106 if self.vf_coef < 0.0 {
107 return Err(anyhow!("vf_coef must be non-negative"));
108 }
109 if self.ent_coef < 0.0 {
110 return Err(anyhow!("ent_coef must be non-negative"));
111 }
112 if self.max_grad_norm <= 0.0 {
113 return Err(anyhow!("max_grad_norm must be positive"));
114 }
115 if self.target_kl < 0.0 {
116 return Err(anyhow!("target_kl must be non-negative"));
117 }
118 Ok(())
119 }
120
121 pub fn learning_rate(mut self, lr: f64) -> Self {
123 self.learning_rate = lr;
124 self
125 }
126
127 pub fn n_epochs(mut self, epochs: usize) -> Self {
129 self.n_epochs = epochs;
130 self
131 }
132
133 pub fn batch_size(mut self, size: usize) -> Self {
135 self.batch_size = size;
136 self
137 }
138
139 pub fn gamma(mut self, gamma: f64) -> Self {
141 self.gamma = gamma;
142 self
143 }
144
145 pub fn gae_lambda(mut self, lambda: f64) -> Self {
147 self.gae_lambda = lambda;
148 self
149 }
150
151 pub fn clip_range(mut self, clip: f64) -> Self {
153 self.clip_range = clip;
154 self
155 }
156
157 pub fn clip_range_vf(mut self, clip: f64) -> Self {
159 self.clip_range_vf = clip;
160 self
161 }
162
163 pub fn vf_coef(mut self, coef: f64) -> Self {
165 self.vf_coef = coef;
166 self
167 }
168
169 pub fn ent_coef(mut self, coef: f64) -> Self {
171 self.ent_coef = coef;
172 self
173 }
174
175 pub fn max_grad_norm(mut self, norm: f64) -> Self {
177 self.max_grad_norm = norm;
178 self
179 }
180
181 pub fn target_kl(mut self, kl: f64) -> Self {
183 self.target_kl = kl;
184 self
185 }
186
187 pub fn seed(mut self, seed: u64) -> Self {
191 self.seed = seed;
192 self
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_default_config() {
202 let config = PPOConfig::default();
203 assert!(config.validate().is_ok());
204 assert_eq!(config.learning_rate, 3e-4);
205 assert_eq!(config.n_epochs, 10);
206 assert_eq!(config.batch_size, 64);
207 }
208
209 #[test]
210 fn test_config_validation() {
211 let config = PPOConfig::new();
213 assert!(config.validate().is_ok());
214
215 let config = PPOConfig::new().learning_rate(-1.0);
217 assert!(config.validate().is_err());
218
219 let config = PPOConfig::new().gamma(1.5);
221 assert!(config.validate().is_err());
222
223 let config = PPOConfig::new().n_epochs(0);
225 assert!(config.validate().is_err());
226
227 let config = PPOConfig::new().batch_size(0);
229 assert!(config.validate().is_err());
230
231 let config = PPOConfig::new().clip_range(-0.1);
233 assert!(config.validate().is_err());
234
235 let config = PPOConfig::new().vf_coef(-0.1);
237 assert!(config.validate().is_err());
238
239 let config = PPOConfig::new().vf_coef(0.0);
241 assert!(config.validate().is_ok());
242 }
243
244 #[test]
245 fn test_config_builder() {
246 let config = PPOConfig::new()
247 .learning_rate(1e-3)
248 .n_epochs(5)
249 .batch_size(128)
250 .gamma(0.95)
251 .clip_range(0.1);
252
253 assert_eq!(config.learning_rate, 1e-3);
254 assert_eq!(config.n_epochs, 5);
255 assert_eq!(config.batch_size, 128);
256 assert_eq!(config.gamma, 0.95);
257 assert_eq!(config.clip_range, 0.1);
258
259 assert_eq!(config.gae_lambda, 0.95);
261 assert_eq!(config.vf_coef, 0.5);
262 }
263}