1use scirs2_core::ndarray::{Array1, Array2};
8use scirs2_core::numeric::Float;
9use std::collections::{HashMap, VecDeque};
10use std::fmt::Debug;
11use std::time::{Duration, Instant};
12
13use super::OptimizerState;
14use crate::error::{OptimError, Result};
15
16pub mod prototypical;
17
18pub use prototypical::{activate, xavier_encoder_layer};
19
20pub struct FewShotLearningSystem<T: Float + Debug + Send + Sync + 'static> {
22 base_optimizer: Box<dyn FewShotOptimizer<T>>,
24
25 prototype_network: PrototypicalNetwork<T>,
27
28 memory_bank: EpisodicMemoryBank<T>,
30
31 fast_adaptation: FastAdaptationEngine<T>,
33
34 performance_tracker: FewShotPerformanceTracker<T>,
36}
37
38pub trait FewShotOptimizer<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
40 fn adapt_few_shot(
42 &mut self,
43 support_set: &SupportSet<T>,
44 query_set: &QuerySet<T>,
45 adaptation_config: &AdaptationConfig,
46 ) -> Result<AdaptationResult<T>>;
47
48 fn get_task_representation(&self, taskdata: &TaskData<T>) -> Result<Array1<T>>;
50
51 fn compute_adaptation_loss(
53 &self,
54 support_set: &SupportSet<T>,
55 query_set: &QuerySet<T>,
56 ) -> Result<T>;
57
58 fn update_meta_parameters(&mut self, metagradients: &MetaGradients<T>) -> Result<()>;
60
61 fn get_transfer_state(&self) -> TransferState<T>;
63
64 fn load_transfer_state(&mut self, state: TransferState<T>) -> Result<()>;
66}
67
68#[derive(Debug, Clone)]
70pub struct SupportSet<T: Float + Debug + Send + Sync + 'static> {
71 pub examples: Vec<SupportExample<T>>,
73
74 pub task_metadata: TaskMetadata,
76
77 pub statistics: SupportSetStatistics<T>,
79
80 pub temporal_order: Option<Vec<usize>>,
82}
83
84#[derive(Debug, Clone)]
86pub struct SupportExample<T: Float + Debug + Send + Sync + 'static> {
87 pub features: Array1<T>,
89
90 pub target: T,
92
93 pub weight: T,
95
96 pub context: HashMap<String, T>,
98
99 pub metadata: ExampleMetadata,
101}
102
103#[derive(Debug, Clone)]
105pub struct QuerySet<T: Float + Debug + Send + Sync + 'static> {
106 pub examples: Vec<QueryExample<T>>,
108
109 pub statistics: QuerySetStatistics<T>,
111
112 pub eval_metrics: Vec<EvaluationMetric>,
114}
115
116#[derive(Debug, Clone)]
118pub struct QueryExample<T: Float + Debug + Send + Sync + 'static> {
119 pub features: Array1<T>,
121
122 pub true_target: Option<T>,
124
125 pub weight: T,
127
128 pub context: HashMap<String, T>,
130}
131
132#[derive(Debug, Clone)]
134pub struct TaskData<T: Float + Debug + Send + Sync + 'static> {
135 pub task_id: String,
137
138 pub support_set: SupportSet<T>,
140
141 pub query_set: QuerySet<T>,
143
144 pub task_params: HashMap<String, T>,
146
147 pub domain_info: DomainInfo,
149}
150
151#[derive(Debug, Clone)]
153pub struct DomainInfo {
154 pub domain_type: DomainType,
156
157 pub characteristics: DomainCharacteristics,
159
160 pub difficulty_level: DifficultyLevel,
162
163 pub constraints: Vec<DomainConstraint>,
165}
166
167#[derive(Debug, Clone, Copy)]
169pub enum DomainType {
170 ComputerVision,
171 NaturalLanguageProcessing,
172 ReinforcementLearning,
173 TimeSeriesForecasting,
174 ScientificComputing,
175 Optimization,
176 ControlSystems,
177 GamePlaying,
178 Robotics,
179 Healthcare,
180}
181
182#[derive(Debug, Clone)]
184pub struct DomainCharacteristics {
185 pub input_dim: usize,
187
188 pub output_dim: usize,
190
191 pub temporal: bool,
193
194 pub stochasticity: f64,
196
197 pub noise_level: f64,
199
200 pub sparsity: f64,
202}
203
204#[derive(Debug, Clone, Copy)]
206pub enum DifficultyLevel {
207 Trivial,
208 Easy,
209 Medium,
210 Hard,
211 Expert,
212 Extreme,
213}
214
215#[derive(Debug, Clone)]
217pub struct DomainConstraint {
218 pub constraint_type: ConstraintType,
220
221 pub description: String,
223
224 pub enforcement: ConstraintEnforcement,
226}
227
228#[derive(Debug, Clone, Copy)]
230pub enum ConstraintType {
231 ResourceLimit,
232 TemporalConstraint,
233 AccuracyRequirement,
234 LatencyRequirement,
235 MemoryConstraint,
236 EnergyConstraint,
237 SafetyConstraint,
238}
239
240#[derive(Debug, Clone, Copy)]
242pub enum ConstraintEnforcement {
243 Hard,
244 Soft,
245 Advisory,
246}
247
248#[derive(Debug, Clone)]
250pub struct AdaptationConfig {
251 pub adaptation_steps: usize,
253
254 pub adaptation_lr: f64,
256
257 pub strategy: AdaptationStrategyType,
259
260 pub early_stopping: Option<EarlyStoppingConfig>,
262
263 pub regularization: RegularizationConfig,
265
266 pub resource_constraints: ResourceConstraints,
268}
269
270#[derive(Debug, Clone, Copy)]
272pub enum AdaptationStrategyType {
273 MAML,
275
276 FOMAML,
278
279 Prototypical,
281
282 Matching,
284
285 Relation,
287
288 MetaSGD,
290
291 LearnedOptimizer,
293
294 GradientBased,
296
297 MemoryAugmented,
299}
300
301#[derive(Debug, Clone)]
303pub struct EarlyStoppingConfig {
304 pub patience: usize,
306
307 pub min_improvement: f64,
309
310 pub validation_frequency: usize,
312}
313
314#[derive(Debug, Clone)]
316pub struct RegularizationConfig {
317 pub l2_strength: f64,
319
320 pub dropout_rate: f64,
322
323 pub gradient_clip: Option<f64>,
325
326 pub task_regularization: HashMap<String, f64>,
328}
329
330#[derive(Debug, Clone)]
332pub struct ResourceConstraints {
333 pub max_time: Duration,
335
336 pub max_memory_mb: usize,
338
339 pub max_compute_budget: f64,
341}
342
343#[derive(Debug, Clone)]
345pub struct AdaptationResult<T: Float + Debug + Send + Sync + 'static> {
346 pub adapted_state: OptimizerState<T>,
348
349 pub performance: AdaptationPerformance<T>,
351
352 pub task_representation: Array1<T>,
354
355 pub adaptation_trajectory: Vec<AdaptationStep<T>>,
357
358 pub resource_usage: ResourceUsage<T>,
360}
361
362#[derive(Debug, Clone)]
364pub struct AdaptationPerformance<T: Float + Debug + Send + Sync + 'static> {
365 pub query_performance: T,
367
368 pub support_performance: T,
370
371 pub adaptation_speed: usize,
373
374 pub final_loss: T,
376
377 pub improvement: T,
379
380 pub stability: T,
382}
383
384#[derive(Debug, Clone)]
386pub struct AdaptationStep<T: Float + Debug + Send + Sync + 'static> {
387 pub step: usize,
389
390 pub loss: T,
392
393 pub performance: T,
395
396 pub gradient_norm: T,
398
399 pub step_time: Duration,
401}
402
403#[derive(Debug, Clone)]
405pub struct ResourceUsage<T: Float + Debug + Send + Sync + 'static> {
406 pub total_time: Duration,
408
409 pub peak_memory_mb: T,
411
412 pub compute_cost: T,
414
415 pub energy_consumption: T,
417}
418
419#[derive(Debug, Clone)]
421pub struct MetaGradients<T: Float + Debug + Send + Sync + 'static> {
422 pub param_gradients: HashMap<String, Array1<T>>,
424
425 pub lr_gradients: HashMap<String, T>,
427
428 pub arch_gradients: HashMap<String, Array1<T>>,
430
431 pub gradient_norm: T,
433}
434
435#[derive(Debug, Clone)]
437pub struct TransferState<T: Float + Debug + Send + Sync + 'static> {
438 pub representations: HashMap<String, Array1<T>>,
440
441 pub meta_parameters: HashMap<String, Array1<T>>,
443
444 pub task_embeddings: Array2<T>,
446
447 pub transfer_stats: TransferStatistics<T>,
449}
450
451#[derive(Debug, Clone)]
453pub struct TransferStatistics<T: Float + Debug + Send + Sync + 'static> {
454 pub source_performance: T,
456
457 pub target_performance: T,
459
460 pub transfer_efficiency: T,
462
463 pub steps_saved: usize,
465}
466
467pub struct PrototypicalNetwork<T: Float + Debug + Send + Sync + 'static> {
469 pub(super) encoder: EncoderNetwork<T>,
472
473 prototypes: HashMap<String, Prototype<T>>,
475
476 distance_metric: DistanceMetric,
478
479 parameters: PrototypicalNetworkParams<T>,
481}
482
483#[derive(Debug)]
485pub struct EncoderNetwork<T: Float + Debug + Send + Sync + 'static> {
486 layers: Vec<EncoderLayer<T>>,
488
489 activation: ActivationFunction,
491}
492
493#[derive(Debug)]
495pub struct EncoderLayer<T: Float + Debug + Send + Sync + 'static> {
496 pub weights: Array2<T>,
498
499 pub bias: Array1<T>,
501
502 pub layer_type: LayerType,
504}
505
506#[derive(Debug, Clone, Copy)]
508pub enum LayerType {
509 Linear,
510 Convolutional,
511 Recurrent,
512 Attention,
513 Residual,
514}
515
516#[derive(Debug, Clone, Copy)]
518pub enum ActivationFunction {
519 ReLU,
520 Tanh,
521 Sigmoid,
522 Swish,
523 GELU,
524 Mish,
525}
526
527#[derive(Debug, Clone)]
529pub struct Prototype<T: Float + Debug + Send + Sync + 'static> {
530 pub vector: Array1<T>,
532
533 pub confidence: T,
535
536 pub example_count: usize,
538
539 pub last_updated: std::time::SystemTime,
541
542 pub metadata: PrototypeMetadata,
544}
545
546#[derive(Debug, Clone)]
548pub struct PrototypeMetadata {
549 pub task_category: String,
551
552 pub domain: DomainType,
554
555 pub created_at: std::time::SystemTime,
557
558 pub update_count: usize,
560}
561
562#[derive(Debug, Clone, Copy)]
564pub enum DistanceMetric {
565 Euclidean,
566 Cosine,
567 Manhattan,
568 Mahalanobis,
569 Learned,
570}
571
572#[derive(Debug, Clone)]
574pub struct PrototypicalNetworkParams<T: Float + Debug + Send + Sync + 'static> {
575 pub embedding_dim: usize,
577
578 pub learning_rate: T,
580
581 pub temperature: T,
583
584 pub prototype_update_rate: T,
586}
587
588pub struct SupportSetManager<T: Float + Debug + Send + Sync + 'static> {
594 config: SupportSetManagerConfig,
596
597 _element: std::marker::PhantomData<T>,
598}
599
600#[derive(Debug, Clone)]
602pub struct SupportSetManagerConfig {
603 pub min_support_size: usize,
605
606 pub max_support_size: usize,
608
609 pub quality_threshold: f64,
611
612 pub enable_augmentation: bool,
614
615 pub cache_support_sets: bool,
617}
618
619pub trait AdaptationStrategy<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
621 fn adapt(
623 &mut self,
624 optimizer: &mut dyn FewShotOptimizer<T>,
625 task_data: &TaskData<T>,
626 config: &AdaptationConfig,
627 ) -> Result<AdaptationResult<T>>;
628
629 fn name(&self) -> &str;
631
632 fn parameters(&self) -> HashMap<String, f64>;
634
635 fn update_parameters(&mut self, params: HashMap<String, f64>) -> Result<()>;
637}
638
639pub struct TaskSimilarityCalculator<T: Float + Debug + Send + Sync + 'static> {
641 similarity_cache: HashMap<(String, String), T>,
643
644 config: SimilarityCalculatorConfig<T>,
646}
647
648pub trait SimilarityMetric<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
650 fn calculate_similarity(&self, task1: &TaskData<T>, task2: &TaskData<T>) -> Result<T>;
652
653 fn name(&self) -> &str;
655
656 fn weight(&self) -> T;
658}
659
660#[derive(Debug, Clone)]
662pub struct SimilarityCalculatorConfig<T: Float + Debug + Send + Sync + 'static> {
663 pub enable_caching: bool,
665
666 pub cache_size_limit: usize,
668
669 pub similarity_threshold: T,
671
672 pub use_metadata: bool,
674}
675
676pub struct EpisodicMemoryBank<T: Float + Debug + Send + Sync + 'static> {
678 episodes: VecDeque<MemoryEpisode<T>>,
680
681 config: MemoryBankConfig<T>,
683
684 usage_stats: MemoryUsageStats,
686}
687
688#[derive(Debug, Clone)]
690pub struct MemoryEpisode<T: Float + Debug + Send + Sync + 'static> {
691 pub episode_id: String,
693
694 pub task_data: TaskData<T>,
696
697 pub adaptation_result: AdaptationResult<T>,
699
700 pub timestamp: std::time::SystemTime,
702
703 pub metadata: EpisodeMetadata,
705
706 pub access_count: usize,
708}
709
710#[derive(Debug, Clone)]
712pub struct EpisodeMetadata {
713 pub difficulty: DifficultyLevel,
715
716 pub domain: DomainType,
718
719 pub success_rate: f64,
721
722 pub tags: Vec<String>,
724}
725
726#[derive(Debug, Clone)]
728pub struct MemoryBankConfig<T: Float + Debug + Send + Sync + 'static> {
729 pub max_memory_size: usize,
731
732 pub eviction_policy: EvictionPolicy,
734
735 pub similarity_threshold: T,
737
738 pub enable_compression: bool,
740}
741
742#[derive(Debug, Clone, Copy)]
744pub enum EvictionPolicy {
745 LRU, LFU, Performance, Age, Random,
750}
751
752#[derive(Debug, Clone)]
754pub struct MemoryBankStats<T: Float + Debug + Send + Sync + 'static> {
755 pub count: usize,
757
758 pub avg_performance: T,
760
761 pub capacity_used: f64,
763
764 pub total_capacity: usize,
766}
767
768#[derive(Debug, Clone)]
770pub struct MemoryUsageStats {
771 pub total_episodes: usize,
773
774 pub memory_utilization: f64,
776
777 pub hit_rate: f64,
779
780 pub avg_retrieval_time: Duration,
782}
783
784pub struct FastAdaptationEngine<T: Float + Debug + Send + Sync + 'static> {
786 algorithms: Vec<Box<dyn FastAdaptationAlgorithm<T>>>,
788
789 config: FastAdaptationConfig,
791}
792
793pub trait FastAdaptationAlgorithm<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
795 fn adapt_fast(
797 &mut self,
798 optimizer: &mut dyn FewShotOptimizer<T>,
799 task_data: &TaskData<T>,
800 target_performance: Option<T>,
801 ) -> Result<AdaptationResult<T>>;
802
803 fn estimate_adaptation_time(&self, taskdata: &TaskData<T>) -> Duration;
805
806 fn name(&self) -> &str;
808}
809
810#[derive(Debug, Clone)]
812pub struct FastAdaptationConfig {
813 pub enable_caching: bool,
815
816 pub enable_prediction: bool,
818
819 pub max_adaptation_time: Duration,
821
822 pub _performance_threshold: f64,
824}
825
826pub struct FewShotPerformanceTracker<T: Float + Debug + Send + Sync + 'static> {
828 performance_history: VecDeque<PerformanceRecord<T>>,
830
831 config: TrackingConfig,
833
834 stats: PerformanceStats<T>,
836}
837
838#[derive(Debug, Clone)]
840pub struct PerformanceRecord<T: Float + Debug + Send + Sync + 'static> {
841 pub task_id: String,
843
844 pub performance: T,
846
847 pub adaptation_time: Duration,
849
850 pub strategy_used: String,
852
853 pub timestamp: std::time::SystemTime,
855
856 pub additional_metrics: HashMap<String, T>,
858}
859
860pub trait PerformanceMetric<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
862 fn calculate(&self, records: &[PerformanceRecord<T>]) -> Result<T>;
864
865 fn name(&self) -> &str;
867
868 fn higher_is_better(&self) -> bool;
870}
871
872#[derive(Debug, Clone)]
874pub struct TrackingConfig {
875 pub max_history_size: usize,
877
878 pub update_frequency: Duration,
880
881 pub detailed_tracking: bool,
883
884 pub export_results: bool,
886}
887
888#[derive(Debug)]
890pub struct PerformanceStats<T: Float + Debug + Send + Sync + 'static> {
891 pub best_performance: T,
893
894 pub average_performance: T,
896
897 pub performance_variance: T,
899
900 pub improvement_rate: T,
902
903 pub success_rate: T,
905}
906
907impl<T: Float + Debug + Send + Sync + 'static> FewShotLearningSystem<T> {
909 pub fn new(
911 base_optimizer: Box<dyn FewShotOptimizer<T>>,
912 config: FewShotConfig<T>,
913 ) -> Result<Self> {
914 Ok(Self {
915 base_optimizer,
916 prototype_network: PrototypicalNetwork::new(config.prototype_config)?,
917 memory_bank: EpisodicMemoryBank::new(config.memory_config)?,
918 fast_adaptation: FastAdaptationEngine::new(config.adaptation_config)?,
919 performance_tracker: FewShotPerformanceTracker::new(config.tracking_config)?,
920 })
921 }
922
923 pub fn learn_few_shot(
925 &mut self,
926 task_data: TaskData<T>,
927 adaptation_config: AdaptationConfig,
928 ) -> Result<AdaptationResult<T>> {
929 let _start_time = Instant::now();
931
932 let task_representation = self.prototype_network.encode_task(&task_data)?;
934
935 let similar_tasks = self.memory_bank.retrieve_similar(&task_data, 5)?;
937
938 let strategy = self.select_adaptation_strategy(&task_data, &similar_tasks)?;
940
941 let mut adaptation_result = self.fast_adaptation.adapt_fast(
943 &mut *self.base_optimizer,
944 &task_data,
945 strategy,
946 &adaptation_config,
947 )?;
948
949 adaptation_result.task_representation = task_representation;
951
952 self.memory_bank
954 .store_episode(task_data.clone(), adaptation_result.clone())?;
955
956 self.performance_tracker
958 .record_performance(&adaptation_result)?;
959
960 self.prototype_network
962 .update_prototypes(&task_data, &adaptation_result)?;
963
964 Ok(adaptation_result)
965 }
966
967 fn select_adaptation_strategy(
968 &self,
969 task_data: &TaskData<T>,
970 _similar_tasks: &[MemoryEpisode<T>],
971 ) -> Result<AdaptationStrategyType> {
972 match task_data.domain_info.difficulty_level {
974 DifficultyLevel::Trivial | DifficultyLevel::Easy => Ok(AdaptationStrategyType::FOMAML),
975 DifficultyLevel::Medium => Ok(AdaptationStrategyType::MAML),
976 DifficultyLevel::Hard | DifficultyLevel::Expert => {
977 Ok(AdaptationStrategyType::Prototypical)
978 }
979 DifficultyLevel::Extreme => Ok(AdaptationStrategyType::MemoryAugmented),
980 }
981 }
982}
983
984#[derive(Debug, Clone)]
986pub struct FewShotConfig<T: Float + Debug + Send + Sync + 'static> {
987 pub prototype_config: PrototypicalNetworkConfig<T>,
989
990 pub support_set_config: SupportSetManagerConfig,
992
993 pub similarity_config: SimilarityCalculatorConfig<T>,
995
996 pub memory_config: MemoryBankConfig<T>,
998
999 pub adaptation_config: FastAdaptationConfig,
1001
1002 pub tracking_config: TrackingConfig,
1004}
1005
1006#[derive(Debug, Clone)]
1008pub struct PrototypicalNetworkConfig<T: Float + Debug + Send + Sync + 'static> {
1009 pub embedding_dim: usize,
1011
1012 pub learning_rate: T,
1014
1015 pub num_layers: usize,
1017
1018 pub hidden_dim: usize,
1020}
1021
1022impl<T: Float + Debug + Send + Sync + 'static> PrototypicalNetwork<T> {
1024 pub fn new(config: PrototypicalNetworkConfig<T>) -> Result<Self> {
1026 if config.embedding_dim == 0 {
1027 return Err(OptimError::InvalidConfig(
1028 "embedding_dim must be > 0".to_string(),
1029 ));
1030 }
1031 let input_dim = config.hidden_dim.max(config.embedding_dim);
1035 let layer = xavier_encoder_layer::<T>(input_dim, config.embedding_dim, LayerType::Linear)?;
1036 Ok(Self {
1037 encoder: EncoderNetwork {
1038 layers: vec![layer],
1039 activation: ActivationFunction::ReLU,
1040 },
1041 prototypes: HashMap::new(),
1042 distance_metric: DistanceMetric::Euclidean,
1043 parameters: PrototypicalNetworkParams {
1044 embedding_dim: config.embedding_dim,
1045 learning_rate: config.learning_rate,
1046 temperature: T::one(),
1047 prototype_update_rate: scirs2_core::numeric::NumCast::from(0.1)
1048 .unwrap_or_else(|| T::one()),
1049 },
1050 })
1051 }
1052
1053 pub fn from_dims(embedding_dim: usize, _num_classes: usize) -> Result<Self> {
1055 if embedding_dim == 0 {
1056 return Err(OptimError::InvalidConfig(
1057 "embedding_dim must be > 0".to_string(),
1058 ));
1059 }
1060 let layer = xavier_encoder_layer::<T>(embedding_dim, embedding_dim, LayerType::Linear)?;
1061 Ok(Self {
1062 encoder: EncoderNetwork {
1063 layers: vec![layer],
1064 activation: ActivationFunction::ReLU,
1065 },
1066 prototypes: HashMap::new(),
1067 distance_metric: DistanceMetric::Euclidean,
1068 parameters: PrototypicalNetworkParams {
1069 embedding_dim,
1070 learning_rate: scirs2_core::numeric::NumCast::from(0.01)
1071 .unwrap_or_else(|| T::one()),
1072 temperature: T::one(),
1073 prototype_update_rate: scirs2_core::numeric::NumCast::from(0.1)
1074 .unwrap_or_else(|| T::one()),
1075 },
1076 })
1077 }
1078
1079 pub fn embedding_dim(&self) -> usize {
1081 self.parameters.embedding_dim
1082 }
1083
1084 pub fn encoder_layers(&self) -> &[EncoderLayer<T>] {
1086 &self.encoder.layers
1087 }
1088
1089 pub fn prototypes_mut(&mut self) -> &mut HashMap<String, Prototype<T>> {
1091 &mut self.prototypes
1092 }
1093
1094 pub fn prototypes(&self) -> &HashMap<String, Prototype<T>> {
1096 &self.prototypes
1097 }
1098
1099 pub fn distance_metric(&self) -> &DistanceMetric {
1101 &self.distance_metric
1102 }
1103
1104 pub fn encode_task(&self, task_data: &TaskData<T>) -> Result<Array1<T>> {
1117 if task_data.support_set.examples.is_empty() {
1118 return Err(OptimError::InsufficientData(
1119 "No support examples for encoding".to_string(),
1120 ));
1121 }
1122
1123 let width = self.encoder.embedding_width()?;
1124 let mut sum = Array1::<T>::zeros(width);
1125 let count = task_data.support_set.examples.len();
1126 for ex in &task_data.support_set.examples {
1127 let embedded = self.encoder.forward(&ex.features)?;
1128 for i in 0..width {
1129 sum[i] = sum[i] + embedded[i];
1130 }
1131 }
1132 let count_t = scirs2_core::numeric::NumCast::from(count).unwrap_or_else(|| T::one());
1133 for slot in sum.iter_mut() {
1134 *slot = *slot / count_t;
1135 }
1136 Ok(sum)
1137 }
1138
1139 pub fn embedding_width(&self) -> Result<usize> {
1144 self.encoder.embedding_width()
1145 }
1146
1147 pub fn update_prototypes(
1149 &mut self,
1150 task_data: &TaskData<T>,
1151 _result: &AdaptationResult<T>,
1152 ) -> Result<()> {
1153 let task_repr = self.encode_task(task_data)?;
1154 let task_id = task_data.task_id.clone();
1155 let update_rate = self.parameters.prototype_update_rate;
1156 let one_minus = T::one() - update_rate;
1157
1158 if let Some(proto) = self.prototypes.get_mut(&task_id) {
1159 let dim = proto.vector.len().min(task_repr.len());
1161 for i in 0..dim {
1162 proto.vector[i] = one_minus * proto.vector[i] + update_rate * task_repr[i];
1163 }
1164 proto.example_count += task_data.support_set.examples.len();
1165 proto.last_updated = std::time::SystemTime::now();
1166 proto.metadata.update_count += 1;
1167 } else {
1168 let proto = Prototype {
1169 vector: task_repr,
1170 confidence: T::one(),
1171 example_count: task_data.support_set.examples.len(),
1172 last_updated: std::time::SystemTime::now(),
1173 metadata: PrototypeMetadata {
1174 task_category: task_id.clone(),
1175 domain: task_data.domain_info.domain_type,
1176 created_at: std::time::SystemTime::now(),
1177 update_count: 1,
1178 },
1179 };
1180 self.prototypes.insert(task_id, proto);
1181 }
1182 Ok(())
1183 }
1184}
1185
1186impl<T: Float + Debug + Send + Sync + 'static> SupportSetManager<T> {
1187 pub fn new(config: SupportSetManagerConfig) -> Result<Self> {
1189 if config.max_support_size == 0 {
1190 return Err(OptimError::InvalidConfig(
1191 "max_support_size must be > 0".to_string(),
1192 ));
1193 }
1194 Ok(Self {
1195 config,
1196 _element: std::marker::PhantomData,
1197 })
1198 }
1199
1200 pub fn from_max_size(max_support_size: usize) -> Result<Self> {
1202 Self::new(SupportSetManagerConfig {
1203 min_support_size: 1,
1204 max_support_size,
1205 quality_threshold: 0.5,
1206 enable_augmentation: true,
1207 cache_support_sets: true,
1208 })
1209 }
1210
1211 pub fn max_support_size(&self) -> usize {
1213 self.config.max_support_size
1214 }
1215
1216 pub fn config(&self) -> &SupportSetManagerConfig {
1218 &self.config
1219 }
1220}
1221
1222impl<T: Float + Debug + Send + Sync + 'static> TaskSimilarityCalculator<T> {
1223 pub fn new(config: SimilarityCalculatorConfig<T>) -> Result<Self> {
1225 Ok(Self {
1226 similarity_cache: HashMap::new(),
1227 config,
1228 })
1229 }
1230
1231 pub fn default_new() -> Result<Self> {
1233 Self::new(SimilarityCalculatorConfig {
1234 enable_caching: true,
1235 cache_size_limit: 1000,
1236 similarity_threshold: scirs2_core::numeric::NumCast::from(0.5)
1237 .unwrap_or_else(|| T::zero()),
1238 use_metadata: true,
1239 })
1240 }
1241
1242 pub fn similarity_cache_mut(&mut self) -> &mut HashMap<(String, String), T> {
1244 &mut self.similarity_cache
1245 }
1246
1247 pub fn caching_enabled(&self) -> bool {
1249 self.config.enable_caching
1250 }
1251}
1252
1253impl<T: Float + Debug + Send + Sync + 'static> EpisodicMemoryBank<T> {
1254 pub fn new(config: MemoryBankConfig<T>) -> Result<Self> {
1256 if config.max_memory_size == 0 {
1257 return Err(OptimError::InvalidConfig(
1258 "max_memory_size must be > 0".to_string(),
1259 ));
1260 }
1261 Ok(Self {
1262 episodes: VecDeque::new(),
1263 config,
1264 usage_stats: MemoryUsageStats {
1265 total_episodes: 0,
1266 memory_utilization: 0.0,
1267 hit_rate: 0.0,
1268 avg_retrieval_time: Duration::from_secs(0),
1269 },
1270 })
1271 }
1272
1273 pub fn from_capacity(capacity: usize) -> Result<Self> {
1275 Self::new(MemoryBankConfig {
1276 max_memory_size: capacity,
1277 eviction_policy: EvictionPolicy::Performance,
1278 similarity_threshold: scirs2_core::numeric::NumCast::from(0.3)
1279 .unwrap_or_else(|| T::zero()),
1280 enable_compression: false,
1281 })
1282 }
1283
1284 pub fn capacity(&self) -> usize {
1286 self.config.max_memory_size
1287 }
1288
1289 pub fn len(&self) -> usize {
1291 self.episodes.len()
1292 }
1293
1294 pub fn is_empty(&self) -> bool {
1296 self.episodes.is_empty()
1297 }
1298
1299 pub fn episodes_mut(&mut self) -> &mut VecDeque<MemoryEpisode<T>> {
1301 &mut self.episodes
1302 }
1303
1304 pub fn episodes(&self) -> &VecDeque<MemoryEpisode<T>> {
1306 &self.episodes
1307 }
1308
1309 pub fn usage_stats_mut(&mut self) -> &mut MemoryUsageStats {
1311 &mut self.usage_stats
1312 }
1313
1314 pub fn usage_stats(&self) -> &MemoryUsageStats {
1316 &self.usage_stats
1317 }
1318
1319 pub fn eviction_policy(&self) -> EvictionPolicy {
1321 self.config.eviction_policy
1322 }
1323
1324 pub fn retrieve_similar(
1326 &self,
1327 task_data: &TaskData<T>,
1328 k: usize,
1329 ) -> Result<Vec<MemoryEpisode<T>>> {
1330 if self.episodes.is_empty() {
1331 return Ok(Vec::new());
1332 }
1333 let count = k.min(self.episodes.len());
1336 let result: Vec<MemoryEpisode<T>> =
1337 self.episodes.iter().rev().take(count).cloned().collect();
1338 let _ = task_data; Ok(result)
1340 }
1341
1342 pub fn store_episode(
1344 &mut self,
1345 task_data: TaskData<T>,
1346 result: AdaptationResult<T>,
1347 ) -> Result<()> {
1348 let episode = MemoryEpisode {
1349 episode_id: format!("ep_{}", self.usage_stats.total_episodes),
1350 task_data,
1351 adaptation_result: result,
1352 timestamp: std::time::SystemTime::now(),
1353 metadata: EpisodeMetadata {
1354 difficulty: DifficultyLevel::Medium,
1355 domain: DomainType::Optimization,
1356 success_rate: 0.0,
1357 tags: Vec::new(),
1358 },
1359 access_count: 0,
1360 };
1361
1362 if self.episodes.len() >= self.config.max_memory_size {
1363 self.episodes.pop_front();
1364 }
1365 self.episodes.push_back(episode);
1366 self.usage_stats.total_episodes += 1;
1367 self.usage_stats.memory_utilization =
1368 self.episodes.len() as f64 / self.config.max_memory_size as f64;
1369 Ok(())
1370 }
1371}
1372
1373impl<T: Float + Debug + Send + Sync + 'static> FastAdaptationEngine<T> {
1374 pub fn new(config: FastAdaptationConfig) -> Result<Self> {
1376 Ok(Self {
1377 algorithms: Vec::new(),
1378 config,
1379 })
1380 }
1381
1382 pub fn register_algorithm(&mut self, algorithm: Box<dyn FastAdaptationAlgorithm<T>>) {
1386 self.algorithms.push(algorithm);
1387 }
1388
1389 pub fn algorithm_count(&self) -> usize {
1391 self.algorithms.len()
1392 }
1393
1394 pub fn from_params(inner_lr: T, adaptation_steps: usize) -> Result<Self> {
1396 let _ = (inner_lr, adaptation_steps);
1397 Self::new(FastAdaptationConfig {
1398 enable_caching: true,
1399 enable_prediction: true,
1400 max_adaptation_time: Duration::from_secs(60),
1401 _performance_threshold: 0.8,
1402 })
1403 }
1404
1405 pub fn config(&self) -> &FastAdaptationConfig {
1407 &self.config
1408 }
1409
1410 pub fn adapt_fast(
1421 &mut self,
1422 optimizer: &mut dyn FewShotOptimizer<T>,
1423 task_data: &TaskData<T>,
1424 strategy: AdaptationStrategyType,
1425 config: &AdaptationConfig,
1426 ) -> Result<AdaptationResult<T>> {
1427 let start = Instant::now();
1428 let label = format!("{strategy:?}");
1429
1430 let mut result =
1431 if let Some(algorithm) = self.algorithms.iter_mut().find(|a| a.name() == label) {
1432 algorithm.adapt_fast(optimizer, task_data, None)?
1433 } else {
1434 optimizer.adapt_few_shot(&task_data.support_set, &task_data.query_set, config)?
1435 };
1436
1437 if result.task_representation.is_empty() {
1440 result.task_representation = optimizer.get_task_representation(task_data)?;
1441 }
1442 result.resource_usage.total_time = start.elapsed();
1445
1446 Ok(result)
1447 }
1448}
1449
1450impl<T: Float + Debug + Send + Sync + 'static> FewShotPerformanceTracker<T> {
1451 pub fn new(config: TrackingConfig) -> Result<Self> {
1453 Ok(Self {
1454 performance_history: VecDeque::new(),
1455 config,
1456 stats: PerformanceStats {
1457 best_performance: T::zero(),
1458 average_performance: T::zero(),
1459 performance_variance: T::zero(),
1460 improvement_rate: T::zero(),
1461 success_rate: T::zero(),
1462 },
1463 })
1464 }
1465
1466 pub fn record_performance(&mut self, result: &AdaptationResult<T>) -> Result<()> {
1468 let record = PerformanceRecord {
1469 task_id: String::new(),
1470 performance: result.performance.query_performance,
1471 adaptation_time: result.resource_usage.total_time,
1472 strategy_used: String::new(),
1473 timestamp: std::time::SystemTime::now(),
1474 additional_metrics: HashMap::new(),
1475 };
1476
1477 if self.performance_history.len() >= self.config.max_history_size {
1478 self.performance_history.pop_front();
1479 }
1480 self.performance_history.push_back(record);
1481
1482 if !self.performance_history.is_empty() {
1484 let mut sum = T::zero();
1485 let mut best = T::neg_infinity();
1486 for r in &self.performance_history {
1487 sum = sum + r.performance;
1488 if r.performance > best {
1489 best = r.performance;
1490 }
1491 }
1492 let count_t = scirs2_core::numeric::NumCast::from(self.performance_history.len())
1493 .unwrap_or_else(|| T::one());
1494 self.stats.average_performance = sum / count_t;
1495 self.stats.best_performance = best;
1496 }
1497 Ok(())
1498 }
1499}
1500
1501#[derive(Debug, Clone)]
1503pub struct TaskMetadata {
1504 pub task_name: String,
1505 pub domain: DomainType,
1506 pub difficulty: DifficultyLevel,
1507 pub created_at: std::time::SystemTime,
1508}
1509
1510#[derive(Debug, Clone)]
1511pub struct ExampleMetadata {
1512 pub source: String,
1513 pub quality_score: f64,
1514 pub created_at: std::time::SystemTime,
1515}
1516
1517#[derive(Debug, Clone)]
1518pub struct SupportSetStatistics<T: Float + Debug + Send + Sync + 'static> {
1519 pub mean: Array1<T>,
1520 pub variance: Array1<T>,
1521 pub size: usize,
1522 pub diversity_score: T,
1523}
1524
1525#[derive(Debug, Clone)]
1526pub struct QuerySetStatistics<T: Float + Debug + Send + Sync + 'static> {
1527 pub mean: Array1<T>,
1528 pub variance: Array1<T>,
1529 pub size: usize,
1530}
1531
1532#[derive(Debug, Clone, Copy)]
1534pub enum EvaluationMetric {
1535 Accuracy,
1536 Loss,
1537 F1Score,
1538 Precision,
1539 Recall,
1540 AUC,
1541 MSE,
1542 MAE,
1543 FinalPerformance,
1544 Efficiency,
1545 TrainingTime,
1546}
1547
1548#[cfg(test)]
1549mod tests {
1550 use super::*;
1551
1552 #[test]
1553 fn test_support_set_creation() {
1554 let support_set = SupportSet::<f64> {
1555 examples: vec![SupportExample {
1556 features: Array1::from_vec(vec![1.0, 2.0, 3.0]),
1557 target: 0.5,
1558 weight: 1.0,
1559 context: HashMap::new(),
1560 metadata: ExampleMetadata {
1561 source: "test".to_string(),
1562 quality_score: 0.9,
1563 created_at: std::time::SystemTime::now(),
1564 },
1565 }],
1566 task_metadata: TaskMetadata {
1567 task_name: "test_task".to_string(),
1568 domain: DomainType::Optimization,
1569 difficulty: DifficultyLevel::Easy,
1570 created_at: std::time::SystemTime::now(),
1571 },
1572 statistics: SupportSetStatistics {
1573 mean: Array1::from_vec(vec![1.0, 2.0, 3.0]),
1574 variance: Array1::from_vec(vec![0.1, 0.1, 0.1]),
1575 size: 1,
1576 diversity_score: 0.8,
1577 },
1578 temporal_order: None,
1579 };
1580
1581 assert_eq!(support_set.examples.len(), 1);
1582 assert_eq!(support_set.statistics.size, 1);
1583 }
1584
1585 #[test]
1586 fn test_adaptation_config() {
1587 let config = AdaptationConfig {
1588 adaptation_steps: 10,
1589 adaptation_lr: 0.01,
1590 strategy: AdaptationStrategyType::MAML,
1591 early_stopping: None,
1592 regularization: RegularizationConfig {
1593 l2_strength: 0.001,
1594 dropout_rate: 0.1,
1595 gradient_clip: Some(1.0),
1596 task_regularization: HashMap::new(),
1597 },
1598 resource_constraints: ResourceConstraints {
1599 max_time: Duration::from_secs(60),
1600 max_memory_mb: 1024,
1601 max_compute_budget: 100.0,
1602 },
1603 };
1604
1605 assert_eq!(config.adaptation_steps, 10);
1606 assert_eq!(config.adaptation_lr, 0.01);
1607 assert!(matches!(config.strategy, AdaptationStrategyType::MAML));
1608 }
1609
1610 #[test]
1611 fn test_domain_info() {
1612 let domain_info = DomainInfo {
1613 domain_type: DomainType::ComputerVision,
1614 characteristics: DomainCharacteristics {
1615 input_dim: 784,
1616 output_dim: 10,
1617 temporal: false,
1618 stochasticity: 0.1,
1619 noise_level: 0.05,
1620 sparsity: 0.0,
1621 },
1622 difficulty_level: DifficultyLevel::Medium,
1623 constraints: vec![DomainConstraint {
1624 constraint_type: ConstraintType::LatencyRequirement,
1625 description: "Max 100ms inference time".to_string(),
1626 enforcement: ConstraintEnforcement::Hard,
1627 }],
1628 };
1629
1630 assert!(matches!(
1631 domain_info.domain_type,
1632 DomainType::ComputerVision
1633 ));
1634 assert_eq!(domain_info.characteristics.input_dim, 784);
1635 assert_eq!(domain_info.constraints.len(), 1);
1636 }
1637
1638 struct RecordingOptimizer {
1643 adapt_calls: usize,
1644 }
1645
1646 impl FewShotOptimizer<f64> for RecordingOptimizer {
1647 fn adapt_few_shot(
1648 &mut self,
1649 support_set: &SupportSet<f64>,
1650 _query_set: &QuerySet<f64>,
1651 _adaptation_config: &AdaptationConfig,
1652 ) -> Result<AdaptationResult<f64>> {
1653 self.adapt_calls += 1;
1654 let width = support_set
1655 .examples
1656 .first()
1657 .map(|e| e.features.len())
1658 .unwrap_or(1);
1659 Ok(AdaptationResult {
1660 adapted_state: OptimizerState {
1661 parameters: Array1::zeros(width),
1662 gradients: Array1::zeros(width),
1663 momentum: None,
1664 hidden_states: HashMap::new(),
1665 memory_buffers: HashMap::new(),
1666 step: 1,
1667 step_count: 1,
1668 loss: Some(0.05),
1669 learning_rate: 0.01,
1670 metadata: crate::StateMetadata::default(),
1671 },
1672 performance: AdaptationPerformance {
1673 query_performance: 0.42,
1674 support_performance: 0.37,
1675 adaptation_speed: 2,
1676 final_loss: 0.05,
1677 improvement: 0.11,
1678 stability: 0.5,
1679 },
1680 task_representation: Array1::zeros(0),
1683 adaptation_trajectory: Vec::new(),
1684 resource_usage: ResourceUsage {
1685 total_time: Duration::from_secs(999),
1688 peak_memory_mb: 1.0,
1689 compute_cost: 1.0,
1690 energy_consumption: 1.0,
1691 },
1692 })
1693 }
1694
1695 fn get_task_representation(&self, _taskdata: &TaskData<f64>) -> Result<Array1<f64>> {
1696 Ok(Array1::from_vec(vec![7.0, 8.0, 9.0]))
1697 }
1698
1699 fn compute_adaptation_loss(
1700 &self,
1701 _support_set: &SupportSet<f64>,
1702 _query_set: &QuerySet<f64>,
1703 ) -> Result<f64> {
1704 Ok(0.1)
1705 }
1706
1707 fn update_meta_parameters(&mut self, _metagradients: &MetaGradients<f64>) -> Result<()> {
1708 Ok(())
1709 }
1710
1711 fn get_transfer_state(&self) -> TransferState<f64> {
1712 TransferState {
1713 representations: HashMap::new(),
1714 meta_parameters: HashMap::new(),
1715 task_embeddings: Array2::zeros((1, 1)),
1716 transfer_stats: TransferStatistics {
1717 source_performance: 0.0,
1718 target_performance: 0.0,
1719 transfer_efficiency: 0.0,
1720 steps_saved: 0,
1721 },
1722 }
1723 }
1724
1725 fn load_transfer_state(&mut self, _state: TransferState<f64>) -> Result<()> {
1726 Ok(())
1727 }
1728 }
1729
1730 fn make_task_data() -> TaskData<f64> {
1731 TaskData {
1732 task_id: "t".to_string(),
1733 support_set: SupportSet {
1734 examples: vec![SupportExample {
1735 features: Array1::from_vec(vec![1.0, 2.0, 3.0]),
1736 target: 0.5,
1737 weight: 1.0,
1738 context: HashMap::new(),
1739 metadata: ExampleMetadata {
1740 source: "test".to_string(),
1741 quality_score: 0.9,
1742 created_at: std::time::SystemTime::now(),
1743 },
1744 }],
1745 task_metadata: TaskMetadata {
1746 task_name: "test_task".to_string(),
1747 domain: DomainType::Optimization,
1748 difficulty: DifficultyLevel::Easy,
1749 created_at: std::time::SystemTime::now(),
1750 },
1751 statistics: SupportSetStatistics {
1752 mean: Array1::from_vec(vec![1.0, 2.0, 3.0]),
1753 variance: Array1::from_vec(vec![0.1, 0.1, 0.1]),
1754 size: 1,
1755 diversity_score: 0.8,
1756 },
1757 temporal_order: None,
1758 },
1759 query_set: QuerySet {
1760 examples: vec![QueryExample {
1761 features: Array1::from_vec(vec![1.0, 2.0, 3.0]),
1762 true_target: Some(0.4),
1763 weight: 1.0,
1764 context: HashMap::new(),
1765 }],
1766 statistics: QuerySetStatistics {
1767 mean: Array1::from_vec(vec![1.0, 2.0, 3.0]),
1768 variance: Array1::from_vec(vec![0.1, 0.1, 0.1]),
1769 size: 1,
1770 },
1771 eval_metrics: vec![EvaluationMetric::MSE],
1772 },
1773 task_params: HashMap::new(),
1774 domain_info: DomainInfo {
1775 domain_type: DomainType::Optimization,
1776 characteristics: DomainCharacteristics {
1777 input_dim: 3,
1778 output_dim: 1,
1779 temporal: false,
1780 stochasticity: 0.0,
1781 noise_level: 0.0,
1782 sparsity: 0.0,
1783 },
1784 difficulty_level: DifficultyLevel::Easy,
1785 constraints: vec![],
1786 },
1787 }
1788 }
1789
1790 fn make_adaptation_config() -> AdaptationConfig {
1791 AdaptationConfig {
1792 adaptation_steps: 5,
1793 adaptation_lr: 0.01,
1794 strategy: AdaptationStrategyType::MAML,
1795 early_stopping: None,
1796 regularization: RegularizationConfig {
1797 l2_strength: 0.0,
1798 dropout_rate: 0.0,
1799 gradient_clip: None,
1800 task_regularization: HashMap::new(),
1801 },
1802 resource_constraints: ResourceConstraints {
1803 max_time: Duration::from_secs(1),
1804 max_memory_mb: 64,
1805 max_compute_budget: 1.0,
1806 },
1807 }
1808 }
1809
1810 #[test]
1815 fn test_adapt_fast_delegates_instead_of_fabricating() {
1816 let mut engine = FastAdaptationEngine::<f64>::new(FastAdaptationConfig {
1817 enable_caching: true,
1818 enable_prediction: true,
1819 max_adaptation_time: Duration::from_secs(1),
1820 _performance_threshold: 0.8,
1821 })
1822 .expect("engine");
1823 assert_eq!(
1824 engine.algorithm_count(),
1825 0,
1826 "no algorithm registered by default"
1827 );
1828
1829 let mut optimizer = RecordingOptimizer { adapt_calls: 0 };
1830 let task_data = make_task_data();
1831 let config = make_adaptation_config();
1832
1833 let result = engine
1834 .adapt_fast(
1835 &mut optimizer,
1836 &task_data,
1837 AdaptationStrategyType::MAML,
1838 &config,
1839 )
1840 .expect("adapt_fast should succeed");
1841
1842 assert_eq!(
1843 optimizer.adapt_calls, 1,
1844 "adapt_fast must actually call the optimizer, not ignore it"
1845 );
1846 approx::assert_abs_diff_eq!(result.performance.query_performance, 0.42, epsilon = 1e-12);
1850 approx::assert_abs_diff_eq!(
1851 result.performance.support_performance,
1852 0.37,
1853 epsilon = 1e-12
1854 );
1855 assert_eq!(result.performance.adaptation_speed, 2);
1856 assert_eq!(result.adapted_state.parameters.len(), 3);
1859 assert_eq!(
1862 result.task_representation.as_slice(),
1863 Some(&[7.0, 8.0, 9.0][..])
1864 );
1865 assert!(result.resource_usage.total_time < Duration::from_secs(999));
1867 }
1868
1869 #[test]
1872 fn test_adapt_fast_prefers_a_registered_algorithm() {
1873 struct AlwaysProto {
1874 calls: usize,
1875 }
1876 impl FastAdaptationAlgorithm<f64> for AlwaysProto {
1877 fn adapt_fast(
1878 &mut self,
1879 _optimizer: &mut dyn FewShotOptimizer<f64>,
1880 _task_data: &TaskData<f64>,
1881 _target_performance: Option<f64>,
1882 ) -> Result<AdaptationResult<f64>> {
1883 self.calls += 1;
1884 Ok(AdaptationResult {
1885 adapted_state: OptimizerState {
1886 parameters: Array1::zeros(2),
1887 gradients: Array1::zeros(2),
1888 momentum: None,
1889 hidden_states: HashMap::new(),
1890 memory_buffers: HashMap::new(),
1891 step: 0,
1892 step_count: 0,
1893 loss: None,
1894 learning_rate: 0.02,
1895 metadata: crate::StateMetadata::default(),
1896 },
1897 performance: AdaptationPerformance {
1898 query_performance: 0.77,
1899 support_performance: 0.66,
1900 adaptation_speed: 1,
1901 final_loss: 0.2,
1902 improvement: 0.3,
1903 stability: 0.4,
1904 },
1905 task_representation: Array1::from_vec(vec![1.0]),
1906 adaptation_trajectory: Vec::new(),
1907 resource_usage: ResourceUsage {
1908 total_time: Duration::from_secs(0),
1909 peak_memory_mb: 0.0,
1910 compute_cost: 0.0,
1911 energy_consumption: 0.0,
1912 },
1913 })
1914 }
1915
1916 fn estimate_adaptation_time(&self, _taskdata: &TaskData<f64>) -> Duration {
1917 Duration::from_millis(1)
1918 }
1919
1920 fn name(&self) -> &str {
1921 "Prototypical"
1922 }
1923 }
1924
1925 let mut engine = FastAdaptationEngine::<f64>::new(FastAdaptationConfig {
1926 enable_caching: false,
1927 enable_prediction: false,
1928 max_adaptation_time: Duration::from_secs(1),
1929 _performance_threshold: 0.5,
1930 })
1931 .expect("engine");
1932 engine.register_algorithm(Box::new(AlwaysProto { calls: 0 }));
1933 assert_eq!(engine.algorithm_count(), 1);
1934
1935 let mut optimizer = RecordingOptimizer { adapt_calls: 0 };
1936 let task_data = make_task_data();
1937 let config = make_adaptation_config();
1938
1939 let result = engine
1940 .adapt_fast(
1941 &mut optimizer,
1942 &task_data,
1943 AdaptationStrategyType::Prototypical,
1944 &config,
1945 )
1946 .expect("adapt_fast should succeed");
1947
1948 assert_eq!(
1949 optimizer.adapt_calls, 0,
1950 "the registered algorithm should have been used instead of the optimizer fallback"
1951 );
1952 approx::assert_abs_diff_eq!(result.performance.query_performance, 0.77, epsilon = 1e-12);
1953 }
1954}