optirs_core/research/
experiments.rs

1// Experiment tracking and management for research projects
2//
3// This module provides comprehensive tools for designing, executing, and tracking
4// machine learning optimization experiments with full reproducibility support.
5
6use crate::error::{OptimError, Result};
7use crate::optimizers::*;
8use crate::unified_api::OptimizerConfig;
9use chrono::{DateTime, Utc};
10use scirs2_core::ndarray::{Array1, Array2};
11use scirs2_core::numeric::Float;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15/// Comprehensive experiment definition and tracking
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Experiment {
18    /// Experiment identifier
19    pub id: String,
20    /// Experiment name
21    pub name: String,
22    /// Research hypothesis
23    pub hypothesis: String,
24    /// Experiment description
25    pub description: String,
26    /// Experiment status
27    pub status: ExperimentStatus,
28    /// Experiment configuration
29    pub config: ExperimentConfig,
30    /// Optimizer configurations being tested
31    pub optimizer_configs: HashMap<String, OptimizerConfig<f64>>,
32    /// Dataset information
33    pub dataset_info: DatasetInfo,
34    /// Metrics to track
35    pub metrics: Vec<String>,
36    /// Experiment results
37    pub results: Vec<ExperimentResult>,
38    /// Reproducibility information
39    pub reproducibility: ReproducibilityInfo,
40    /// Experiment timeline
41    pub timeline: ExperimentTimeline,
42    /// Analysis notes
43    pub notes: Vec<ExperimentNote>,
44    /// Experiment metadata
45    pub metadata: ExperimentMetadata,
46}
47
48/// Experiment status
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
50pub enum ExperimentStatus {
51    /// Planning phase
52    Planning,
53    /// Ready to run
54    Ready,
55    /// Currently running
56    Running,
57    /// Completed successfully
58    Completed,
59    /// Failed with errors
60    Failed,
61    /// Paused/suspended
62    Paused,
63    /// Cancelled
64    Cancelled,
65    /// Under analysis
66    Analyzing,
67    /// Results published
68    Published,
69}
70
71/// Experiment configuration
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct ExperimentConfig {
74    /// Random seed for reproducibility
75    pub random_seed: u64,
76    /// Number of runs/repetitions
77    pub num_runs: usize,
78    /// Maximum training epochs
79    pub max_epochs: usize,
80    /// Early stopping criteria
81    pub early_stopping: Option<EarlyStoppingConfig>,
82    /// Hardware configuration
83    pub hardware_config: HardwareConfig,
84    /// Environment variables
85    pub environment: HashMap<String, String>,
86    /// Validation split
87    pub validation_split: f64,
88    /// Test split
89    pub test_split: f64,
90    /// Cross-validation folds
91    pub cv_folds: Option<usize>,
92}
93
94/// Early stopping configuration
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct EarlyStoppingConfig {
97    /// Metric to monitor
98    pub monitor_metric: String,
99    /// Patience (epochs without improvement)
100    pub patience: usize,
101    /// Minimum improvement threshold
102    pub min_improvement: f64,
103    /// Mode (minimize or maximize)
104    pub mode: OptimizationMode,
105}
106
107/// Optimization mode
108#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
109pub enum OptimizationMode {
110    /// Minimize the metric
111    Minimize,
112    /// Maximize the metric
113    Maximize,
114}
115
116/// Hardware configuration
117#[derive(Debug, Clone, Serialize, Deserialize, Default)]
118pub struct HardwareConfig {
119    /// CPU information
120    pub cpu_info: CpuInfo,
121    /// GPU information
122    pub gpu_info: Option<GpuInfo>,
123    /// Memory configuration
124    pub memory_config: MemoryConfig,
125    /// Parallel processing settings
126    pub parallel_config: ParallelConfig,
127}
128
129/// CPU information
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct CpuInfo {
132    /// CPU model
133    pub model: String,
134    /// Number of cores
135    pub cores: usize,
136    /// Number of threads
137    pub threads: usize,
138    /// CPU frequency (MHz)
139    pub frequency_mhz: u32,
140    /// Cache sizes
141    pub cache_sizes: Vec<String>,
142    /// SIMD capabilities
143    pub simd_capabilities: Vec<String>,
144}
145
146/// GPU information
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct GpuInfo {
149    /// GPU model
150    pub model: String,
151    /// Memory size (MB)
152    pub memory_mb: usize,
153    /// Compute capability
154    pub compute_capability: String,
155    /// CUDA version
156    pub cuda_version: Option<String>,
157    /// Driver version
158    pub driver_version: String,
159}
160
161/// Memory configuration
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct MemoryConfig {
164    /// Total system memory (MB)
165    pub total_memory_mb: usize,
166    /// Available memory (MB)
167    pub available_memory_mb: usize,
168    /// Memory allocation strategy
169    pub allocation_strategy: MemoryAllocationStrategy,
170    /// Memory pool size (MB)
171    pub pool_size_mb: Option<usize>,
172}
173
174/// Memory allocation strategies
175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
176pub enum MemoryAllocationStrategy {
177    /// Standard allocation
178    Standard,
179    /// Pool-based allocation
180    Pooled,
181    /// Memory-mapped allocation
182    MemoryMapped,
183    /// Compressed allocation
184    Compressed,
185}
186
187/// Parallel processing configuration
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct ParallelConfig {
190    /// Number of threads
191    pub num_threads: usize,
192    /// Thread affinity
193    pub thread_affinity: Option<Vec<usize>>,
194    /// Work stealing enabled
195    pub work_stealing: bool,
196    /// Chunk size for parallel operations
197    pub chunk_size: Option<usize>,
198}
199
200/// Dataset information
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct DatasetInfo {
203    /// Dataset name
204    pub name: String,
205    /// Dataset description
206    pub description: String,
207    /// Dataset source/URL
208    pub source: String,
209    /// Dataset version
210    pub version: String,
211    /// Number of samples
212    pub num_samples: usize,
213    /// Number of features
214    pub num_features: usize,
215    /// Number of classes (for classification)
216    pub num_classes: Option<usize>,
217    /// Data type
218    pub data_type: DataType,
219    /// Dataset statistics
220    pub statistics: DatasetStatistics,
221    /// Data preprocessing steps
222    pub preprocessing: Vec<PreprocessingStep>,
223}
224
225/// Data types
226#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
227pub enum DataType {
228    /// Tabular data
229    Tabular,
230    /// Image data
231    Image,
232    /// Text data
233    Text,
234    /// Audio data
235    Audio,
236    /// Video data
237    Video,
238    /// Time series data
239    TimeSeries,
240    /// Graph data
241    Graph,
242    /// Multi-modal data
243    MultiModal,
244}
245
246/// Dataset statistics
247#[derive(Debug, Clone, Serialize, Deserialize, Default)]
248pub struct DatasetStatistics {
249    /// Feature means
250    pub feature_means: Vec<f64>,
251    /// Feature standard deviations
252    pub feature_stds: Vec<f64>,
253    /// Feature ranges
254    pub feature_ranges: Vec<(f64, f64)>,
255    /// Class distribution (for classification)
256    pub class_distribution: Option<HashMap<String, usize>>,
257    /// Missing value counts
258    pub missing_values: Vec<usize>,
259    /// Correlation matrix
260    pub correlation_matrix: Option<Array2<f64>>,
261}
262
263/// Preprocessing step
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct PreprocessingStep {
266    /// Step name
267    pub name: String,
268    /// Step description
269    pub description: String,
270    /// Parameters
271    pub parameters: HashMap<String, serde_json::Value>,
272    /// Order in preprocessing pipeline
273    pub order: usize,
274}
275
276/// Experiment result for a single run
277#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct ExperimentResult {
279    /// Run identifier
280    pub run_id: String,
281    /// Optimizer name
282    pub optimizer_name: String,
283    /// Start timestamp
284    pub start_time: DateTime<Utc>,
285    /// End timestamp
286    pub end_time: Option<DateTime<Utc>>,
287    /// Run status
288    pub status: RunStatus,
289    /// Final metrics
290    pub final_metrics: HashMap<String, f64>,
291    /// Training history
292    pub training_history: TrainingHistory,
293    /// Resource usage
294    pub resource_usage: ResourceUsage,
295    /// Error information (if failed)
296    pub error_info: Option<String>,
297    /// Additional metadata
298    pub metadata: HashMap<String, serde_json::Value>,
299}
300
301/// Run status
302#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
303pub enum RunStatus {
304    /// Run completed successfully
305    Success,
306    /// Run failed with error
307    Failed,
308    /// Run was terminated early
309    Terminated,
310    /// Run timed out
311    Timeout,
312    /// Run was cancelled
313    Cancelled,
314}
315
316/// Training history tracking
317#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct TrainingHistory {
319    /// Epoch numbers
320    pub epochs: Vec<usize>,
321    /// Training metrics by epoch
322    pub train_metrics: HashMap<String, Vec<f64>>,
323    /// Validation metrics by epoch
324    pub val_metrics: HashMap<String, Vec<f64>>,
325    /// Learning rates by epoch
326    pub learning_rates: Vec<f64>,
327    /// Gradient norms by epoch
328    pub gradient_norms: Vec<f64>,
329    /// Parameter norms by epoch
330    pub parameter_norms: Vec<f64>,
331    /// Step times by epoch
332    pub step_times: Vec<f64>,
333}
334
335/// Resource usage tracking
336#[derive(Debug, Clone, Default, Serialize, Deserialize)]
337pub struct ResourceUsage {
338    /// Peak CPU usage (%)
339    pub peak_cpu_usage: f64,
340    /// Average CPU usage (%)
341    pub avg_cpu_usage: f64,
342    /// Peak memory usage (MB)
343    pub peak_memory_mb: usize,
344    /// Average memory usage (MB)
345    pub avg_memory_mb: usize,
346    /// Peak GPU memory usage (MB)
347    pub peak_gpu_memory_mb: Option<usize>,
348    /// Total training time (seconds)
349    pub total_time_seconds: f64,
350    /// Energy consumption (Joules)
351    pub energy_consumption_joules: Option<f64>,
352}
353
354/// Reproducibility information
355#[derive(Debug, Clone, Serialize, Deserialize, Default)]
356pub struct ReproducibilityInfo {
357    /// Environment hash for reproducibility
358    pub environment_hash: String,
359    /// Git commit hash
360    pub git_commit: Option<String>,
361    /// Code checksum
362    pub code_checksum: String,
363    /// Dependency versions
364    pub dependency_versions: HashMap<String, String>,
365    /// System information
366    pub system_info: SystemInfo,
367    /// Reproducibility checklist
368    pub checklist: ReproducibilityChecklist,
369}
370
371/// System information
372#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct SystemInfo {
374    /// Operating system
375    pub os: String,
376    /// OS version
377    pub os_version: String,
378    /// Architecture
379    pub architecture: String,
380    /// Hostname
381    pub hostname: String,
382    /// Username
383    pub username: String,
384    /// Timezone
385    pub timezone: String,
386}
387
388/// Reproducibility checklist
389#[derive(Debug, Clone, Serialize, Deserialize, Default)]
390pub struct ReproducibilityChecklist {
391    /// Random seed set
392    pub random_seed_set: bool,
393    /// Dependencies pinned
394    pub dependencies_pinned: bool,
395    /// Data version controlled
396    pub data_version_controlled: bool,
397    /// Code version controlled
398    pub code_version_controlled: bool,
399    /// Environment documented
400    pub environment_documented: bool,
401    /// Hardware documented
402    pub hardware_documented: bool,
403    /// Results archived
404    pub results_archived: bool,
405}
406
407/// Experiment timeline
408#[derive(Debug, Clone, Serialize, Deserialize)]
409pub struct ExperimentTimeline {
410    /// Created timestamp
411    pub created_at: DateTime<Utc>,
412    /// Started timestamp
413    pub started_at: Option<DateTime<Utc>>,
414    /// Completed timestamp
415    pub completed_at: Option<DateTime<Utc>>,
416    /// Estimated duration
417    pub estimated_duration: Option<chrono::Duration>,
418    /// Actual duration
419    pub actual_duration: Option<chrono::Duration>,
420}
421
422/// Experiment note
423#[derive(Debug, Clone, Serialize, Deserialize)]
424pub struct ExperimentNote {
425    /// Note timestamp
426    pub timestamp: DateTime<Utc>,
427    /// Note author
428    pub author: String,
429    /// Note content
430    pub content: String,
431    /// Note type
432    pub note_type: NoteType,
433    /// Associated run ID
434    pub run_id: Option<String>,
435}
436
437/// Note types
438#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
439pub enum NoteType {
440    /// General observation
441    Observation,
442    /// Issue or problem
443    Issue,
444    /// Solution or fix
445    Solution,
446    /// Hypothesis
447    Hypothesis,
448    /// Conclusion
449    Conclusion,
450    /// Question
451    Question,
452    /// Reminder
453    Reminder,
454}
455
456/// Experiment metadata
457#[derive(Debug, Clone, Serialize, Deserialize, Default)]
458pub struct ExperimentMetadata {
459    /// Experiment tags
460    pub tags: Vec<String>,
461    /// Research question
462    pub research_question: String,
463    /// Expected outcomes
464    pub expected_outcomes: Vec<String>,
465    /// Success criteria
466    pub success_criteria: Vec<String>,
467    /// Related experiments
468    pub related_experiments: Vec<String>,
469    /// References/citations
470    pub references: Vec<String>,
471}
472
473/// Experiment runner for executing experiments
474pub struct ExperimentRunner {
475    /// Current experiment
476    experiment: Experiment,
477    /// Resource monitor
478    resource_monitor: ResourceMonitor,
479    /// Progress callback
480    progress_callback: Option<Box<dyn Fn(f64) + Send + Sync>>,
481}
482
483impl std::fmt::Debug for ExperimentRunner {
484    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
485        f.debug_struct("ExperimentRunner")
486            .field("experiment", &self.experiment)
487            .field("resource_monitor", &self.resource_monitor)
488            .field("progress_callback", &self.progress_callback.is_some())
489            .finish()
490    }
491}
492
493/// Resource monitoring
494#[derive(Debug)]
495pub struct ResourceMonitor {
496    /// CPU usage history
497    cpu_usage: Vec<f64>,
498    /// Memory usage history  
499    memory_usage: Vec<usize>,
500    /// GPU memory usage history
501    gpu_memory_usage: Vec<Option<usize>>,
502    /// Monitoring interval (seconds)
503    interval_seconds: u64,
504}
505
506impl Experiment {
507    /// Create a new experiment
508    pub fn new(name: &str) -> Self {
509        let now = Utc::now();
510        Self {
511            id: uuid::Uuid::new_v4().to_string(),
512            name: name.to_string(),
513            hypothesis: String::new(),
514            description: String::new(),
515            status: ExperimentStatus::Planning,
516            config: ExperimentConfig::default(),
517            optimizer_configs: HashMap::new(),
518            dataset_info: DatasetInfo::default(),
519            metrics: Vec::new(),
520            results: Vec::new(),
521            reproducibility: ReproducibilityInfo::default(),
522            timeline: ExperimentTimeline {
523                created_at: now,
524                started_at: None,
525                completed_at: None,
526                estimated_duration: None,
527                actual_duration: None,
528            },
529            notes: Vec::new(),
530            metadata: ExperimentMetadata::default(),
531        }
532    }
533
534    /// Set experiment hypothesis
535    pub fn hypothesis(mut self, hypothesis: &str) -> Self {
536        self.hypothesis = hypothesis.to_string();
537        self
538    }
539
540    /// Set experiment description
541    pub fn description(mut self, description: &str) -> Self {
542        self.description = description.to_string();
543        self
544    }
545
546    /// Add optimizer configuration
547    pub fn add_optimizer_config(mut self, name: &str, config: OptimizerConfig<f64>) -> Self {
548        self.optimizer_configs.insert(name.to_string(), config);
549        self
550    }
551
552    /// Set dataset information
553    pub fn dataset(mut self, datasetinfo: DatasetInfo) -> Self {
554        self.dataset_info = datasetinfo;
555        self
556    }
557
558    /// Set metrics to track
559    pub fn metrics(mut self, metrics: Vec<String>) -> Self {
560        self.metrics = metrics;
561        self
562    }
563
564    /// Add a note to the experiment
565    pub fn add_note(&mut self, author: &str, content: &str, notetype: NoteType) {
566        let note = ExperimentNote {
567            timestamp: Utc::now(),
568            author: author.to_string(),
569            content: content.to_string(),
570            note_type: notetype,
571            run_id: None,
572        };
573        self.notes.push(note);
574    }
575
576    /// Start the experiment
577    pub fn start(&mut self) -> Result<()> {
578        if self.status != ExperimentStatus::Ready && self.status != ExperimentStatus::Planning {
579            return Err(OptimError::InvalidConfig(format!(
580                "Cannot start experiment in status {:?}",
581                self.status
582            )));
583        }
584
585        self.status = ExperimentStatus::Running;
586        self.timeline.started_at = Some(Utc::now());
587
588        Ok(())
589    }
590
591    /// Complete the experiment
592    pub fn complete(&mut self) -> Result<()> {
593        if self.status != ExperimentStatus::Running {
594            return Err(OptimError::InvalidConfig(format!(
595                "Cannot complete experiment in status {:?}",
596                self.status
597            )));
598        }
599
600        self.status = ExperimentStatus::Completed;
601        self.timeline.completed_at = Some(Utc::now());
602
603        if let (Some(start), Some(end)) = (self.timeline.started_at, self.timeline.completed_at) {
604            self.timeline.actual_duration = Some(end - start);
605        }
606
607        Ok(())
608    }
609
610    /// Generate experiment report
611    pub fn generate_report(&self) -> String {
612        let mut report = String::new();
613
614        report.push_str(&format!("# Experiment Report: {}\n\n", self.name));
615        report.push_str(&format!("**ID**: {}\n", self.id));
616        report.push_str(&format!("**Status**: {:?}\n", self.status));
617        report.push_str(&format!("**Hypothesis**: {}\n\n", self.hypothesis));
618
619        if !self.description.is_empty() {
620            report.push_str(&format!("## Description\n\n{}\n\n", self.description));
621        }
622
623        report.push_str("## Configuration\n\n");
624        report.push_str(&format!("- **Random Seed**: {}\n", self.config.random_seed));
625        report.push_str(&format!("- **Number of Runs**: {}\n", self.config.num_runs));
626        report.push_str(&format!("- **Max Epochs**: {}\n", self.config.max_epochs));
627
628        report.push_str("\n## Optimizers\n\n");
629        for name in self.optimizer_configs.keys() {
630            report.push_str(&format!("- {}\n", name));
631        }
632
633        report.push_str("\n## Dataset\n\n");
634        report.push_str(&format!("- **Name**: {}\n", self.dataset_info.name));
635        report.push_str(&format!(
636            "- **Samples**: {}\n",
637            self.dataset_info.num_samples
638        ));
639        report.push_str(&format!(
640            "- **Features**: {}\n",
641            self.dataset_info.num_features
642        ));
643
644        report.push_str("\n## Results\n\n");
645        report.push_str(&format!("**Total Runs**: {}\n\n", self.results.len()));
646
647        // Group results by optimizer
648        let mut optimizer_results: HashMap<String, Vec<&ExperimentResult>> = HashMap::new();
649        for result in &self.results {
650            optimizer_results
651                .entry(result.optimizer_name.clone())
652                .or_default()
653                .push(result);
654        }
655
656        for (optimizer, results) in optimizer_results {
657            report.push_str(&format!("### {}\n\n", optimizer));
658
659            if !results.is_empty() {
660                // Calculate statistics
661                let successful_runs: Vec<&ExperimentResult> = results
662                    .iter()
663                    .filter(|r| r.status == RunStatus::Success)
664                    .copied()
665                    .collect();
666
667                report.push_str(&format!(
668                    "- **Successful Runs**: {}/{}\n",
669                    successful_runs.len(),
670                    results.len()
671                ));
672
673                if !successful_runs.is_empty() {
674                    for metric in &self.metrics {
675                        if let Some(values) = self.get_metric_values(&successful_runs, metric) {
676                            let mean = values.iter().sum::<f64>() / values.len() as f64;
677                            let std = (values.iter().map(|v| (v - mean).powi(2)).sum::<f64>()
678                                / values.len() as f64)
679                                .sqrt();
680                            report
681                                .push_str(&format!("- **{}**: {:.4} ± {:.4}\n", metric, mean, std));
682                        }
683                    }
684                }
685            }
686            report.push('\n');
687        }
688
689        if !self.notes.is_empty() {
690            report.push_str("## Notes\n\n");
691            for note in &self.notes {
692                report.push_str(&format!(
693                    "**{}** ({}): {}\n\n",
694                    note.author,
695                    note.timestamp.format("%Y-%m-%d %H:%M"),
696                    note.content
697                ));
698            }
699        }
700
701        report
702    }
703
704    fn get_metric_values(&self, results: &[&ExperimentResult], metric: &str) -> Option<Vec<f64>> {
705        let mut values = Vec::new();
706        for result in results {
707            if let Some(&value) = result.final_metrics.get(metric) {
708                values.push(value);
709            }
710        }
711        if values.is_empty() {
712            None
713        } else {
714            Some(values)
715        }
716    }
717}
718
719impl Default for ExperimentConfig {
720    fn default() -> Self {
721        Self {
722            random_seed: 42,
723            num_runs: 1,
724            max_epochs: 100,
725            early_stopping: None,
726            hardware_config: HardwareConfig::default(),
727            environment: HashMap::new(),
728            validation_split: 0.2,
729            test_split: 0.1,
730            cv_folds: None,
731        }
732    }
733}
734
735impl Default for CpuInfo {
736    fn default() -> Self {
737        Self {
738            model: "Unknown".to_string(),
739            cores: std::thread::available_parallelism()
740                .map(|p| p.get())
741                .unwrap_or(1),
742            threads: std::thread::available_parallelism()
743                .map(|p| p.get())
744                .unwrap_or(1),
745            frequency_mhz: 0,
746            cache_sizes: Vec::new(),
747            simd_capabilities: Vec::new(),
748        }
749    }
750}
751
752impl Default for MemoryConfig {
753    fn default() -> Self {
754        Self {
755            total_memory_mb: 8192,     // 8GB default
756            available_memory_mb: 6144, // 6GB default
757            allocation_strategy: MemoryAllocationStrategy::Standard,
758            pool_size_mb: None,
759        }
760    }
761}
762
763impl Default for ParallelConfig {
764    fn default() -> Self {
765        Self {
766            num_threads: std::thread::available_parallelism()
767                .map(|p| p.get())
768                .unwrap_or(1),
769            thread_affinity: None,
770            work_stealing: true,
771            chunk_size: None,
772        }
773    }
774}
775
776impl Default for DatasetInfo {
777    fn default() -> Self {
778        Self {
779            name: "Unknown".to_string(),
780            description: String::new(),
781            source: String::new(),
782            version: "1.0".to_string(),
783            num_samples: 0,
784            num_features: 0,
785            num_classes: None,
786            data_type: DataType::Tabular,
787            statistics: DatasetStatistics::default(),
788            preprocessing: Vec::new(),
789        }
790    }
791}
792
793impl Default for SystemInfo {
794    fn default() -> Self {
795        Self {
796            os: std::env::consts::OS.to_string(),
797            os_version: String::new(),
798            architecture: std::env::consts::ARCH.to_string(),
799            hostname: String::new(),
800            username: std::env::var("USER").unwrap_or_else(|_| "unknown".to_string()),
801            timezone: String::new(),
802        }
803    }
804}
805
806impl ResourceMonitor {
807    /// Create a new resource monitor
808    pub fn new(_intervalseconds: u64) -> Self {
809        Self {
810            cpu_usage: Vec::new(),
811            memory_usage: Vec::new(),
812            gpu_memory_usage: Vec::new(),
813            interval_seconds: _intervalseconds,
814        }
815    }
816
817    /// Start monitoring resources
818    pub fn start_monitoring(&mut self) {
819        // Implementation would use system monitoring libraries
820        // This is a placeholder for the actual monitoring logic
821    }
822
823    /// Stop monitoring and return resource usage summary
824    pub fn stop_monitoring(&self) -> ResourceUsage {
825        let peak_cpu = self.cpu_usage.iter().fold(0.0f64, |a, &b| a.max(b));
826        let avg_cpu = if self.cpu_usage.is_empty() {
827            0.0
828        } else {
829            self.cpu_usage.iter().sum::<f64>() / self.cpu_usage.len() as f64
830        };
831
832        let peak_memory = self.memory_usage.iter().fold(0usize, |a, &b| a.max(b));
833        let avg_memory = if self.memory_usage.is_empty() {
834            0
835        } else {
836            self.memory_usage.iter().sum::<usize>() / self.memory_usage.len()
837        };
838
839        ResourceUsage {
840            peak_cpu_usage: peak_cpu,
841            avg_cpu_usage: avg_cpu,
842            peak_memory_mb: peak_memory,
843            avg_memory_mb: avg_memory,
844            peak_gpu_memory_mb: None, // Would be calculated from gpu_memory_usage
845            total_time_seconds: 0.0,  // Would be calculated from monitoring duration
846            energy_consumption_joules: None,
847        }
848    }
849}
850
851#[cfg(test)]
852mod tests {
853    use super::*;
854
855    #[test]
856    fn test_experiment_creation() {
857        let experiment = Experiment::new("Test Experiment")
858            .hypothesis("Test hypothesis")
859            .description("Test description")
860            .metrics(vec!["accuracy".to_string(), "loss".to_string()]);
861
862        assert_eq!(experiment.name, "Test Experiment");
863        assert_eq!(experiment.hypothesis, "Test hypothesis");
864        assert_eq!(experiment.description, "Test description");
865        assert_eq!(experiment.metrics.len(), 2);
866        assert_eq!(experiment.status, ExperimentStatus::Planning);
867    }
868
869    #[test]
870    fn test_experiment_lifecycle() {
871        let mut experiment = Experiment::new("Lifecycle Test");
872
873        // Start experiment
874        experiment.status = ExperimentStatus::Ready;
875        assert!(experiment.start().is_ok());
876        assert_eq!(experiment.status, ExperimentStatus::Running);
877        assert!(experiment.timeline.started_at.is_some());
878
879        // Complete experiment
880        assert!(experiment.complete().is_ok());
881        assert_eq!(experiment.status, ExperimentStatus::Completed);
882        assert!(experiment.timeline.completed_at.is_some());
883        assert!(experiment.timeline.actual_duration.is_some());
884    }
885
886    #[test]
887    fn test_experiment_notes() {
888        let mut experiment = Experiment::new("Notes Test");
889
890        experiment.add_note("Researcher", "Initial observation", NoteType::Observation);
891        experiment.add_note("Researcher", "Found an issue", NoteType::Issue);
892
893        assert_eq!(experiment.notes.len(), 2);
894        assert_eq!(experiment.notes[0].note_type, NoteType::Observation);
895        assert_eq!(experiment.notes[1].note_type, NoteType::Issue);
896    }
897}