1use std::collections::HashMap;
2use trustformers_core::errors::{Result, TrustformersError};
3use trustformers_core::tensor::Tensor;
4use trustformers_core::traits::Optimizer;
5
6#[derive(Debug)]
19pub struct PDEAwareOptimizer {
20 pub learning_rate: f32,
21 pub beta1: f32,
22 pub beta2: f32,
23 pub epsilon: f32,
24 pub weight_decay: f32,
25
26 pub residual_variance_weight: f32, pub gradient_alignment_factor: f32, pub smoothing_factor: f32, pub sharp_gradient_threshold: f32, pub step: usize,
34 pub momentum: HashMap<String, Vec<f32>>,
35 pub variance: HashMap<String, Vec<f32>>,
36 pub residual_variance_history: Vec<f32>,
37 pub gradient_alignment_history: Vec<f32>,
38 params: crate::param_id::ParamRegistry,
43}
44
45#[derive(Debug, Clone)]
46pub struct PDEAwareConfig {
47 pub learning_rate: f32,
48 pub beta1: f32,
49 pub beta2: f32,
50 pub epsilon: f32,
51 pub weight_decay: f32,
52 pub residual_variance_weight: f32,
53 pub gradient_alignment_factor: f32,
54 pub smoothing_factor: f32,
55 pub sharp_gradient_threshold: f32,
56}
57
58impl Default for PDEAwareConfig {
59 fn default() -> Self {
60 Self {
61 learning_rate: 1e-3,
62 beta1: 0.9,
63 beta2: 0.999,
64 epsilon: 1e-8,
65 weight_decay: 0.0,
66 residual_variance_weight: 0.1,
67 gradient_alignment_factor: 0.05,
68 smoothing_factor: 0.95,
69 sharp_gradient_threshold: 1.0,
70 }
71 }
72}
73
74impl Default for PDEAwareOptimizer {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80impl PDEAwareOptimizer {
81 pub fn new() -> Self {
82 Self::from_config(PDEAwareConfig::default())
83 }
84
85 pub fn from_config(config: PDEAwareConfig) -> Self {
86 Self {
87 learning_rate: config.learning_rate,
88 beta1: config.beta1,
89 beta2: config.beta2,
90 epsilon: config.epsilon,
91 weight_decay: config.weight_decay,
92 residual_variance_weight: config.residual_variance_weight,
93 gradient_alignment_factor: config.gradient_alignment_factor,
94 smoothing_factor: config.smoothing_factor,
95 sharp_gradient_threshold: config.sharp_gradient_threshold,
96 step: 0,
97 momentum: HashMap::new(),
98 variance: HashMap::new(),
99 residual_variance_history: Vec::new(),
100 gradient_alignment_history: Vec::new(),
101 params: crate::param_id::ParamRegistry::new(),
102 }
103 }
104
105 pub fn for_burgers_equation() -> Self {
107 Self::from_config(PDEAwareConfig {
108 learning_rate: 5e-4,
109 beta1: 0.95,
110 beta2: 0.999,
111 epsilon: 1e-10,
112 weight_decay: 1e-6,
113 residual_variance_weight: 0.15,
114 gradient_alignment_factor: 0.08,
115 smoothing_factor: 0.98,
116 sharp_gradient_threshold: 0.8,
117 })
118 }
119
120 pub fn for_allen_cahn() -> Self {
122 Self::from_config(PDEAwareConfig {
123 learning_rate: 1e-3,
124 beta1: 0.9,
125 beta2: 0.995,
126 epsilon: 1e-9,
127 weight_decay: 1e-5,
128 residual_variance_weight: 0.2,
129 gradient_alignment_factor: 0.1,
130 smoothing_factor: 0.95,
131 sharp_gradient_threshold: 1.5,
132 })
133 }
134
135 pub fn for_kdv_equation() -> Self {
137 Self::from_config(PDEAwareConfig {
138 learning_rate: 2e-4,
139 beta1: 0.95,
140 beta2: 0.9995,
141 epsilon: 1e-12,
142 weight_decay: 0.0,
143 residual_variance_weight: 0.25,
144 gradient_alignment_factor: 0.12,
145 smoothing_factor: 0.99,
146 sharp_gradient_threshold: 0.5,
147 })
148 }
149
150 pub fn for_sharp_gradients() -> Self {
152 Self::from_config(PDEAwareConfig {
153 learning_rate: 1e-4,
154 beta1: 0.95,
155 beta2: 0.9999,
156 epsilon: 1e-10,
157 weight_decay: 1e-7,
158 residual_variance_weight: 0.3,
159 gradient_alignment_factor: 0.15,
160 smoothing_factor: 0.99,
161 sharp_gradient_threshold: 0.3,
162 })
163 }
164
165 fn compute_residual_variance_from_norm(&mut self, grad_norm: f32) -> f32 {
167 let variance = grad_norm;
168
169 self.residual_variance_history.push(variance);
171
172 if self.residual_variance_history.len() > 100 {
174 self.residual_variance_history.remove(0);
175 }
176
177 if self.residual_variance_history.len() > 1 {
179 let prev_variance =
180 self.residual_variance_history[self.residual_variance_history.len() - 2];
181 self.smoothing_factor * prev_variance + (1.0 - self.smoothing_factor) * variance
182 } else {
183 variance
184 }
185 }
186
187 fn is_sharp_gradient_region_from_norm(&self, grad_norm: f32, max_grad: f32) -> bool {
189 grad_norm > self.sharp_gradient_threshold || max_grad > 2.0 * self.sharp_gradient_threshold
191 }
192
193 pub fn adaptive_learning_rate(
195 &self,
196 base_lr: f32,
197 residual_variance: f32,
198 is_sharp_region: bool,
199 ) -> f32 {
200 let mut adaptive_lr = base_lr;
201
202 if residual_variance > 0.1 {
204 adaptive_lr *= 1.0 / (1.0 + self.residual_variance_weight * residual_variance);
205 }
206
207 if is_sharp_region {
209 adaptive_lr *= 0.5;
210 }
211
212 adaptive_lr.clamp(base_lr * 0.01, base_lr * 2.0)
214 }
215
216 pub fn get_pde_stats(&self) -> PDEAwareStats {
218 let avg_residual_variance = if !self.residual_variance_history.is_empty() {
219 self.residual_variance_history.iter().sum::<f32>()
220 / self.residual_variance_history.len() as f32
221 } else {
222 0.0
223 };
224
225 PDEAwareStats {
226 step: self.step,
227 average_residual_variance: avg_residual_variance,
228 parameters_tracked: self.momentum.len(),
229 }
230 }
231}
232
233#[derive(Debug, Clone)]
234pub struct PDEAwareStats {
235 pub step: usize,
236 pub average_residual_variance: f32,
237 pub parameters_tracked: usize,
238}
239
240impl Optimizer for PDEAwareOptimizer {
241 fn update(&mut self, parameter: &mut Tensor, grad: &Tensor) -> Result<()> {
242 match (parameter, grad) {
243 (Tensor::F32(param), Tensor::F32(grad_arr)) => {
244 self.step += 1;
245
246 let param_id = self.params.key_for_addr(param.as_ptr() as usize, param.len())?;
247
248 let grad_norm: f32 = grad_arr.iter().map(|g| g * g).sum::<f32>().sqrt();
250 let max_grad: f32 = grad_arr.iter().map(|g| g.abs()).fold(0.0, f32::max);
251
252 let residual_variance = self.compute_residual_variance_from_norm(grad_norm);
253 let is_sharp_region = self.is_sharp_gradient_region_from_norm(grad_norm, max_grad);
254
255 let adaptive_lr = self.adaptive_learning_rate(
257 self.learning_rate,
258 residual_variance,
259 is_sharp_region,
260 );
261
262 let m = self
264 .momentum
265 .entry(param_id.clone())
266 .or_insert_with(|| vec![0.0; grad_arr.len()]);
267 let v = self.variance.entry(param_id).or_insert_with(|| vec![0.0; grad_arr.len()]);
268
269 if m.len() != grad_arr.len() || v.len() != grad_arr.len() {
270 return Err(TrustformersError::tensor_op_error(
271 "Momentum/variance buffer size mismatch",
272 "pde_aware_update",
273 ));
274 }
275
276 for i in 0..grad_arr.len() {
278 m[i] = self.beta1 * m[i] + (1.0 - self.beta1) * grad_arr[i];
279 v[i] = self.beta2 * v[i] + (1.0 - self.beta2) * grad_arr[i] * grad_arr[i];
280 }
281
282 let bias_correction1 = 1.0 - self.beta1.powi(self.step as i32);
284 let bias_correction2 = 1.0 - self.beta2.powi(self.step as i32);
285
286 let mut update_vec = vec![0.0; param.len()];
288 for i in 0..param.len() {
289 let m_hat = m[i] / bias_correction1;
290 let v_hat = v[i] / bias_correction2;
291
292 let update = adaptive_lr * m_hat / (v_hat.sqrt() + self.epsilon);
293 update_vec[i] = update;
294
295 if self.weight_decay > 0.0 {
297 update_vec[i] += self.weight_decay * param[i];
298 }
299 }
300
301 for (i, update) in update_vec.iter().enumerate() {
303 param[i] -= update;
304 }
305
306 Ok(())
307 },
308 _ => Err(TrustformersError::tensor_op_error(
309 "Unsupported tensor types for PDEAwareOptimizer",
310 "pde_aware_update",
311 )),
312 }
313 }
314
315 fn zero_grad(&mut self) {
316 }
318
319 fn step(&mut self) {
320 }
322
323 fn get_lr(&self) -> f32 {
324 self.learning_rate
325 }
326
327 fn set_lr(&mut self, lr: f32) {
328 self.learning_rate = lr;
329 }
330}
331
332#[cfg(test)]
333#[path = "pde_aware_tests.rs"]
334mod pde_aware_tests;