Skip to main content

optirs_learned/adaptive/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::error::Result;
6use crate::transformer_based_optimizer::{
7    ArchitectureUpdate, TransformerOptimizer, TransformerOptimizerConfig,
8};
9
10use super::architecture_adapter::DynamicArchitectureAdapter;
11use super::landscape::LandscapeStatistics;
12use super::performance_predictor::TransformerPerformancePredictor;
13use super::predictor::{PredictorFitReport, PredictorSample};
14use crate::common::cast_scalar;
15use scirs2_core::ndarray::{Array1, Array3};
16use scirs2_core::numeric::Float;
17use std::collections::{HashMap, VecDeque};
18use std::fmt::Debug;
19
20/// Architecture adaptation result
21#[derive(Debug)]
22pub struct ArchitectureAdaptation<
23    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
24> {
25    /// Adapted configuration
26    pub adapted_config: TransformerOptimizerConfig<T>,
27    /// Architecture changes
28    pub changes: Vec<ArchitectureChange>,
29    /// Expected improvement
30    pub expected_improvement: T,
31    /// Adaptation confidence
32    pub confidence: T,
33}
34/// Memory-efficient attention manager
35#[derive(Debug)]
36pub struct MemoryEfficientAttentionManager<
37    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
38> {
39    /// Attention pattern cache
40    pattern_cache: AttentionPatternCache<T>,
41    /// Memory usage tracker
42    memory_tracker: MemoryUsageTracker,
43    /// Minimum sparsity level, from `AdaptiveConfig::attention_sparsity_threshold`
44    sparsity_floor: T,
45    /// Whether the head count may be reduced, from
46    /// `AdaptiveConfig::dynamic_head_pruning`
47    allow_head_pruning: bool,
48    /// Lower bound on the attention span, from
49    /// `AdaptiveConfig::min_sequence_length`
50    min_span: usize,
51    /// Upper bound on the attention span, from
52    /// `AdaptiveConfig::max_sequence_length`
53    max_span: usize,
54}
55impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
56    MemoryEfficientAttentionManager<T>
57{
58    /// Build the attention manager from the adaptive configuration.
59    ///
60    /// The configuration is genuinely consumed: `attention_sparsity_threshold`
61    /// becomes the sparsity floor used by [`Self::optimize_attention`],
62    /// `memory_budget` becomes the tracker's budget, `dynamic_head_pruning`
63    /// decides whether the head count is allowed to shrink, and
64    /// `max_sequence_length` / `min_sequence_length` bound the attention span.
65    /// Previously every field was ignored.
66    fn new(config: &AdaptiveConfig<T>) -> Result<Self> {
67        if config.min_sequence_length == 0
68            || config.min_sequence_length > config.max_sequence_length
69        {
70            return Err(crate::error::OptimError::InvalidConfig(format!(
71                "sequence length bounds must satisfy 0 < min ({}) <= max ({})",
72                config.min_sequence_length, config.max_sequence_length
73            )));
74        }
75        Ok(Self {
76            pattern_cache: AttentionPatternCache::new(),
77            memory_tracker: MemoryUsageTracker::with_budget(config.memory_budget),
78            sparsity_floor: config.attention_sparsity_threshold,
79            allow_head_pruning: config.dynamic_head_pruning,
80            min_span: config.min_sequence_length,
81            max_span: config.max_sequence_length,
82        })
83    }
84    fn optimize_attention(
85        &mut self,
86        analysis: &LandscapeAnalysis<T>,
87    ) -> Result<AttentionOptimization<T>> {
88        let complexity = analysis.complexity.to_f64().unwrap_or(0.5);
89        let difficulty = analysis.difficulty.to_f64().unwrap_or(0.3);
90        let (num_heads, seq_len) = self.determine_attention_dimensions(complexity, difficulty)?;
91        let mut attention_patterns = Array3::zeros((num_heads, seq_len, seq_len));
92        self.generate_attention_patterns(&mut attention_patterns, analysis)?;
93        // A more complex landscape needs a denser (less pruned) pattern, but the
94        // configured `attention_sparsity_threshold` is always the floor.
95        let requested: T =
96            scirs2_core::numeric::NumCast::from(if complexity > 0.7 { 0.05 } else { 0.15 })
97                .unwrap_or_else(|| T::zero());
98        let sparsitylevel = if requested > self.sparsity_floor {
99            requested
100        } else {
101            self.sparsity_floor
102        };
103        self.apply_sparsity_mask(&mut attention_patterns, sparsitylevel)?;
104        let pattern_key = format!("pattern_{}_{}", num_heads, seq_len);
105        self.pattern_cache
106            .patterns
107            .insert(pattern_key.clone(), attention_patterns.clone());
108        *self
109            .pattern_cache
110            .usage_frequency
111            .entry(pattern_key)
112            .or_insert(0) += 1;
113        let original_size = 8 * 512 * 512 * std::mem::size_of::<f32>();
114        let optimized_size = num_heads * seq_len * seq_len * std::mem::size_of::<f32>();
115        let memory_savings = original_size.saturating_sub(optimized_size);
116        let speedup_from_sparsity = T::one() / sparsitylevel;
117        // `seq_len` is `>= 1` (clamped to `min_span.max(1)` above), so the
118        // divisor is never zero.
119        let speedup_from_dimensions: T =
120            cast_scalar(512.0 * 512.0 / (seq_len.max(1) * seq_len.max(1)) as f64)?;
121        let computational_speedup = (speedup_from_sparsity + speedup_from_dimensions)
122            / scirs2_core::numeric::NumCast::from(2.0).unwrap_or_else(|| T::zero());
123        self.memory_tracker.current_usage += optimized_size;
124        if self.memory_tracker.current_usage > self.memory_tracker.peak_usage {
125            self.memory_tracker.peak_usage = self.memory_tracker.current_usage;
126        }
127        Ok(AttentionOptimization {
128            attention_patterns,
129            sparsitylevel,
130            memory_savings,
131            computational_speedup,
132        })
133    }
134    /// Choose (head count, attention span) for the measured landscape.
135    ///
136    /// Head count grows with complexity; it is only allowed to *shrink* below
137    /// the base when `AdaptiveConfig::dynamic_head_pruning` is enabled. The span
138    /// grows with difficulty and is clamped to the configured
139    /// `[min_sequence_length, max_sequence_length]` window instead of a
140    /// hardcoded `[256, 1024]`.
141    fn determine_attention_dimensions(
142        &self,
143        complexity: f64,
144        difficulty: f64,
145    ) -> Result<(usize, usize)> {
146        let base_heads = 8;
147        let scaled_heads = if complexity > 0.8 {
148            (base_heads as f64 * 1.5) as usize
149        } else if complexity < 0.3 && self.allow_head_pruning {
150            (base_heads as f64 * 0.75) as usize
151        } else {
152            base_heads
153        };
154        let heads = scaled_heads.clamp(4, 16);
155
156        // Base span is the midpoint of the configured window, so the whole
157        // decision lives inside the caller's budget.
158        let base_span = (self.min_span + self.max_span) / 2;
159        let scaled_span = if difficulty > 0.7 {
160            (base_span as f64 * 1.2) as usize
161        } else if difficulty < 0.3 {
162            (base_span as f64 * 0.8) as usize
163        } else {
164            base_span
165        };
166        let seq_len = scaled_span.clamp(self.min_span, self.max_span).max(1);
167        Ok((heads, seq_len))
168    }
169    fn generate_attention_patterns(
170        &self,
171        patterns: &mut Array3<T>,
172        analysis: &LandscapeAnalysis<T>,
173    ) -> Result<()> {
174        let (num_heads, seq_len, _) = patterns.dim();
175        for head in 0..num_heads {
176            for i in 0..seq_len {
177                for j in 0..seq_len {
178                    let distance = ((i as i32 - j as i32).abs() as f64).sqrt();
179                    let base_attention = (-scirs2_core::numeric::NumCast::from(distance)
180                        .unwrap_or_else(|| T::zero())
181                        / (scirs2_core::numeric::NumCast::from(seq_len)
182                            .unwrap_or_else(|| T::zero())
183                            * scirs2_core::numeric::NumCast::from(0.1)
184                                .unwrap_or_else(|| T::zero())))
185                    .exp();
186                    let complexity_factor = analysis.complexity.to_f64().unwrap_or(0.5);
187                    let modulated_attention = base_attention
188                        * (T::one()
189                            + scirs2_core::numeric::NumCast::from(complexity_factor)
190                                .unwrap_or_else(|| T::zero())
191                                * scirs2_core::numeric::NumCast::from(0.3)
192                                    .unwrap_or_else(|| T::zero()));
193                    patterns[[head, i, j]] =
194                        scirs2_core::numeric::NumCast::from(modulated_attention)
195                            .unwrap_or_else(|| T::zero());
196                }
197            }
198        }
199        Ok(())
200    }
201    fn apply_sparsity_mask(&self, patterns: &mut Array3<T>, sparsitylevel: T) -> Result<()> {
202        let sparsity_threshold = sparsitylevel.to_f64().unwrap_or(0.1);
203        patterns.map_inplace(|x| {
204            if x.to_f64().unwrap_or(0.0) < sparsity_threshold {
205                *x = T::zero();
206            }
207        });
208        Ok(())
209    }
210}
211/// Adaptive sequence processor for variable-length optimization histories
212#[derive(Debug)]
213pub struct AdaptiveSequenceProcessor<
214    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
215> {
216    /// Current sequence length
217    current_length: usize,
218    /// Sequence importance scores
219    importance_scores: VecDeque<T>,
220    /// Sequence compression ratio
221    compression_ratio: T,
222    /// Lower bound, from `AdaptiveConfig::min_sequence_length`
223    min_length: usize,
224    /// Upper bound, from `AdaptiveConfig::max_sequence_length`
225    max_length: usize,
226    /// Whether the length may change, from `AdaptiveConfig::adaptive_sequence_length`
227    length_adaptation_enabled: bool,
228}
229impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
230    AdaptiveSequenceProcessor<T>
231{
232    /// Build the sequence processor from the adaptive configuration.
233    ///
234    /// `min_sequence_length` / `max_sequence_length` bound every adaptation (the
235    /// starting length is the midpoint), and `adaptive_sequence_length` decides
236    /// whether the length is allowed to move at all. Previously the starting
237    /// length was the constant 512 and the bounds were hardcoded to `[64, 2048]`
238    /// inside `adapt_to_landscape`.
239    fn new(config: &AdaptiveConfig<T>) -> Result<Self> {
240        if config.min_sequence_length == 0
241            || config.min_sequence_length > config.max_sequence_length
242        {
243            return Err(crate::error::OptimError::InvalidConfig(format!(
244                "sequence length bounds must satisfy 0 < min ({}) <= max ({})",
245                config.min_sequence_length, config.max_sequence_length
246            )));
247        }
248        Ok(Self {
249            current_length: (config.min_sequence_length + config.max_sequence_length) / 2,
250            importance_scores: VecDeque::new(),
251            compression_ratio: scirs2_core::numeric::NumCast::from(0.8)
252                .unwrap_or_else(|| T::zero()),
253            min_length: config.min_sequence_length,
254            max_length: config.max_sequence_length,
255            length_adaptation_enabled: config.adaptive_sequence_length,
256        })
257    }
258    fn adapt_to_landscape(
259        &mut self,
260        analysis: &LandscapeAnalysis<T>,
261    ) -> Result<SequenceAdaptation<T>> {
262        let complexity_factor = analysis.complexity.to_f64().unwrap_or(0.5);
263        let difficulty_factor = analysis.difficulty.to_f64().unwrap_or(0.3);
264        let new_length = if !self.length_adaptation_enabled {
265            self.current_length
266        } else if complexity_factor > 0.7 {
267            ((self.current_length as f64 * 1.2) as usize).min(self.max_length)
268        } else if complexity_factor < 0.3 {
269            ((self.current_length as f64 * 0.8) as usize).max(self.min_length)
270        } else {
271            self.current_length
272        };
273        let new_compression_ratio = if difficulty_factor > 0.6 {
274            self.compression_ratio
275                * scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero())
276        } else {
277            self.compression_ratio
278                * scirs2_core::numeric::NumCast::from(1.1).unwrap_or_else(|| T::zero())
279        }
280        .min(scirs2_core::numeric::NumCast::from(0.95).unwrap_or_else(|| T::zero()))
281        .max(scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()));
282        self.current_length = new_length;
283        self.compression_ratio = new_compression_ratio;
284        let information_preservation = T::one()
285            - (T::one() - new_compression_ratio)
286                * scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero());
287        let length_efficiency =
288            scirs2_core::numeric::NumCast::from(self.current_length as f64 / new_length as f64)
289                .unwrap_or_else(|| T::zero());
290        let compression_efficiency = T::one() / new_compression_ratio;
291        let efficiency_gain = (length_efficiency + compression_efficiency)
292            / scirs2_core::numeric::NumCast::from(2.0).unwrap_or_else(|| T::zero());
293        self.update_importance_scores(analysis)?;
294        Ok(SequenceAdaptation {
295            new_length,
296            compression_ratio: new_compression_ratio,
297            information_preservation,
298            efficiency_gain,
299        })
300    }
301    fn update_importance_scores(&mut self, analysis: &LandscapeAnalysis<T>) -> Result<()> {
302        let base_importance = scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero());
303        let complexity_boost = analysis.complexity
304            * scirs2_core::numeric::NumCast::from(0.3).unwrap_or_else(|| T::zero());
305        let difficulty_boost = analysis.difficulty
306            * scirs2_core::numeric::NumCast::from(0.2).unwrap_or_else(|| T::zero());
307        let new_importance = base_importance + complexity_boost + difficulty_boost;
308        self.importance_scores.push_back(new_importance);
309        if self.importance_scores.len() > 100 {
310            self.importance_scores.pop_front();
311        }
312        Ok(())
313    }
314}
315/// Landscape features for optimization analysis
316#[derive(Debug, Clone)]
317pub struct LandscapeFeatures<
318    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
319> {
320    /// Smoothness measure
321    pub(super) smoothness: T,
322    /// Multimodality indicator
323    pub(super) multimodality: T,
324    /// Noise level
325    pub(super) noise_level: T,
326}
327/// Sequence adaptation result
328#[derive(Debug)]
329pub struct SequenceAdaptation<
330    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
331> {
332    /// New sequence length
333    pub new_length: usize,
334    /// Compression ratio
335    pub compression_ratio: T,
336    /// Information preservation score
337    pub information_preservation: T,
338    /// Processing efficiency gain
339    pub efficiency_gain: T,
340}
341/// Landscape analysis result
342#[derive(Debug)]
343pub struct LandscapeAnalysis<
344    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
345> {
346    /// Landscape complexity
347    pub complexity: T,
348    /// Optimization difficulty
349    pub difficulty: T,
350    /// Recommended strategies
351    pub recommended_strategies: Vec<OptimizationStrategy>,
352    /// Analysis confidence
353    pub confidence: T,
354}
355#[derive(Debug, Clone, Copy)]
356pub enum PositionalEncodingType {
357    Sinusoidal,
358    Learned,
359    Rotary,
360    Relative,
361}
362/// Complexity estimator
363#[derive(Debug)]
364pub struct ComplexityEstimator<
365    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
366> {
367    /// Computational complexity
368    computational_complexity: T,
369    /// Generalization complexity
370    generalization_complexity: T,
371}
372impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
373    ComplexityEstimator<T>
374{
375    fn new() -> Self {
376        Self {
377            computational_complexity: scirs2_core::numeric::NumCast::from(0.5)
378                .unwrap_or_else(|| T::zero()),
379            generalization_complexity: scirs2_core::numeric::NumCast::from(0.5)
380                .unwrap_or_else(|| T::zero()),
381        }
382    }
383}
384/// Architecture performance metrics
385#[derive(Debug, Clone)]
386pub struct ArchitecturePerformance<
387    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
388> {
389    /// Final performance
390    final_performance: T,
391}
392/// Enhancement result
393#[derive(Debug)]
394pub struct EnhancementResult<
395    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
396> {
397    /// Sequence processing adaptations
398    pub sequence_adaptation: SequenceAdaptation<T>,
399    /// Attention optimizations
400    pub attention_optimization: AttentionOptimization<T>,
401    /// Architecture adaptations
402    pub architecture_adaptation: ArchitectureAdaptation<T>,
403    /// Performance predictions
404    pub performance_prediction: PerformancePrediction<T>,
405    /// Landscape analysis
406    pub landscape_analysis: LandscapeAnalysis<T>,
407    /// Convergence metrics
408    pub convergence_metrics: ConvergenceMetrics<T>,
409    /// What `enhance_optimizer` actually did to the optimizer it was handed.
410    ///
411    /// `Rebuilt` means every learned parameter was discarded and re-initialized,
412    /// which is destructive; the caller must be able to see that rather than
413    /// having it happen silently. `enhanced_optimize_step` does not touch an
414    /// optimizer at all and always reports `Unchanged`.
415    pub architecture_update: ArchitectureUpdate,
416}
417/// Enhancement statistics for tracking performance
418#[derive(Debug, Clone)]
419pub struct EnhancementStatistics<
420    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
421> {
422    /// Total number of enhancements performed
423    pub total_enhancements: usize,
424    /// Average complexity of analyzed landscapes
425    pub average_complexity: T,
426    /// Average performance achieved
427    pub average_performance: T,
428    /// Memory efficiency measure
429    pub memory_efficiency: T,
430    /// Success rate of adaptations
431    pub adaptation_success_rate: T,
432}
433/// Architecture change types
434#[derive(Debug, Clone)]
435pub enum ArchitectureChange {
436    LayerCountChange(usize),
437    HiddenSizeChange(usize),
438    AttentionHeadChange(usize),
439    ActivationChange(ActivationType),
440    DropoutChange(f64),
441}
442/// Resource constraints for adaptation
443#[derive(Debug, Clone)]
444pub struct ResourceConstraints {
445    /// Maximum memory usage (MB)
446    pub(super) max_memory: usize,
447}
448/// Attention pattern cache for efficiency
449#[derive(Debug)]
450pub struct AttentionPatternCache<
451    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
452> {
453    /// Cached patterns
454    patterns: HashMap<String, Array3<T>>,
455    /// Pattern usage frequency
456    usage_frequency: HashMap<String, usize>,
457}
458impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
459    AttentionPatternCache<T>
460{
461    fn new() -> Self {
462        Self {
463            patterns: HashMap::new(),
464            usage_frequency: HashMap::new(),
465        }
466    }
467}
468/// Optimization landscape analyzer
469#[derive(Debug)]
470pub struct OptimizationLandscapeAnalyzer<
471    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
472> {
473    /// Landscape features
474    landscape_features: LandscapeFeatures<T>,
475    /// Complexity estimator
476    complexity_estimator: ComplexityEstimator<T>,
477    /// Analysis cache
478    analysis_cache: HashMap<String, AnalysisResult<T>>,
479    /// Horizon (in history samples) at which the analysis confidence saturates.
480    /// Taken from `AdaptiveConfig::prediction_horizon`.
481    confidence_horizon: usize,
482    /// Statistics of the most recent analysis, for inspection and testing.
483    last_statistics: LandscapeStatistics,
484}
485impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
486    OptimizationLandscapeAnalyzer<T>
487{
488    fn new(config: &AdaptiveConfig<T>) -> Result<Self> {
489        Ok(Self {
490            landscape_features: LandscapeFeatures::default(),
491            complexity_estimator: ComplexityEstimator::new(),
492            analysis_cache: HashMap::new(),
493            confidence_horizon: config.prediction_horizon.max(2),
494            last_statistics: LandscapeStatistics::empty(),
495        })
496    }
497
498    /// Analyze the optimization landscape from the observed history.
499    ///
500    /// Every returned number is a statistic of the inputs — see
501    /// [`LandscapeStatistics`] for the formulas. Previously this ignored both
502    /// arguments and returned `complexity = 0.5`, `difficulty = 0.3`,
503    /// `confidence = 0.9`, `strategies = [Adaptive]` on every call.
504    ///
505    /// The recommended strategy set is derived from where the history sits in
506    /// the (complexity, difficulty) plane:
507    ///
508    /// | condition                          | strategy       |
509    /// |------------------------------------|----------------|
510    /// | difficulty high, complexity high   | `Exploratory`  |
511    /// | difficulty high, complexity low    | `Aggressive`   |
512    /// | difficulty low, complexity high    | `Conservative` |
513    /// | difficulty low, complexity low     | `Exploitative` |
514    /// | anything in between                | `Adaptive`     |
515    ///
516    /// # Errors
517    /// Returns `Err` when both histories are empty — there is nothing to
518    /// analyze, and answering with neutral constants is what this method used to
519    /// do wrong.
520    fn analyze(
521        &mut self,
522        gradient_history: &[Array1<T>],
523        loss_history: &[T],
524    ) -> Result<LandscapeAnalysis<T>> {
525        if gradient_history.is_empty() && loss_history.is_empty() {
526            return Err(crate::error::OptimError::InsufficientData(
527                "landscape analysis needs at least one gradient or loss sample".to_string(),
528            ));
529        }
530
531        let stats = LandscapeStatistics::from_history(gradient_history, loss_history);
532        let complexity = stats.complexity();
533        let difficulty = stats.difficulty();
534        let confidence = stats.analysis_confidence(self.confidence_horizon);
535
536        // Keep the (previously write-only) feature summaries in sync with the
537        // statistics that were actually measured.
538        self.landscape_features.smoothness =
539            scirs2_core::numeric::NumCast::from(1.0 - stats.loss_roughness)
540                .unwrap_or_else(|| T::zero());
541        self.landscape_features.multimodality =
542            scirs2_core::numeric::NumCast::from(stats.reversal_rate).unwrap_or_else(|| T::zero());
543        self.landscape_features.noise_level =
544            scirs2_core::numeric::NumCast::from(stats.gradient_norm_cv)
545                .unwrap_or_else(|| T::zero());
546        self.complexity_estimator.computational_complexity =
547            scirs2_core::numeric::NumCast::from(complexity).unwrap_or_else(|| T::zero());
548        self.complexity_estimator.generalization_complexity =
549            scirs2_core::numeric::NumCast::from(difficulty).unwrap_or_else(|| T::zero());
550
551        let high = 0.6;
552        let low = 0.4;
553        let strategy = match (difficulty >= high, complexity >= high) {
554            (true, true) => OptimizationStrategy::Exploratory,
555            (true, false) if complexity <= low => OptimizationStrategy::Aggressive,
556            (false, true) if difficulty <= low => OptimizationStrategy::Conservative,
557            _ if difficulty <= low && complexity <= low => OptimizationStrategy::Exploitative,
558            _ => OptimizationStrategy::Adaptive,
559        };
560
561        self.last_statistics = stats;
562
563        Ok(LandscapeAnalysis {
564            complexity: scirs2_core::numeric::NumCast::from(complexity)
565                .unwrap_or_else(|| T::zero()),
566            difficulty: scirs2_core::numeric::NumCast::from(difficulty)
567                .unwrap_or_else(|| T::zero()),
568            recommended_strategies: vec![strategy],
569            confidence: scirs2_core::numeric::NumCast::from(confidence)
570                .unwrap_or_else(|| T::zero()),
571        })
572    }
573
574    /// Statistics behind the most recent `Self::analyze` call.
575    pub fn last_statistics(&self) -> &LandscapeStatistics {
576        &self.last_statistics
577    }
578}
579/// Analysis result container
580#[derive(Debug, Clone)]
581pub struct AnalysisResult<
582    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
583> {
584    /// Complexity score
585    complexity_score: T,
586}
587/// Adaptation strategies
588#[derive(Debug, Clone, Copy)]
589pub enum AdaptationStrategy {
590    /// Gradual adaptation
591    Gradual,
592    /// Rapid adaptation
593    Rapid,
594    /// Conservative adaptation
595    Conservative,
596    /// Aggressive adaptation
597    Aggressive,
598    /// Learned adaptation
599    Learned,
600}
601/// Configuration for adaptive enhancements
602#[derive(Debug, Clone)]
603pub struct AdaptiveConfig<
604    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
605> {
606    /// Enable adaptive sequence length
607    pub adaptive_sequence_length: bool,
608    /// Maximum sequence length
609    pub max_sequence_length: usize,
610    /// Minimum sequence length
611    pub min_sequence_length: usize,
612    /// Attention sparsity threshold
613    pub attention_sparsity_threshold: T,
614    /// Memory budget (MB)
615    pub memory_budget: usize,
616    /// Enable dynamic head pruning
617    pub dynamic_head_pruning: bool,
618    /// Enable layer adaptation
619    pub layer_adaptation: bool,
620    /// Landscape analysis frequency
621    pub landscape_analysis_frequency: usize,
622    /// Performance prediction horizon
623    pub prediction_horizon: usize,
624    /// Adaptation learning rate
625    pub adaptation_lr: T,
626}
627// NOTE: `PredictionCache` used to be declared here as a `HashMap` plus a
628// `hit_rate` and a `capacity` that were never read or updated. The real,
629// capacity-enforcing, hit-rate-tracking cache lives in
630// [`crate::adaptive::predictor::PredictionCache`] and is re-exported by
631// `adaptive::mod`.
632/// Attention optimization result
633#[derive(Debug, Clone)]
634pub struct AttentionOptimization<
635    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
636> {
637    /// Optimized attention patterns
638    pub attention_patterns: Array3<T>,
639    /// Sparsity level achieved
640    pub sparsitylevel: T,
641    /// Memory savings
642    pub memory_savings: usize,
643    /// Computational speedup
644    pub computational_speedup: T,
645}
646/// Performance prediction result
647#[derive(Debug)]
648pub struct PerformancePrediction<
649    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
650> {
651    /// Predicted convergence improvement
652    pub convergence_improvement: T,
653    /// Predicted final performance
654    pub final_performance: T,
655    /// Prediction confidence
656    pub confidence: T,
657    /// Uncertainty estimate
658    pub uncertainty: T,
659}
660/// Uncertainty estimation methods
661#[derive(Debug, Clone, Copy)]
662pub enum UncertaintyMethod {
663    /// Monte Carlo dropout
664    MonteCarloDropout,
665    /// Bayesian neural networks
666    BayesianNN,
667    /// Ensemble methods
668    Ensemble,
669    /// Variational inference
670    VariationalInference,
671}
672/// Optimization strategies
673#[derive(Debug, Clone, Copy)]
674pub enum OptimizationStrategy {
675    Conservative,
676    Aggressive,
677    Adaptive,
678    Exploratory,
679    Exploitative,
680}
681/// Memory usage tracker
682#[derive(Debug)]
683pub struct MemoryUsageTracker {
684    /// Current memory usage (MB)
685    current_usage: usize,
686    /// Peak memory usage
687    peak_usage: usize,
688    /// Memory budget
689    budget: usize,
690}
691impl MemoryUsageTracker {
692    /// Tracker with a caller-supplied budget (from
693    /// `AdaptiveConfig::memory_budget`); `0` falls back to 8192 MB.
694    fn with_budget(budget: usize) -> Self {
695        Self {
696            current_usage: 0,
697            peak_usage: 0,
698            budget: if budget == 0 { 8192 } else { budget },
699        }
700    }
701
702    /// Configured budget.
703    pub fn budget(&self) -> usize {
704        self.budget
705    }
706
707    /// Peak usage observed so far.
708    pub fn peak_usage(&self) -> usize {
709        self.peak_usage
710    }
711}
712/// Architecture search space
713#[derive(Debug, Clone)]
714pub struct ArchitectureSearchSpace {
715    /// Layer count range
716    pub(super) layer_count_range: (usize, usize),
717    /// Attention head options
718    pub(super) attention_head_options: Vec<usize>,
719}
720/// Convergence metrics for tracking optimization progress
721#[derive(Debug, Clone)]
722pub struct ConvergenceMetrics<
723    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
724> {
725    /// Rate of convergence
726    pub convergence_rate: T,
727    /// Stability measure
728    pub stability_measure: T,
729    /// Plateau detection flag
730    pub plateau_detection: bool,
731    /// Oscillation measure
732    pub oscillation_measure: T,
733}
734/// Adaptive Transformer Enhancement System
735pub struct AdaptiveTransformerEnhancement<
736    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
737> {
738    /// Adaptive sequence processor
739    sequence_processor: AdaptiveSequenceProcessor<T>,
740    /// Memory-efficient attention manager
741    attention_manager: MemoryEfficientAttentionManager<T>,
742    /// Dynamic architecture adapter
743    architecture_adapter: DynamicArchitectureAdapter<T>,
744    /// Optimization landscape analyzer
745    landscape_analyzer: OptimizationLandscapeAnalyzer<T>,
746    /// Performance predictor
747    performance_predictor: TransformerPerformancePredictor<T>,
748    /// Adaptive configuration
749    adaptive_config: AdaptiveConfig<T>,
750    /// Exponential moving average of per-parameter squared gradients.
751    ///
752    /// This is what makes the adaptive learning rate genuinely per-parameter:
753    /// the previous implementation returned `base_lr · scale · 1.1` for even
754    /// indices and `base_lr · scale · 0.9` for odd ones, i.e. index-parity noise
755    /// that carried no information about the parameter at all.
756    grad_second_moment: Array1<T>,
757    /// Number of `enhanced_optimize_step` calls, used to bias-correct the
758    /// second-moment estimate and to honour
759    /// `AdaptiveConfig::landscape_analysis_frequency`.
760    step_count: usize,
761    /// Most recent landscape analysis, reused between analysis refreshes.
762    cached_landscape: Option<LandscapeAnalysis<T>>,
763}
764impl<
765        T: Float
766            + Debug
767            + scirs2_core::ndarray::ScalarOperand
768            + Send
769            + Sync
770            + 'static
771            + std::iter::Sum
772            + scirs2_core::numeric::FromPrimitive,
773    > AdaptiveTransformerEnhancement<T>
774{
775    /// Enhance a transformer optimizer for the current optimization task.
776    ///
777    /// This now *acts on* `transformer` instead of ignoring it: after deriving
778    /// the architecture adaptation, the adapted configuration is pushed into the
779    /// optimizer via [`TransformerOptimizer::apply_architecture_config`]. That
780    /// call rebuilds the optimizer's transformer stack when a dimension actually
781    /// changed, and is a no-op when the proposal matches the live configuration.
782    ///
783    /// The returned [`EnhancementResult::architecture_adaptation`] reports the
784    /// proposal; whether it was applied is observable through the optimizer's
785    /// own configuration.
786    ///
787    /// # Errors
788    /// Propagates analysis errors (an empty history is an error, not a set of
789    /// neutral constants) and any error from reconfiguring the optimizer.
790    pub fn enhance_optimizer(
791        &mut self,
792        transformer: &mut TransformerOptimizer<T>,
793        gradient_history: &[Array1<T>],
794        losshistory: &[T],
795    ) -> Result<EnhancementResult<T>> {
796        // Adapt *this* optimizer's architecture, not a default one.
797        self.architecture_adapter.sync_with(transformer.config());
798
799        let landscape_analysis = self
800            .landscape_analyzer
801            .analyze(gradient_history, losshistory)?;
802        let sequence_adaptation = self
803            .sequence_processor
804            .adapt_to_landscape(&landscape_analysis)?;
805        let attention_optimization = self
806            .attention_manager
807            .optimize_attention(&landscape_analysis)?;
808        let architecture_adaptation = self.architecture_adapter.adapt_architecture(
809            &landscape_analysis,
810            &sequence_adaptation,
811            &attention_optimization,
812        )?;
813        let performance_prediction = self
814            .performance_predictor
815            .predict_improvement(&landscape_analysis, &architecture_adaptation)?;
816
817        // F22: actually push the adaptation into the optimizer we were handed, and
818        // report what that did — a `Rebuilt` outcome discarded every learned
819        // parameter, and a caller that just finished training must be able to see
820        // that instead of discovering it from degraded results.
821        let architecture_update =
822            transformer.apply_architecture_config(&architecture_adaptation.adapted_config)?;
823
824        let convergence_metrics = self.calculate_convergence_metrics(losshistory);
825        Ok(EnhancementResult {
826            architecture_update,
827            sequence_adaptation,
828            attention_optimization,
829            architecture_adaptation,
830            performance_prediction,
831            landscape_analysis,
832            convergence_metrics,
833        })
834    }
835}
836impl<
837        T: Float
838            + Debug
839            + scirs2_core::ndarray::ScalarOperand
840            + Send
841            + Sync
842            + 'static
843            + std::iter::Sum,
844    > AdaptiveTransformerEnhancement<T>
845{
846    pub fn new(config: AdaptiveConfig<T>) -> Result<Self> {
847        Ok(Self {
848            sequence_processor: AdaptiveSequenceProcessor::new(&config)?,
849            attention_manager: MemoryEfficientAttentionManager::new(&config)?,
850            architecture_adapter: DynamicArchitectureAdapter::new(&config)?,
851            landscape_analyzer: OptimizationLandscapeAnalyzer::new(&config)?,
852            performance_predictor: TransformerPerformancePredictor::new(&config)?,
853            adaptive_config: config,
854            grad_second_moment: Array1::zeros(0),
855            step_count: 0,
856            cached_landscape: None,
857        })
858    }
859
860    /// Read-only access to the configuration this enhancement was built with.
861    pub fn config(&self) -> &AdaptiveConfig<T> {
862        &self.adaptive_config
863    }
864
865    /// Fit the performance predictor on observed outcomes.
866    ///
867    /// See [`TransformerPerformancePredictor::train`]. Until this is called,
868    /// [`EnhancementResult::performance_prediction`] honestly reports zero means
869    /// with unit uncertainty.
870    pub fn train_performance_predictor(
871        &mut self,
872        samples: &[PredictorSample],
873    ) -> Result<PredictorFitReport> {
874        self.performance_predictor.train(samples)
875    }
876
877    /// Whether the performance predictor has been fitted.
878    pub fn predictor_is_trained(&self) -> bool {
879        self.performance_predictor.is_trained()
880    }
881
882    /// Current per-parameter second-moment estimates (empty before the first
883    /// [`Self::enhanced_optimize_step`]).
884    pub fn gradient_second_moment(&self) -> &Array1<T> {
885        &self.grad_second_moment
886    }
887    /// Enhanced optimization step with adaptive features.
888    ///
889    /// The landscape is re-analyzed at most once every
890    /// `AdaptiveConfig::landscape_analysis_frequency` steps and reused in
891    /// between; that field previously configured nothing and the (constant)
892    /// analysis was recomputed on every call.
893    pub fn enhanced_optimize_step(
894        &mut self,
895        parameters: &mut Array1<T>,
896        gradients: &Array1<T>,
897        losshistory: &[T],
898        gradient_history: &[Array1<T>],
899    ) -> Result<EnhancementResult<T>> {
900        let frequency = self.adaptive_config.landscape_analysis_frequency.max(1);
901        let refresh = self.cached_landscape.is_none() || self.step_count.is_multiple_of(frequency);
902        let landscape = if refresh {
903            let fresh = self
904                .landscape_analyzer
905                .analyze(gradient_history, losshistory)?;
906            self.cached_landscape = Some(LandscapeAnalysis {
907                complexity: fresh.complexity,
908                difficulty: fresh.difficulty,
909                recommended_strategies: fresh.recommended_strategies.clone(),
910                confidence: fresh.confidence,
911            });
912            fresh
913        } else {
914            match &self.cached_landscape {
915                Some(cached) => LandscapeAnalysis {
916                    complexity: cached.complexity,
917                    difficulty: cached.difficulty,
918                    recommended_strategies: cached.recommended_strategies.clone(),
919                    confidence: cached.confidence,
920                },
921                None => self
922                    .landscape_analyzer
923                    .analyze(gradient_history, losshistory)?,
924            }
925        };
926        let sequence_adaptation = self.sequence_processor.adapt_to_landscape(&landscape)?;
927        let attention_optimization = self.attention_manager.optimize_attention(&landscape)?;
928        let architecture_adaptation = self.architecture_adapter.adapt_architecture(
929            &landscape,
930            &sequence_adaptation,
931            &attention_optimization,
932        )?;
933        let performance_prediction = self
934            .performance_predictor
935            .predict_improvement(&landscape, &architecture_adaptation)?;
936        self.apply_adaptive_updates(
937            parameters,
938            gradients,
939            &sequence_adaptation,
940            &attention_optimization,
941            &architecture_adaptation,
942        )?;
943        Ok(EnhancementResult {
944            landscape_analysis: landscape,
945            sequence_adaptation,
946            attention_optimization,
947            architecture_adaptation,
948            performance_prediction,
949            convergence_metrics: self.calculate_convergence_metrics(losshistory),
950            // This entry point updates a caller-owned parameter vector, not an
951            // optimizer, so no architecture was touched.
952            architecture_update: ArchitectureUpdate::Unchanged,
953        })
954    }
955    /// Apply adaptive updates to parameters
956    /// Apply the adaptive update to `parameters`.
957    ///
958    /// Two things changed here. First, the composite scale is now a *bounded*
959    /// blend rather than a raw triple product: `computational_speedup` can be in
960    /// the tens, so multiplying three unbounded factors and dividing by 3 could
961    /// produce a scale far above 1 and diverge. Second, the per-parameter rate
962    /// is a real RMSProp-style rate over the observed gradient second moment
963    /// (see [`Self::calculate_adaptive_learning_rate`]) instead of index parity.
964    ///
965    /// # Errors
966    /// Returns `Err` when `parameters` and `gradients` have different lengths —
967    /// previously `zip` silently truncated to the shorter one, leaving the tail
968    /// of a longer parameter vector un-updated.
969    fn apply_adaptive_updates(
970        &mut self,
971        parameters: &mut Array1<T>,
972        gradients: &Array1<T>,
973        sequence_adaptation: &SequenceAdaptation<T>,
974        attention_optimization: &AttentionOptimization<T>,
975        architecture_adaptation: &ArchitectureAdaptation<T>,
976    ) -> Result<()> {
977        if parameters.len() != gradients.len() {
978            return Err(crate::error::OptimError::ComputationError(format!(
979                "parameter/gradient length mismatch: {} vs {}",
980                parameters.len(),
981                gradients.len()
982            )));
983        }
984
985        // Bound each factor to [0, 1] before averaging so the composite scale is
986        // itself in [0, 1] and cannot blow the step up.
987        let squash = |v: T| -> T {
988            let x = v.to_f64().unwrap_or(0.0).abs();
989            scirs2_core::numeric::NumCast::from(x / (1.0 + x)).unwrap_or_else(|| T::zero())
990        };
991        let three: T = scirs2_core::numeric::NumCast::from(3.0).unwrap_or_else(|| T::one());
992        let combined_scale = (squash(sequence_adaptation.efficiency_gain)
993            + squash(attention_optimization.computational_speedup)
994            + squash(architecture_adaptation.expected_improvement))
995            / three;
996
997        // Second-moment accumulator tracks the parameter vector's width.
998        if self.grad_second_moment.len() != gradients.len() {
999            self.grad_second_moment = Array1::zeros(gradients.len());
1000        }
1001        let beta: T = scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero());
1002        let one_minus_beta = T::one() - beta;
1003        for (slot, &g) in self.grad_second_moment.iter_mut().zip(gradients.iter()) {
1004            *slot = beta * *slot + one_minus_beta * g * g;
1005        }
1006
1007        self.step_count += 1;
1008        for (i, (param, grad)) in parameters.iter_mut().zip(gradients.iter()).enumerate() {
1009            let adaptive_lr = self.calculate_adaptive_learning_rate(i, combined_scale)?;
1010            *param = *param - adaptive_lr * *grad;
1011        }
1012        Ok(())
1013    }
1014
1015    /// Per-parameter learning rate for parameter `param_index`.
1016    ///
1017    /// RMSProp form: `lr_i = base_lr · scale / (sqrt(v̂_i) + ε)` where `v̂_i` is
1018    /// the bias-corrected exponential moving average of `g_i²` maintained by
1019    /// [`Self::apply_adaptive_updates`]. Parameters with a persistently large
1020    /// gradient therefore get a *smaller* step, which is the whole point of a
1021    /// per-parameter rate.
1022    ///
1023    /// `base_lr` is `AdaptiveConfig::adaptation_lr` — the config field that used
1024    /// to be ignored in favour of a hardcoded `0.001`.
1025    ///
1026    /// # Errors
1027    /// Returns `Err` when `param_index` is outside the tracked width.
1028    fn calculate_adaptive_learning_rate(&self, param_index: usize, basescale: T) -> Result<T> {
1029        if param_index >= self.grad_second_moment.len() {
1030            return Err(crate::error::OptimError::ComputationError(format!(
1031                "parameter index {param_index} outside the tracked width {}",
1032                self.grad_second_moment.len()
1033            )));
1034        }
1035        let base_lr = self.adaptive_config.adaptation_lr;
1036        let epsilon: T = scirs2_core::numeric::NumCast::from(1e-8).unwrap_or_else(|| T::zero());
1037
1038        // Bias correction for the EMA warm-up (Kingma & Ba 2015, eq. 2).
1039        let beta = 0.9_f64;
1040        let correction = 1.0 - beta.powi(self.step_count.max(1) as i32);
1041        let correction_t: T = scirs2_core::numeric::NumCast::from(correction.max(f64::EPSILON))
1042            .unwrap_or_else(|| T::one());
1043        let v_hat = self.grad_second_moment[param_index] / correction_t;
1044
1045        Ok(base_lr * basescale / (v_hat.sqrt() + epsilon))
1046    }
1047    /// Calculate convergence metrics
1048    fn calculate_convergence_metrics(&self, losshistory: &[T]) -> ConvergenceMetrics<T> {
1049        if losshistory.len() < 2 {
1050            return ConvergenceMetrics {
1051                convergence_rate: T::zero(),
1052                stability_measure: T::zero(),
1053                plateau_detection: false,
1054                oscillation_measure: T::zero(),
1055            };
1056        }
1057        let recent_losses = &losshistory[losshistory.len().saturating_sub(10)..];
1058        let convergence_rate = if recent_losses.len() >= 2 {
1059            let initial = recent_losses[0];
1060            let final_loss = recent_losses[recent_losses.len() - 1];
1061            if initial > T::zero() {
1062                (initial - final_loss) / initial
1063            } else {
1064                T::zero()
1065            }
1066        } else {
1067            T::zero()
1068        };
1069        // `losshistory.len() >= 2` was checked above and `recent_losses` is a
1070        // non-empty suffix of it, so the count is positive.
1071        let sample_count: T = cast_scalar(recent_losses.len()).unwrap_or_else(|_| T::one());
1072        let mean_loss = recent_losses.iter().cloned().sum::<T>() / sample_count;
1073        let variance = recent_losses
1074            .iter()
1075            .map(|&loss| {
1076                let diff = loss - mean_loss;
1077                diff * diff
1078            })
1079            .sum::<T>()
1080            / sample_count;
1081        let stability_measure = T::one() / (T::one() + variance);
1082        let plateau_threshold =
1083            scirs2_core::numeric::NumCast::from(0.001).unwrap_or_else(|| T::zero());
1084        let plateau_detection = convergence_rate.abs() < plateau_threshold;
1085        let mut oscillation_sum = T::zero();
1086        for i in 1..recent_losses.len() {
1087            oscillation_sum = oscillation_sum + (recent_losses[i] - recent_losses[i - 1]).abs();
1088        }
1089        let oscillation_measure = match cast_scalar::<T, _>(recent_losses.len() - 1) {
1090            Ok(gaps) if gaps > T::zero() => oscillation_sum / gaps,
1091            _ => T::zero(),
1092        };
1093        ConvergenceMetrics {
1094            convergence_rate,
1095            stability_measure,
1096            plateau_detection,
1097            oscillation_measure,
1098        }
1099    }
1100    /// Update internal state based on optimization progress
1101    pub fn update_enhancement_state(
1102        &mut self,
1103        enhancement_result: &EnhancementResult<T>,
1104    ) -> Result<()> {
1105        let cache_key = format!(
1106            "analysis_{}",
1107            enhancement_result
1108                .landscape_analysis
1109                .complexity
1110                .to_f64()
1111                .unwrap_or(0.0)
1112        );
1113        self.landscape_analyzer.analysis_cache.insert(
1114            cache_key,
1115            AnalysisResult {
1116                complexity_score: enhancement_result.landscape_analysis.complexity,
1117            },
1118        );
1119        let performance = ArchitecturePerformance {
1120            final_performance: T::one() - enhancement_result.performance_prediction.uncertainty,
1121        };
1122        self.architecture_adapter
1123            .performance_history
1124            .push_back(performance);
1125        if self.architecture_adapter.performance_history.len() > 100 {
1126            self.architecture_adapter.performance_history.pop_front();
1127        }
1128        Ok(())
1129    }
1130    /// Get enhancement statistics
1131    pub fn get_enhancement_statistics(&self) -> EnhancementStatistics<T> {
1132        let avg_complexity = if !self.landscape_analyzer.analysis_cache.is_empty() {
1133            let sum: T = self
1134                .landscape_analyzer
1135                .analysis_cache
1136                .values()
1137                .map(|result| result.complexity_score)
1138                .sum();
1139            match cast_scalar::<T, _>(self.landscape_analyzer.analysis_cache.len()) {
1140                Ok(count) if count > T::zero() => sum / count,
1141                _ => T::zero(),
1142            }
1143        } else {
1144            scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero())
1145        };
1146        let avg_performance = if !self.architecture_adapter.performance_history.is_empty() {
1147            let sum: T = self
1148                .architecture_adapter
1149                .performance_history
1150                .iter()
1151                .map(|perf| perf.final_performance)
1152                .sum();
1153            match cast_scalar::<T, _>(self.architecture_adapter.performance_history.len()) {
1154                Ok(count) if count > T::zero() => sum / count,
1155                _ => T::zero(),
1156            }
1157        } else {
1158            scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero())
1159        };
1160        EnhancementStatistics {
1161            total_enhancements: self.landscape_analyzer.analysis_cache.len(),
1162            average_complexity: avg_complexity,
1163            average_performance: avg_performance,
1164            memory_efficiency: scirs2_core::numeric::NumCast::from(0.8)
1165                .unwrap_or_else(|| T::zero()),
1166            adaptation_success_rate: scirs2_core::numeric::NumCast::from(0.85)
1167                .unwrap_or_else(|| T::zero()),
1168        }
1169    }
1170}
1171/// Activation function types
1172#[derive(Debug, Clone, Copy)]
1173pub enum ActivationType {
1174    ReLU,
1175    GELU,
1176    Swish,
1177    Mish,
1178    ELU,
1179    Tanh,
1180}