1#[allow(dead_code)]
7use super::core::*;
8#[cfg(feature = "cross-platform-testing")]
9use crate::benchmarking::cross_platform_tester::{PerformanceBaseline, PlatformTarget};
10use crate::error::{OptimError, Result};
11use scirs2_core::ndarray::Array1;
12use scirs2_core::numeric::Float;
13use std::collections::HashMap;
14use std::fmt::Debug;
15
16pub struct BaseOptimizerPlugin<A: Float + std::fmt::Debug> {
18 info: PluginInfo,
20 capabilities: PluginCapabilities,
22 config: OptimizerConfig,
24 state: BaseOptimizerState<A>,
26 metrics: PerformanceMetrics,
28 memory_usage: MemoryUsage,
30 event_handlers: Vec<Box<dyn PluginEventHandler>>,
32}
33
34impl<A: Float + std::fmt::Debug + Send + Sync> std::fmt::Debug for BaseOptimizerPlugin<A> {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 f.debug_struct("BaseOptimizerPlugin")
37 .field("info", &self.info)
38 .field("capabilities", &self.capabilities)
39 .field("config", &self.config)
40 .field("state", &self.state)
41 .field("metrics", &self.metrics)
42 .field("memory_usage", &self.memory_usage)
43 .field(
44 "event_handlers",
45 &format!("{} handlers", self.event_handlers.len()),
46 )
47 .finish()
48 }
49}
50
51#[derive(Debug, Clone)]
53pub struct BaseOptimizerState<A: Float + std::fmt::Debug> {
54 pub step_count: usize,
56 pub param_count: usize,
58 pub lr_history: Vec<A>,
60 pub grad_norm_history: Vec<A>,
62 pub param_change_history: Vec<A>,
64 pub momentum_buffer: Vec<A>,
67 pub custom_state: HashMap<String, StateValue>,
69}
70
71pub struct PluginSDK;
73
74#[derive(Debug, Clone)]
76pub struct TestConfig {
77 pub iterations: usize,
79 pub tolerance: f64,
81 pub random_seed: u64,
83 pub enable_performance_tests: bool,
85 pub enable_memory_tests: bool,
87 pub enable_convergence_tests: bool,
89}
90
91#[derive(Debug)]
93pub struct TestSuite<A: Float> {
94 pub functionality_tests: Vec<Box<dyn PluginTest<A>>>,
96 pub performance_tests: Vec<Box<dyn PerformanceTest<A>>>,
98 pub convergence_tests: Vec<Box<dyn ConvergenceTest<A>>>,
100 pub memory_tests: Vec<Box<dyn MemoryTest<A>>>,
102}
103
104pub trait PluginTest<A: Float>: Debug {
106 fn run_test(&self, plugin: &mut dyn OptimizerPlugin<A>) -> TestResult;
108
109 fn name(&self) -> &str;
111
112 fn description(&self) -> &str;
114}
115
116pub trait PerformanceTest<A: Float>: Debug {
118 fn run_performance_test(&self, plugin: &mut dyn OptimizerPlugin<A>) -> PerformanceTestResult;
120
121 fn name(&self) -> &str;
123
124 #[cfg(feature = "cross-platform-testing")]
126 fn baseline(&self) -> PerformanceBaseline;
127}
128
129pub trait ConvergenceTest<A: Float>: Debug {
131 fn run_convergence_test(&self, plugin: &mut dyn OptimizerPlugin<A>)
133 -> ConvergenceTestResult<A>;
134
135 fn name(&self) -> &str;
137
138 fn convergence_criteria(&self) -> ConvergenceCriteria<A>;
140}
141
142pub trait MemoryTest<A: Float>: Debug {
144 fn run_memory_test(&self, plugin: &mut dyn OptimizerPlugin<A>) -> MemoryTestResult;
146
147 fn name(&self) -> &str;
149
150 fn memory_constraints(&self) -> MemoryConstraints;
152}
153
154#[derive(Debug, Clone)]
156pub struct TestResult {
157 pub passed: bool,
159 pub message: String,
161 pub execution_time: std::time::Duration,
163 pub data: HashMap<String, serde_json::Value>,
165}
166
167#[derive(Debug, Clone)]
169pub struct PerformanceTestResult {
170 pub metrics: PerformanceMetrics,
172 pub baseline_comparison: BaselineComparison,
174 pub performance_score: f64,
176}
177
178#[derive(Debug, Clone)]
180pub struct ConvergenceTestResult<A: Float> {
181 pub converged: bool,
183 pub iterations_to_convergence: Option<usize>,
185 pub final_objective: A,
187 pub convergence_rate: f64,
189 pub metrics: ConvergenceMetrics,
191}
192
193#[derive(Debug, Clone)]
195pub struct MemoryTestResult {
196 pub memory_metrics: MemoryUsage,
198 pub memory_leak_detected: bool,
200 pub efficiency_score: f64,
202}
203
204#[derive(Debug, Clone)]
206pub struct BaselineComparison {
207 pub relative_performance: f64,
209 pub absolute_difference: f64,
211 pub improvement_percent: f64,
213}
214
215#[derive(Debug, Clone)]
217pub struct ConvergenceCriteria<A: Float> {
218 pub max_iterations: usize,
220 pub gradient_tolerance: A,
222 pub function_tolerance: A,
224 pub parameter_tolerance: A,
226}
227
228#[derive(Debug, Clone)]
230pub struct MemoryConstraints {
231 pub max_memory_usage: usize,
233 pub max_allocations: usize,
235 pub leak_tolerance: usize,
237}
238
239pub trait ValidationRule<A: Float>: Debug {
241 fn validate(&self, plugin: &dyn OptimizerPlugin<A>) -> ValidationResult;
243
244 fn name(&self) -> &str;
246
247 fn severity(&self) -> ValidationSeverity;
249}
250
251#[derive(Debug, Clone)]
253pub struct ValidationResult {
254 pub passed: bool,
256 pub message: String,
258 pub severity: ValidationSeverity,
260 pub suggestions: Vec<String>,
262}
263
264#[derive(Debug, Clone)]
266pub enum ValidationSeverity {
267 Info,
268 Warning,
269 Error,
270 Critical,
271}
272
273pub trait Benchmark<A: Float>: Debug {
275 fn run_benchmark(&self, plugin: &mut dyn OptimizerPlugin<A>) -> BenchmarkResult<A>;
277
278 fn name(&self) -> &str;
280
281 fn description(&self) -> &str;
283
284 fn category(&self) -> BenchmarkCategory;
286}
287
288#[derive(Debug, Clone)]
290pub enum BenchmarkCategory {
291 Speed,
293 Memory,
295 Accuracy,
297 Scalability,
299 Robustness,
301}
302
303#[derive(Debug, Clone)]
305pub struct BenchmarkResult<A: Float> {
306 pub name: String,
308 pub score: f64,
311 pub metrics: HashMap<String, f64>,
313 pub execution_time: std::time::Duration,
315 pub memory_usage: usize,
317 pub data: HashMap<String, A>,
319 pub verified: bool,
323}
324
325#[derive(Debug, Clone)]
327pub struct BenchmarkConfig {
328 pub runs: usize,
330 pub warmup_iterations: usize,
332 pub problem_sizes: Vec<usize>,
334 pub random_seeds: Vec<u64>,
336}
337
338impl PluginSDK {
340 pub fn create_plugin_template(name: &str) -> PluginTemplate {
342 PluginTemplate::new(name)
343 }
344
345 pub fn validate_config_schema(schema: &ConfigSchema) -> Result<()> {
347 for (field_name, field_schema) in &schema.fields {
348 if field_name.is_empty() {
349 return Err(OptimError::InvalidConfig(
350 "Field name cannot be empty".to_string(),
351 ));
352 }
353
354 if field_schema.description.is_empty() {
355 return Err(OptimError::InvalidConfig(format!(
356 "Field '{}' must have a description",
357 field_name
358 )));
359 }
360 }
361 Ok(())
362 }
363
364 pub fn generate_plugin_manifest(info: &PluginInfo) -> String {
366 format!(
367 r#"[plugin]
368name = "{}"
369version = "{}"
370description = "{}"
371author = "{}"
372license = "{}"
373entry_point = "plugin_main"
374
375[build]
376rust_version = "1.70.0"
377target = "*"
378profile = "release"
379
380[runtime]
381min_rust_version = "1.70.0"
382"#,
383 info.name, info.version, info.description, info.author, info.license
384 )
385 }
386
387 pub fn default_test_config() -> TestConfig {
389 TestConfig {
390 iterations: 100,
391 tolerance: 1e-6,
392 random_seed: 42,
393 enable_performance_tests: true,
394 enable_memory_tests: true,
395 enable_convergence_tests: true,
396 }
397 }
398
399 #[cfg(feature = "cross-platform-testing")]
401 pub fn create_performance_baseline<A>(
402 optimizer: &mut dyn OptimizerPlugin<A>,
403 test_data: &[(Array1<A>, Array1<A>)],
404 ) -> PerformanceBaseline
405 where
406 A: Float + Debug + Send + Sync + 'static,
407 {
408 let start_time = std::time::Instant::now();
409 let mut total_memory = 0;
410
411 for (params, gradients) in test_data {
412 let _result = optimizer.step(params, gradients);
413 total_memory += optimizer.memory_usage().current_usage;
414 }
415
416 let execution_time = start_time.elapsed();
417 let _avg_memory = total_memory / test_data.len();
418
419 PerformanceBaseline {
420 target: PlatformTarget::CPU,
421 throughput_ops_per_sec: test_data.len() as f64 / execution_time.as_secs_f64(),
422 latency_ms: execution_time.as_secs_f64() * 1000.0 / test_data.len() as f64,
423 memory_usage_mb: total_memory as f64 / (1024.0 * 1024.0),
424 energy_consumption_joules: None,
425 accuracy_metrics: HashMap::new(),
426 }
427 }
428}
429
430#[derive(Debug)]
432pub struct PluginTemplate {
433 name: String,
435 structure: TemplateStructure,
437}
438
439#[derive(Debug)]
441pub struct TemplateStructure {
442 pub source_files: Vec<TemplateFile>,
444 pub config_files: Vec<TemplateFile>,
446 pub test_files: Vec<TemplateFile>,
448 pub doc_files: Vec<TemplateFile>,
450}
451
452#[derive(Debug)]
454pub struct TemplateFile {
455 pub path: String,
457 pub content: String,
459 pub file_type: TemplateFileType,
461}
462
463#[derive(Debug)]
465pub enum TemplateFileType {
466 RustSource,
468 TomlConfig,
470 Markdown,
472 Test,
474}
475
476impl PluginTemplate {
477 pub fn name(&self) -> &str {
479 &self.name
480 }
481
482 pub fn new(name: &str) -> Self {
484 let structure = Self::create_default_structure(name);
485 Self {
486 name: name.to_string(),
487 structure,
488 }
489 }
490
491 pub fn generate_to_directory(&self, outputdir: &std::path::Path) -> Result<()> {
493 std::fs::create_dir_all(outputdir)?;
494
495 for file in &self.structure.source_files {
496 let file_path = outputdir.join(&file.path);
497 if let Some(parent) = file_path.parent() {
498 std::fs::create_dir_all(parent)?;
499 }
500 std::fs::write(&file_path, &file.content)?;
501 }
502
503 for file in &self.structure.config_files {
504 let file_path = outputdir.join(&file.path);
505 std::fs::write(&file_path, &file.content)?;
506 }
507
508 for file in &self.structure.test_files {
509 let file_path = outputdir.join(&file.path);
510 if let Some(parent) = file_path.parent() {
511 std::fs::create_dir_all(parent)?;
512 }
513 std::fs::write(&file_path, &file.content)?;
514 }
515
516 Ok(())
517 }
518
519 fn create_default_structure(name: &str) -> TemplateStructure {
520 let lib_rs_content = format!(
521 r#"//! {} optimizer plugin
522//
523// This is an auto-generated plugin template.
524
525use optirs_core::plugin::*;
526use scirs2_core::ndarray::Array1;
527use scirs2_core::numeric::Float;
528
529#[derive(Debug)]
530pub struct {}Optimizer<A: Float> {{
531 learning_rate: A,
532 // Add your optimizer state here
533}}
534
535impl<A: Float + Send + Sync> {}Optimizer<A> {{
536 pub fn new(_learningrate: A) -> Self {{
537 Self {{
538 learning_rate,
539 }}
540 }}
541}}
542
543impl<A: Float + std::fmt::Debug + Send + Sync + 'static> OptimizerPlugin<A> for {}Optimizer<A> {{
544 fn step(&mut self, params: &Array1<A>, gradients: &Array1<A>) -> Result<Array1<A>> {{
545 // Implement your optimization step here
546 Ok(params - &(gradients * self.learning_rate))
547 }}
548
549 fn name(&self) -> &str {{
550 "{}"
551 }}
552
553 fn version(&self) -> &str {{
554 "0.1.0"
555 }}
556
557 fn plugin_info(&self) -> PluginInfo {{
558 create_plugin_info("{}", "0.1.0", "Plugin Developer")
559 }}
560
561 fn capabilities(&self) -> PluginCapabilities {{
562 create_basic_capabilities()
563 }}
564
565 fn initialize(&mut self, paramshape: &[usize]) -> Result<()> {{
566 Ok(())
567 }}
568
569 fn reset(&mut self) -> Result<()> {{
570 Ok(())
571 }}
572
573 fn get_config(&self) -> OptimizerConfig {{
574 OptimizerConfig::default()
575 }}
576
577 fn set_config(&mut self, config: OptimizerConfig) -> Result<()> {{
578 Ok(())
579 }}
580
581 fn get_state(&self) -> Result<OptimizerState> {{
582 Ok(OptimizerState::default())
583 }}
584
585 fn set_state(&mut self, state: OptimizerState) -> Result<()> {{
586 Ok(())
587 }}
588
589 fn clone_plugin(&self) -> Box<dyn OptimizerPlugin<A>> {{
590 Box::new(Self::new(self.learning_rate))
591 }}
592}}
593
594// Plugin factory implementation
595#[derive(Debug)]
596pub struct {}Factory;
597
598impl<A: Float + std::fmt::Debug + Send + Sync + 'static> OptimizerPluginFactory<A> for {}Factory {{
599 fn create_optimizer(&self, config: OptimizerConfig) -> Result<Box<dyn OptimizerPlugin<A>>> {{
600 let learning_rate = A::from(config.learning_rate).ok_or_else(|| {{
601 OptimError::InvalidConfig(format!(
602 "learning_rate {{}} is not representable in this optimizer's element type",
603 config.learning_rate
604 ))
605 }})?;
606 Ok(Box::new({}Optimizer::new(learning_rate)))
607 }}
608
609 fn factory_info(&self) -> PluginInfo {{
610 create_plugin_info("{}", "0.1.0", "Plugin Developer")
611 }}
612
613 fn validate_config(&self, config: &OptimizerConfig) -> Result<()> {{
614 if config.learning_rate <= 0.0 {{
615 return Err(OptimError::InvalidConfig(
616 "Learning rate must be positive".to_string(),
617 ));
618 }}
619 Ok(())
620 }}
621
622 fn default_config(&self) -> OptimizerConfig {{
623 OptimizerConfig {{
624 learning_rate: 0.001,
625 ..Default::default()
626 }}
627 }}
628
629 fn config_schema(&self) -> ConfigSchema {{
630 let mut schema = ConfigSchema {{
631 fields: std::collections::HashMap::new(),
632 required_fields: vec!["learning_rate".to_string()],
633 version: "1.0".to_string(),
634 }};
635
636 schema.fields.insert(
637 "learning_rate".to_string(),
638 FieldSchema {{
639 field_type: FieldType::Float {{ min: Some(0.0), max: None }},
640 description: "Learning rate for optimization".to_string(),
641 default_value: Some(ConfigValue::Float(0.001)),
642 constraints: vec![ValidationConstraint::Positive],
643 required: true,
644 }},
645 );
646
647 schema
648 }}
649}}
650"#,
651 name, name, name, name, name, name, name, name, name, name
652 );
653
654 let plugin_toml_content = format!(
655 r#"[plugin]
656name = "{}"
657version = "0.1.0"
658description = "Custom optimizer plugin"
659author = "Plugin Developer"
660license = "MIT"
661entry_point = "plugin_main"
662
663[build]
664rust_version = "1.70.0"
665target = "*"
666profile = "release"
667
668[runtime]
669min_rust_version = "1.70.0"
670"#,
671 name
672 );
673
674 let test_content = format!(
675 r#"//! Tests for {} optimizer plugin
676
677use super::*;
678use scirs2_core::ndarray::Array1;
679
680#[test]
681#[allow(dead_code)]
682fn test_{}_basic_functionality() {{
683 let mut optimizer = {}Optimizer::new(0.01);
684 let params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
685 let gradients = Array1::from_vec(vec![0.1, 0.2, 0.3]);
686
687 let result = optimizer.step(¶ms, &gradients).expect("a step over finite inputs");
688
689 // Verify the result
690 assert!((result[0] - 0.999).abs() < 1e-6);
691 assert!((result[1] - 1.998).abs() < 1e-6);
692 assert!((result[2] - 2.997).abs() < 1e-6);
693}}
694
695#[test]
696#[allow(dead_code)]
697fn test_{}_convergence() {{
698 let mut optimizer = {}Optimizer::new(0.1);
699 let mut params = Array1::from_vec(vec![1.0, 1.0]);
700
701 // Optimize towards zero
702 for _ in 0..100 {{
703 let gradients = ¶ms * 2.0; // Gradient of x^2
704 params = optimizer.step(¶ms, &gradients).expect("a step over finite inputs");
705 }}
706
707 // Should converge close to zero
708 assert!(params.iter().all(|&x| x.abs() < 0.1));
709}}
710"#,
711 name,
712 name.to_lowercase(),
713 name,
714 name.to_lowercase(),
715 name
716 );
717
718 TemplateStructure {
719 source_files: vec![TemplateFile {
720 path: "src/lib.rs".to_string(),
721 content: lib_rs_content,
722 file_type: TemplateFileType::RustSource,
723 }],
724 config_files: vec![TemplateFile {
725 path: "plugin.toml".to_string(),
726 content: plugin_toml_content,
727 file_type: TemplateFileType::TomlConfig,
728 }],
729 test_files: vec![TemplateFile {
730 path: "tests/integration_tests.rs".to_string(),
731 content: test_content,
732 file_type: TemplateFileType::Test,
733 }],
734 doc_files: vec![],
735 }
736 }
737}
738
739impl<A: Float + Debug + Send + Sync + 'static> BaseOptimizerPlugin<A> {
742 pub fn new(info: PluginInfo, capabilities: PluginCapabilities) -> Self {
744 Self {
745 info,
746 capabilities,
747 config: OptimizerConfig::default(),
748 state: BaseOptimizerState::new(),
749 metrics: PerformanceMetrics::default(),
750 memory_usage: MemoryUsage::default(),
751 event_handlers: Vec::new(),
752 }
753 }
754
755 pub fn add_event_handler(&mut self, handler: Box<dyn PluginEventHandler>) {
757 self.event_handlers.push(handler);
758 }
759
760 pub fn update_metrics(&mut self, steptime: std::time::Duration) {
762 self.metrics.total_steps += 1;
763 self.metrics.avg_step_time = (self.metrics.avg_step_time
764 * (self.metrics.total_steps - 1) as f64
765 + steptime.as_secs_f64())
766 / self.metrics.total_steps as f64;
767 self.metrics.throughput = 1.0 / self.metrics.avg_step_time;
768 }
769}
770
771impl<A: Float + std::fmt::Debug + Send + Sync> BaseOptimizerState<A> {
772 fn new() -> Self {
773 Self {
774 step_count: 0,
775 param_count: 0,
776 lr_history: Vec::new(),
777 grad_norm_history: Vec::new(),
778 param_change_history: Vec::new(),
779 momentum_buffer: Vec::new(),
780 custom_state: HashMap::new(),
781 }
782 }
783}
784
785impl<A: Float + Debug + Send + Sync + 'static> OptimizerPlugin<A> for BaseOptimizerPlugin<A> {
786 fn step(&mut self, params: &Array1<A>, gradients: &Array1<A>) -> Result<Array1<A>> {
793 if params.len() != gradients.len() {
794 return Err(OptimError::DimensionMismatch(format!(
795 "parameter vector has {} elements but gradient vector has {}",
796 params.len(),
797 gradients.len()
798 )));
799 }
800 if params.is_empty() {
801 return Err(OptimError::InvalidConfig(
802 "cannot step an optimizer over zero parameters".to_string(),
803 ));
804 }
805
806 let lr = A::from(self.config.learning_rate).ok_or_else(|| {
807 OptimError::InvalidConfig(format!(
808 "learning rate {} could not be represented in the parameter type",
809 self.config.learning_rate
810 ))
811 })?;
812 let weight_decay = A::from(self.config.weight_decay).ok_or_else(|| {
813 OptimError::InvalidConfig(format!(
814 "weight decay {} could not be represented in the parameter type",
815 self.config.weight_decay
816 ))
817 })?;
818 let momentum = A::from(self.config.momentum).ok_or_else(|| {
819 OptimError::InvalidConfig(format!(
820 "momentum {} could not be represented in the parameter type",
821 self.config.momentum
822 ))
823 })?;
824
825 let mut effective_grad: Vec<A> = gradients
829 .iter()
830 .zip(params.iter())
831 .map(|(&g, &p)| g + weight_decay * p)
832 .collect();
833
834 let grad_norm = effective_grad
836 .iter()
837 .fold(A::zero(), |acc, &g| acc + g * g)
838 .sqrt();
839 if let Some(clip) = self.config.gradient_clip {
840 let clip_a = A::from(clip).ok_or_else(|| {
841 OptimError::InvalidConfig(format!(
842 "gradient clip {clip} could not be represented in the parameter type"
843 ))
844 })?;
845 if clip_a > A::zero() && grad_norm > clip_a {
846 let scale = clip_a / grad_norm;
847 for g in &mut effective_grad {
848 *g = *g * scale;
849 }
850 }
851 }
852
853 if self.state.momentum_buffer.len() != params.len() {
857 self.state.momentum_buffer = vec![A::zero(); params.len()];
858 }
859
860 let mut new_params = Array1::<A>::zeros(params.len());
861 let mut change_sq = A::zero();
862 for i in 0..params.len() {
863 let velocity = momentum * self.state.momentum_buffer[i] + effective_grad[i];
864 self.state.momentum_buffer[i] = velocity;
865 let delta = lr * velocity;
866 let updated = params[i] - delta;
867 new_params[i] = updated;
868 change_sq = change_sq + delta * delta;
869 }
870
871 self.state.step_count += 1;
872 self.state.param_count = params.len();
873 self.state.lr_history.push(lr);
874 self.state.grad_norm_history.push(grad_norm);
875 self.state.param_change_history.push(change_sq.sqrt());
876
877 for handler in &mut self.event_handlers {
878 let params_f64 = Array1::from_iter(params.iter().map(|p| p.to_f64().unwrap_or(0.0)));
879 let grad_f64 = Array1::from_iter(gradients.iter().map(|g| g.to_f64().unwrap_or(0.0)));
880 handler.on_step(self.state.step_count, ¶ms_f64, &grad_f64);
881 }
882
883 Ok(new_params)
884 }
885
886 fn name(&self) -> &str {
887 &self.info.name
888 }
889
890 fn version(&self) -> &str {
891 &self.info.version
892 }
893
894 fn plugin_info(&self) -> PluginInfo {
895 self.info.clone()
896 }
897
898 fn capabilities(&self) -> PluginCapabilities {
899 self.capabilities.clone()
900 }
901
902 fn initialize(&mut self, paramshape: &[usize]) -> Result<()> {
903 if paramshape.is_empty() {
904 return Err(OptimError::InvalidConfig(
905 "cannot initialize with zero parameter groups".to_string(),
906 ));
907 }
908 let total: usize = paramshape.iter().product();
909 self.state = BaseOptimizerState::new();
910 self.state.param_count = total;
911 self.state.momentum_buffer = vec![A::zero(); total];
912 Ok(())
913 }
914
915 fn reset(&mut self) -> Result<()> {
916 let param_count = self.state.param_count;
917 self.state = BaseOptimizerState::new();
918 self.state.param_count = param_count;
919 self.state.momentum_buffer = vec![A::zero(); param_count];
920 Ok(())
921 }
922
923 fn get_config(&self) -> OptimizerConfig {
924 self.config.clone()
925 }
926
927 fn set_config(&mut self, config: OptimizerConfig) -> Result<()> {
928 self.config = config;
929 Ok(())
930 }
931
932 fn get_state(&self) -> Result<OptimizerState> {
933 let mut state_vectors = HashMap::new();
934 state_vectors.insert(
935 "momentum_buffer".to_string(),
936 self.state
937 .momentum_buffer
938 .iter()
939 .map(|v| v.to_f64().unwrap_or(0.0))
940 .collect(),
941 );
942 Ok(OptimizerState {
943 state_vectors,
944 step_count: self.state.step_count,
945 custom_state: self.state.custom_state.clone(),
946 })
947 }
948
949 fn set_state(&mut self, state: OptimizerState) -> Result<()> {
950 self.state.step_count = state.step_count;
951 self.state.custom_state = state.custom_state;
952 if let Some(buf) = state.state_vectors.get("momentum_buffer") {
953 self.state.momentum_buffer = buf
954 .iter()
955 .map(|&v| A::from(v).unwrap_or_else(A::zero))
956 .collect();
957 self.state.param_count = self.state.momentum_buffer.len();
958 }
959 Ok(())
960 }
961
962 fn clone_plugin(&self) -> Box<dyn OptimizerPlugin<A>> {
963 Box::new(BaseOptimizerPlugin {
964 info: self.info.clone(),
965 capabilities: self.capabilities.clone(),
966 config: self.config.clone(),
967 state: self.state.clone(),
968 metrics: self.metrics.clone(),
969 memory_usage: self.memory_usage.clone(),
970 event_handlers: Vec::new(),
974 })
975 }
976
977 fn memory_usage(&self) -> MemoryUsage {
978 self.memory_usage.clone()
979 }
980
981 fn performance_metrics(&self) -> PerformanceMetrics {
982 self.metrics.clone()
983 }
984}
985
986impl Default for TestConfig {
989 fn default() -> Self {
990 Self {
991 iterations: 100,
992 tolerance: 1e-6,
993 random_seed: 42,
994 enable_performance_tests: true,
995 enable_memory_tests: true,
996 enable_convergence_tests: true,
997 }
998 }
999}
1000
1001impl Default for BenchmarkConfig {
1002 fn default() -> Self {
1003 Self {
1004 runs: 10,
1005 warmup_iterations: 5,
1006 problem_sizes: vec![10, 100, 1000],
1007 random_seeds: vec![42, 123, 456],
1008 }
1009 }
1010}
1011
1012#[macro_export]
1014macro_rules! create_optimizer_plugin {
1015 ($name:ident, $step_fn:expr) => {
1016 #[derive(Debug)]
1017 pub struct $name<A: Float> {
1018 config: OptimizerConfig,
1019 state: OptimizerState,
1020 _phantom: std::marker::PhantomData<A>,
1021 }
1022
1023 impl<A: Float + Send + Sync> $name<A> {
1024 pub fn new() -> Self {
1025 Self {
1026 config: OptimizerConfig::default(),
1027 state: OptimizerState::default(),
1028 _phantom: std::marker::PhantomData,
1029 }
1030 }
1031 }
1032
1033 impl<A: Float + Send + Sync> Default for $name<A> {
1034 fn default() -> Self {
1035 Self::new()
1036 }
1037 }
1038
1039 impl<A: Float + std::fmt::Debug + Send + Sync + 'static> OptimizerPlugin<A> for $name<A> {
1040 fn step(&mut self, params: &Array1<A>, gradients: &Array1<A>) -> Result<Array1<A>> {
1041 $step_fn(self, params, gradients)
1042 }
1043
1044 fn name(&self) -> &str {
1045 stringify!($name)
1046 }
1047
1048 fn version(&self) -> &str {
1049 "0.1.0"
1050 }
1051
1052 fn plugin_info(&self) -> PluginInfo {
1053 create_plugin_info(stringify!($name), "0.1.0", "Auto-generated")
1054 }
1055
1056 fn capabilities(&self) -> PluginCapabilities {
1057 create_basic_capabilities()
1058 }
1059
1060 fn initialize(&mut self, paramshape: &[usize]) -> Result<()> {
1061 if paramshape.is_empty() {
1062 return Err($crate::error::OptimError::InvalidConfig(
1063 "cannot initialize a plugin optimizer with zero parameter groups"
1064 .to_string(),
1065 ));
1066 }
1067 self.state.step_count = 0;
1071 Ok(())
1072 }
1073
1074 fn reset(&mut self) -> Result<()> {
1075 self.state = OptimizerState::default();
1076 Ok(())
1077 }
1078
1079 fn get_config(&self) -> OptimizerConfig {
1080 self.config.clone()
1081 }
1082
1083 fn set_config(&mut self, config: OptimizerConfig) -> Result<()> {
1084 self.config = config;
1085 Ok(())
1086 }
1087
1088 fn get_state(&self) -> Result<OptimizerState> {
1089 Ok(self.state.clone())
1090 }
1091
1092 fn set_state(&mut self, state: OptimizerState) -> Result<()> {
1093 self.state = state;
1094 Ok(())
1095 }
1096
1097 fn clone_plugin(&self) -> Box<dyn OptimizerPlugin<A>> {
1098 Box::new(Self {
1099 config: self.config.clone(),
1100 state: self.state.clone(),
1101 _phantom: std::marker::PhantomData,
1102 })
1103 }
1104 }
1105 };
1106}
1107
1108#[cfg(test)]
1109mod tests {
1110 use super::*;
1111
1112 create_optimizer_plugin!(MacroGeneratedSgd, |_this, params, gradients| {
1127 Ok(params - gradients)
1128 });
1129
1130 #[test]
1131 fn test_macro_generated_plugin_compiles_and_steps() {
1132 let mut plugin: MacroGeneratedSgd<f64> = MacroGeneratedSgd::new();
1138 plugin.initialize(&[3]).expect("initialize should succeed");
1139
1140 let params = Array1::from(vec![1.0_f64, 2.0, 3.0]);
1141 let grads = Array1::from(vec![0.1_f64, 0.2, 0.3]);
1142 let updated = plugin.step(¶ms, &grads).expect("step should succeed");
1143 assert_eq!(updated.len(), 3);
1144
1145 plugin
1148 .set_config(OptimizerConfig {
1149 learning_rate: 0.5,
1150 ..OptimizerConfig::default()
1151 })
1152 .expect("set_config should succeed");
1153 let cloned = plugin.clone_plugin();
1154 assert_eq!(cloned.get_config().learning_rate, 0.5);
1155 }
1156
1157 #[test]
1158 fn test_macro_generated_plugin_rejects_empty_paramshape() {
1159 let mut plugin: MacroGeneratedSgd<f64> = MacroGeneratedSgd::new();
1160 assert!(plugin.initialize(&[]).is_err());
1161 }
1162
1163 fn base_plugin() -> BaseOptimizerPlugin<f64> {
1172 BaseOptimizerPlugin::new(
1173 create_plugin_info("TestBase", "0.1.0", "test"),
1174 create_basic_capabilities(),
1175 )
1176 }
1177
1178 #[test]
1179 fn test_base_optimizer_plugin_implements_the_trait() {
1180 let mut owned = base_plugin();
1181 let plugin: &mut dyn OptimizerPlugin<f64> = &mut owned;
1182 plugin.initialize(&[3]).expect("initialize should succeed");
1183
1184 let params = Array1::from(vec![1.0_f64, 2.0, 3.0]);
1185 let grads = Array1::from(vec![0.1_f64, 0.2, 0.3]);
1186 let updated = plugin.step(¶ms, &grads).expect("step should succeed");
1187 assert_eq!(updated.len(), 3);
1188 for (u, p) in updated.iter().zip(params.iter()) {
1190 assert!(u < p, "expected descent step to reduce each parameter");
1191 }
1192 }
1193
1194 #[test]
1195 fn test_base_optimizer_plugin_rejects_dimension_mismatch() {
1196 let mut plugin = base_plugin();
1197 plugin.initialize(&[3]).expect("initialize should succeed");
1198 let params = Array1::from(vec![1.0_f64, 2.0, 3.0]);
1199 let grads = Array1::from(vec![0.1_f64, 0.2]);
1200 assert!(plugin.step(¶ms, &grads).is_err());
1201 }
1202
1203 #[test]
1204 fn test_base_optimizer_plugin_gradient_clipping_bounds_update_norm() {
1205 let mut plugin = base_plugin();
1206 plugin
1207 .set_config(OptimizerConfig {
1208 learning_rate: 1.0,
1209 weight_decay: 0.0,
1210 momentum: 0.0,
1211 gradient_clip: Some(1.0),
1212 ..OptimizerConfig::default()
1213 })
1214 .expect("set_config should succeed");
1215 plugin.initialize(&[2]).expect("initialize should succeed");
1216
1217 let params = Array1::from(vec![0.0_f64, 0.0]);
1218 let grads = Array1::from(vec![100.0_f64, 100.0]);
1220 let updated = plugin.step(¶ms, &grads).expect("step should succeed");
1221 let step_norm =
1222 ((updated[0] - params[0]).powi(2) + (updated[1] - params[1]).powi(2)).sqrt();
1223 assert!(
1224 step_norm <= 1.0 + 1e-9,
1225 "clipped step norm should not exceed the configured clip value, got {step_norm}"
1226 );
1227 }
1228
1229 #[test]
1230 fn test_base_optimizer_plugin_state_round_trips() {
1231 let mut plugin = base_plugin();
1232 plugin.initialize(&[2]).expect("initialize should succeed");
1233 let params = Array1::from(vec![1.0_f64, 1.0]);
1234 let grads = Array1::from(vec![0.5_f64, 0.5]);
1235 plugin.step(¶ms, &grads).expect("step should succeed");
1236
1237 let saved = plugin.get_state().expect("get_state should succeed");
1238 assert_eq!(saved.step_count, 1);
1239
1240 let mut restored = base_plugin();
1241 restored.set_state(saved).expect("set_state should succeed");
1242 assert_eq!(restored.get_state().unwrap().step_count, 1);
1243 }
1244
1245 #[test]
1246 fn test_base_optimizer_plugin_clone_preserves_config_and_state() {
1247 let mut plugin = base_plugin();
1248 plugin
1249 .set_config(OptimizerConfig {
1250 learning_rate: 0.25,
1251 ..OptimizerConfig::default()
1252 })
1253 .expect("set_config should succeed");
1254 plugin.initialize(&[2]).expect("initialize should succeed");
1255 let params = Array1::from(vec![1.0_f64, 1.0]);
1256 let grads = Array1::from(vec![0.5_f64, 0.5]);
1257 plugin.step(¶ms, &grads).expect("step should succeed");
1258
1259 let cloned = plugin.clone_plugin();
1260 assert_eq!(cloned.get_config().learning_rate, 0.25);
1261 assert_eq!(cloned.get_state().unwrap().step_count, 1);
1262 }
1263
1264 #[test]
1265 fn test_plugin_template_creation() {
1266 let template = PluginTemplate::new("TestOptimizer");
1267 assert_eq!(template.name, "TestOptimizer");
1268 assert!(!template.structure.source_files.is_empty());
1269 }
1270
1271 #[test]
1272 fn test_test_config_default() {
1273 let config = TestConfig::default();
1274 assert_eq!(config.iterations, 100);
1275 assert!(config.enable_performance_tests);
1276 }
1277
1278 #[test]
1279 fn test_sdk_config_validation() {
1280 let mut schema = ConfigSchema {
1281 fields: HashMap::new(),
1282 required_fields: vec!["test_field".to_string()],
1283 version: "1.0".to_string(),
1284 };
1285
1286 schema.fields.insert(
1287 "test_field".to_string(),
1288 FieldSchema {
1289 field_type: FieldType::Float {
1290 min: None,
1291 max: None,
1292 },
1293 description: "Test field".to_string(),
1294 default_value: None,
1295 constraints: Vec::new(),
1296 required: true,
1297 },
1298 );
1299
1300 assert!(PluginSDK::validate_config_schema(&schema).is_ok());
1301
1302 schema.fields.insert(
1304 "".to_string(),
1305 FieldSchema {
1306 field_type: FieldType::Float {
1307 min: None,
1308 max: None,
1309 },
1310 description: "Test".to_string(),
1311 default_value: None,
1312 constraints: Vec::new(),
1313 required: false,
1314 },
1315 );
1316
1317 assert!(PluginSDK::validate_config_schema(&schema).is_err());
1318 }
1319}