Skip to main content

optirs_core/coordination/
mod.rs

1// Coordination module for optimization processes
2//
3// This module provides comprehensive coordination capabilities for optimization
4// workflows, including task scheduling, pipeline orchestration, and monitoring.
5// It replaces the monolithic optimization_coordinator.rs with a modular architecture.
6
7#[allow(dead_code)]
8use crate::coordination::monitoring::anomaly_detection::{AnomalyConfig, AnomalyResult};
9use crate::coordination::monitoring::performance_tracking::{
10    DashboardConfiguration, TrackerConfiguration,
11};
12use crate::coordination::orchestration::pipeline_orchestrator::{
13    ExecutionState, OrchestratorConfiguration,
14};
15use crate::coordination::scheduling::task_scheduler::SchedulerConfig;
16use crate::research::experiments::ResourceUsage;
17use scirs2_core::numeric::Float;
18use std::collections::HashMap;
19use std::fmt::Debug;
20use std::marker::PhantomData;
21use std::time::{Duration, Instant, SystemTime};
22
23// Submodule declarations
24pub mod monitoring;
25pub mod orchestration;
26pub mod scheduling;
27
28// Re-export key types from submodules
29pub use scheduling::{
30    PriorityLevel, PriorityManager, PriorityQueue, PriorityUpdateStrategy,
31    ResourceAllocationStrategy, ResourceAllocationTracker, ResourceManager, ResourcePool,
32    ScheduledTask, SchedulingStrategy, StaticPriorityStrategy, TaskPriority, TaskScheduler,
33};
34
35// Type alias for convenience
36pub type OptimizationTask<T> = ScheduledTask<T>;
37
38pub use orchestration::{
39    AlertConfiguration, Checkpoint, CheckpointConfiguration, CheckpointManager, CheckpointMetadata,
40    CheckpointStorage, Experiment, ExperimentConfiguration, ExperimentExecution, ExperimentManager,
41    ExperimentResult, ExperimentStatus, FileCheckpointStorage, InMemoryCheckpointStorage,
42    MonitoringConfiguration, OptimizationPipeline, PipelineConfiguration, PipelineExecution,
43    PipelineOrchestrator, PipelineStage, RecoveryManager, RecoveryOptions, RecoveryStrategy,
44    RecoveryTarget, ResourceLimits, StageResult, StateType, StorageConfiguration, TimeoutSettings,
45    ValidationRule,
46};
47
48pub use monitoring::{
49    AlertManager, AnomalyAlert, AnomalyAnalyzer, AnomalyClassifier, AnomalyDetector,
50    AnomalyReporter, ConvergenceAnalyzer, ConvergenceCriteria, ConvergenceDetector,
51    ConvergenceIndicator, ConvergenceMonitor, ConvergenceResult, MetricCollector, OutlierDetector,
52    PerformanceAlert, PerformanceMetrics, PerformanceTracker,
53};
54
55/// Main coordination manager that integrates all coordination components
56pub struct OptimizationCoordinator<T: Float + Debug + Send + Sync + 'static> {
57    scheduler: TaskScheduler<T>,
58    orchestrator: PipelineOrchestrator<T>,
59    performance_tracker: PerformanceTracker<T>,
60    convergence_detector: ConvergenceDetector<T>,
61    anomaly_detector: AnomalyDetector<T>,
62    config: CoordinatorConfig<T>,
63    state: CoordinatorState<T>,
64    metrics: CoordinatorMetrics<T>,
65    _phantom: PhantomData<T>,
66}
67
68/// Configuration for the optimization coordinator
69#[derive(Debug)]
70pub struct CoordinatorConfig<T: Float + Debug + Send + Sync + 'static> {
71    pub max_concurrent_tasks: usize,
72    pub default_timeout: Duration,
73    pub monitoring_interval: Duration,
74    pub checkpoint_interval: Duration,
75    pub resource_allocation_strategy: ResourceAllocationStrategy,
76    pub priority_strategy: Box<dyn PriorityUpdateStrategy<T>>,
77    pub convergence_criteria: ConvergenceCriteria<T>,
78    pub enable_anomaly_detection: bool,
79    pub enable_auto_scaling: bool,
80    pub enable_fault_tolerance: bool,
81    pub performance_threshold: T,
82}
83
84impl<T: Float + Debug + Send + Sync + 'static> Default for CoordinatorConfig<T> {
85    fn default() -> Self {
86        Self {
87            max_concurrent_tasks: 10,
88            default_timeout: Duration::from_secs(3600),
89            monitoring_interval: Duration::from_secs(10),
90            checkpoint_interval: Duration::from_secs(300),
91            resource_allocation_strategy: ResourceAllocationStrategy::FairShare,
92            priority_strategy: Box::new(StaticPriorityStrategy),
93            convergence_criteria: ConvergenceCriteria::default(),
94            enable_anomaly_detection: true,
95            enable_auto_scaling: true,
96            enable_fault_tolerance: true,
97            performance_threshold: T::from(0.01).unwrap_or_else(|| T::zero()),
98        }
99    }
100}
101
102/// Internal state of the coordination manager
103#[derive(Debug)]
104pub struct CoordinatorState<T: Float + Debug + Send + Sync + 'static> {
105    pub active_tasks: HashMap<String, OptimizationTask<T>>,
106    pub active_pipelines: HashMap<String, OptimizationPipeline<T>>,
107    /// Orchestrator execution id per submitted pipeline id, so a caller can ask
108    /// the orchestrator for the pipeline's real state.
109    pub pipeline_execution_ids: HashMap<String, String>,
110    /// Pipeline id each submitted experiment was converted into. Without this
111    /// the pipeline id returned by `submit_pipeline` was dropped on the floor
112    /// and an experiment's execution could not be located afterwards.
113    pub experiment_pipeline_ids: HashMap<String, String>,
114    pub active_experiments: HashMap<String, Experiment<T>>,
115    pub resource_usage: ResourceUsage,
116    pub last_checkpoint: Option<Instant>,
117    pub last_monitoring_update: Option<Instant>,
118    pub coordination_start_time: Instant,
119    pub total_tasks_processed: usize,
120    pub total_experiments_completed: usize,
121    /// Total convergence checks performed via `monitor_optimization_value`.
122    pub convergence_checks_total: usize,
123    /// Of those, how many reported `converged == true`.
124    pub convergence_checks_passed: usize,
125    /// Total anomaly checks performed via `monitor_optimization_value`.
126    pub anomaly_checks_total: usize,
127    /// Of those, how many flagged `is_anomaly == true`.
128    pub anomaly_checks_flagged: usize,
129}
130
131impl<T: Float + Debug + Send + Sync + 'static> Default for CoordinatorState<T> {
132    fn default() -> Self {
133        Self::new()
134    }
135}
136
137impl<T: Float + Debug + Send + Sync + 'static> CoordinatorState<T> {
138    pub fn new() -> Self {
139        Self {
140            active_tasks: HashMap::new(),
141            active_pipelines: HashMap::new(),
142            pipeline_execution_ids: HashMap::new(),
143            experiment_pipeline_ids: HashMap::new(),
144            active_experiments: HashMap::new(),
145            resource_usage: ResourceUsage::default(),
146            last_checkpoint: None,
147            last_monitoring_update: None,
148            coordination_start_time: Instant::now(),
149            total_tasks_processed: 0,
150            total_experiments_completed: 0,
151            convergence_checks_total: 0,
152            convergence_checks_passed: 0,
153            anomaly_checks_total: 0,
154            anomaly_checks_flagged: 0,
155        }
156    }
157}
158
159/// Metrics for coordination performance
160#[derive(Debug, Clone)]
161pub struct CoordinatorMetrics<T: Float + Debug + Send + Sync + 'static> {
162    pub average_task_completion_time: T,
163    pub throughput: T,
164    pub resource_utilization: T,
165    pub error_rate: T,
166    pub convergence_rate: T,
167    pub anomaly_detection_rate: T,
168    pub uptime: Duration,
169    pub total_processed_tasks: usize,
170}
171
172impl<T: Float + Debug + Send + Sync + 'static> Default for CoordinatorMetrics<T> {
173    fn default() -> Self {
174        Self {
175            average_task_completion_time: T::zero(),
176            throughput: T::zero(),
177            resource_utilization: T::zero(),
178            error_rate: T::zero(),
179            convergence_rate: T::zero(),
180            anomaly_detection_rate: T::zero(),
181            uptime: Duration::new(0, 0),
182            total_processed_tasks: 0,
183        }
184    }
185}
186
187/// Result of coordination operations
188#[derive(Debug, Clone)]
189pub struct CoordinationResult<T: Float + Debug + Send + Sync + 'static> {
190    pub success: bool,
191    pub task_id: String,
192    pub execution_time: Duration,
193    pub resource_usage: ResourceUsage,
194    pub performance_metrics: PerformanceMetrics<T>,
195    pub convergence_result: Option<ConvergenceResult<T>>,
196    pub anomaly_alerts: Vec<AnomalyAlert<T>>,
197    pub errors: Vec<String>,
198}
199
200impl<T: Float + Debug + Send + Sync + 'static + Default> OptimizationCoordinator<T> {
201    /// Create a new optimization coordinator
202    ///
203    /// # Errors
204    /// Returns `Err` (as a descriptive `String`, matching this type's other
205    /// public methods) if any underlying scheduler, orchestrator, or
206    /// performance tracker fails to construct, or if `0.9` cannot be
207    /// represented in the target float type `T`.
208    pub fn new(config: CoordinatorConfig<T>) -> Result<Self, String> {
209        let estimation_threshold = T::from(0.9)
210            .ok_or_else(|| "failed to represent 0.9 in target float type".to_string())?;
211
212        let scheduler = TaskScheduler::new(SchedulerConfig {
213            max_concurrent_tasks: config.max_concurrent_tasks,
214            queue_size_limit: 1000,
215            task_timeout: config.default_timeout,
216            priority_update_interval: Duration::from_secs(5),
217            load_balance_interval: Duration::from_secs(10),
218            estimation_threshold,
219            enable_adaptive_scheduling: true,
220            enable_performance_learning: true,
221        })
222        .map_err(|e| format!("Failed to create task scheduler: {e}"))?;
223
224        let orchestrator = PipelineOrchestrator::new(OrchestratorConfiguration {
225            max_concurrent_pipelines: config.max_concurrent_tasks,
226            default_resource_limits: ResourceLimits::default(),
227            default_timeouts: TimeoutSettings::default(),
228            monitoring: MonitoringConfiguration::default(),
229        })
230        .map_err(|e| format!("Failed to create pipeline orchestrator: {e}"))?;
231
232        let performance_tracker = PerformanceTracker::new(TrackerConfiguration {
233            collection_interval: config.monitoring_interval,
234            enabled_collectors: vec!["default".to_string()],
235            enabled_analyzers: vec!["default".to_string()],
236            storage_config: StorageConfiguration::default(),
237            alert_config: AlertConfiguration::default(),
238            dashboard_config: DashboardConfiguration {
239                theme: String::from("default"),
240                auto_refresh: true,
241                default_time_range: Duration::from_secs(3600),
242                custom_params: HashMap::new(),
243            },
244        })
245        .map_err(|e| format!("Failed to create performance tracker: {e}"))?;
246
247        let convergence_detector = ConvergenceDetector::new(config.convergence_criteria.clone());
248
249        let anomaly_detector = AnomalyDetector::new(AnomalyConfig::default());
250
251        Ok(Self {
252            scheduler,
253            orchestrator,
254            performance_tracker,
255            convergence_detector,
256            anomaly_detector,
257            state: CoordinatorState::new(),
258            metrics: CoordinatorMetrics::default(),
259            config,
260            _phantom: PhantomData,
261        })
262    }
263
264    /// Submit a single optimization task
265    pub fn submit_task(&mut self, mut task: OptimizationTask<T>) -> Result<String, String> {
266        // Generate unique task ID
267        let task_id = format!(
268            "task_{}_{}",
269            self.state.total_tasks_processed,
270            Instant::now().elapsed().as_nanos()
271        );
272        task.task_id = task_id.clone();
273
274        // Schedule the task
275        match self.scheduler.submit_task(task.clone()) {
276            Ok(()) => {
277                self.state.active_tasks.insert(task_id.clone(), task);
278                self.state.total_tasks_processed += 1;
279
280                // Start performance monitoring for the task
281                // Performance tracking is handled by collectors
282
283                Ok(task_id)
284            }
285            Err(e) => Err(format!("Failed to schedule task: {}", e)),
286        }
287    }
288
289    /// Submit an optimization pipeline
290    pub fn submit_pipeline(
291        &mut self,
292        mut pipeline: OptimizationPipeline<T>,
293    ) -> Result<String, String> {
294        let pipeline_id = format!(
295            "pipeline_{}_{}",
296            self.state.active_pipelines.len(),
297            Instant::now().elapsed().as_nanos()
298        );
299
300        pipeline.pipeline_id = pipeline_id.clone();
301
302        // Hand the pipeline to the orchestrator. Until 0.3.2 this block read
303        // `let execution_result: Result<(), String> = Ok(());` -- a hardcoded
304        // success with the comment "needs proper orchestrator API" -- so
305        // `submit_pipeline` reported that every pipeline had been executed while
306        // the `orchestrator` field was never touched at all. The orchestrator's
307        // `execute_pipeline` has been there the whole time; it returns the
308        // execution id, which is now recorded on the pipeline's state entry.
309        let execution_id = self
310            .orchestrator
311            .execute_pipeline(pipeline.clone())
312            .map_err(|err| format!("Failed to execute pipeline: {err}"))?;
313        self.state
314            .active_pipelines
315            .insert(pipeline_id.clone(), pipeline);
316        self.state
317            .pipeline_execution_ids
318            .insert(pipeline_id.clone(), execution_id);
319        Ok(pipeline_id)
320    }
321
322    /// Execution id the orchestrator assigned to a submitted pipeline.
323    pub fn pipeline_execution_id(&self, pipeline_id: &str) -> Option<&str> {
324        self.state
325            .pipeline_execution_ids
326            .get(pipeline_id)
327            .map(String::as_str)
328    }
329
330    /// Current orchestrator-reported state of a submitted pipeline.
331    pub fn pipeline_execution_status(&self, pipeline_id: &str) -> Option<ExecutionState> {
332        let execution_id = self.state.pipeline_execution_ids.get(pipeline_id)?;
333        self.orchestrator.get_execution_status(execution_id)
334    }
335
336    /// Submit an experiment
337    pub fn submit_experiment(&mut self, mut experiment: Experiment<T>) -> Result<String, String> {
338        let experiment_id = format!(
339            "experiment_{}_{}",
340            self.state.active_experiments.len(),
341            Instant::now().elapsed().as_nanos()
342        );
343
344        experiment.experiment_id = experiment_id.clone();
345
346        // Convert experiment to pipeline for execution
347        let pipeline = self.experiment_to_pipeline(&experiment)?;
348        let pipeline_id = self.submit_pipeline(pipeline)?;
349
350        self.state
351            .active_experiments
352            .insert(experiment_id.clone(), experiment);
353        self.state
354            .experiment_pipeline_ids
355            .insert(experiment_id.clone(), pipeline_id);
356
357        Ok(experiment_id)
358    }
359
360    /// Pipeline id an experiment was converted into, if it was submitted.
361    pub fn experiment_pipeline_id(&self, experiment_id: &str) -> Option<&str> {
362        self.state
363            .experiment_pipeline_ids
364            .get(experiment_id)
365            .map(String::as_str)
366    }
367
368    /// Execute a coordination cycle
369    pub fn execute_cycle(&mut self) -> Vec<CoordinationResult<T>> {
370        let mut results = Vec::new();
371
372        // Update monitoring
373        self.update_monitoring();
374
375        // Process scheduled tasks
376        let task_results = self.process_scheduled_tasks();
377        results.extend(task_results);
378
379        // Update pipeline executions
380        self.update_pipeline_executions();
381
382        // Perform maintenance operations
383        self.perform_maintenance();
384
385        // Update metrics
386        self.update_metrics();
387
388        results
389    }
390
391    /// Monitor optimization value for convergence and anomalies
392    pub fn monitor_optimization_value(&mut self, task_id: &str, value: T) -> MonitoringResult<T> {
393        let mut alerts = Vec::new();
394
395        // Update performance tracking
396        let _ = self.performance_tracker.collect_metrics();
397
398        // Check for convergence
399        let convergence_result = self.convergence_detector.check_convergence(value);
400
401        // Check for anomalies if enabled
402        let anomaly_result = if self.config.enable_anomaly_detection {
403            Some(self.anomaly_detector.detect_anomaly(value))
404        } else {
405            None
406        };
407
408        // Generate alerts based on monitoring results
409        if let Some(ref anomaly) = anomaly_result {
410            self.state.anomaly_checks_total += 1;
411            if anomaly.is_anomaly {
412                self.state.anomaly_checks_flagged += 1;
413                alerts.push(MonitoringAlert::Anomaly(anomaly.clone()));
414            }
415        }
416
417        self.state.convergence_checks_total += 1;
418        if convergence_result.converged {
419            self.state.convergence_checks_passed += 1;
420            alerts.push(MonitoringAlert::Convergence(convergence_result.clone()));
421        }
422
423        MonitoringResult {
424            task_id: task_id.to_string(),
425            value,
426            convergence_result,
427            anomaly_result,
428            alerts,
429            timestamp: Instant::now(),
430        }
431    }
432
433    /// Get current coordination status
434    pub fn get_status(&self) -> CoordinationStatus<T> {
435        CoordinationStatus {
436            active_tasks: self.state.active_tasks.len(),
437            active_pipelines: self.state.active_pipelines.len(),
438            active_experiments: self.state.active_experiments.len(),
439            resource_utilization: self.state.resource_usage.clone(),
440            metrics: self.metrics.clone(),
441            uptime: self.state.coordination_start_time.elapsed(),
442            health_status: self.assess_health_status(),
443        }
444    }
445
446    /// Update resource allocation
447    pub fn update_resource_allocation(&mut self, allocation: ResourceUsage) -> Result<(), String> {
448        self.state.resource_usage = allocation;
449
450        // Update scheduler with new resource information
451        // Update resource availability - needs proper implementation
452        // self.scheduler.update_resources(&self.state.resource_usage);
453
454        // Update orchestrator
455        // Update resource allocation - needs proper implementation
456        // self.orchestrator.update_resources(&self.state.resource_usage);
457
458        Ok(())
459    }
460
461    /// Shutdown coordination gracefully
462    pub fn shutdown(&mut self) -> Result<(), String> {
463        // Complete active tasks
464        self.complete_active_tasks()?;
465
466        // Save checkpoints
467        self.save_final_checkpoints()?;
468
469        // Generate final report
470        let report = self.generate_final_report();
471        println!("Coordination shutdown report:\n{}", report);
472
473        Ok(())
474    }
475
476    // Private helper methods
477
478    fn update_monitoring(&mut self) {
479        let now = Instant::now();
480
481        if let Some(last_update) = self.state.last_monitoring_update {
482            if now.duration_since(last_update) < self.config.monitoring_interval {
483                return;
484            }
485        }
486
487        // Update performance metrics
488        let _ = self.performance_tracker.collect_metrics();
489
490        // Check for system-level anomalies
491        if self.config.enable_anomaly_detection {
492            let system_metrics = self.collect_system_metrics();
493            for metric in system_metrics {
494                let _ = self.anomaly_detector.detect_anomaly(metric);
495            }
496        }
497
498        self.state.last_monitoring_update = Some(now);
499    }
500
501    fn process_scheduled_tasks(&mut self) -> Vec<CoordinationResult<T>> {
502        let mut results = Vec::new();
503
504        // Pull every task the scheduler is currently willing to hand out
505        // (respecting its configured scheduling strategy) and dispatch each
506        // one for real, instead of iterating over a permanently-empty list.
507        while let Some(task) = self.scheduler.get_next_task() {
508            let task_id = task.task_id.clone();
509            match self.execute_task(&task) {
510                Ok(result) => {
511                    results.push(result);
512                    self.state.active_tasks.remove(&task_id);
513                }
514                Err(e) => {
515                    self.state.active_tasks.remove(&task_id);
516                    results.push(CoordinationResult {
517                        success: false,
518                        task_id,
519                        execution_time: Duration::new(0, 0),
520                        resource_usage: ResourceUsage::default(),
521                        performance_metrics: PerformanceMetrics::default(),
522                        convergence_result: None,
523                        anomaly_alerts: Vec::new(),
524                        errors: vec![e],
525                    });
526                }
527            }
528        }
529
530        results
531    }
532
533    fn execute_task(
534        &mut self,
535        task: &OptimizationTask<T>,
536    ) -> Result<CoordinationResult<T>, String> {
537        let start_time = Instant::now();
538        let task_id = task.task_id.clone();
539
540        // Dispatch through the scheduler's real execution lifecycle (start
541        // -> do the work this layer is responsible for -> complete) instead
542        // of being a no-op that always reports success without doing
543        // anything checkable.
544        let assigned_resources =
545            crate::coordination::scheduling::task_scheduler::AssignedResources {
546                cpu_cores: (0..task.resource_requirements.cpu_cores).collect(),
547                memory_mb: task.resource_requirements.memory_mb,
548                gpu_devices: (0..task.resource_requirements.gpu_devices).collect(),
549                storage_gb: task.resource_requirements.storage_gb,
550                network_bandwidth: task.resource_requirements.network_bandwidth,
551            };
552        self.scheduler
553            .start_task_execution(task.clone(), assigned_resources)
554            .map_err(|e| format!("Failed to start task execution: {e}"))?;
555
556        // The concrete unit of work this coordination layer performs per
557        // task is collecting real performance metrics; its actual `Result`
558        // determines success instead of being papered over with a default.
559        let (success, performance_metrics, error_info) =
560            match self.performance_tracker.collect_metrics() {
561                Ok(metrics) => (true, metrics, None),
562                Err(e) => (false, PerformanceMetrics::default(), Some(e.to_string())),
563            };
564
565        let execution_time = start_time.elapsed();
566
567        self.scheduler
568            .complete_task(&task_id, success, error_info.clone())
569            .map_err(|e| format!("Failed to complete task in scheduler: {e}"))?;
570
571        Ok(CoordinationResult {
572            success,
573            task_id,
574            execution_time,
575            resource_usage: self.state.resource_usage.clone(),
576            performance_metrics,
577            convergence_result: None,
578            anomaly_alerts: Vec::new(),
579            errors: error_info.into_iter().collect(),
580        })
581    }
582
583    fn update_pipeline_executions(&mut self) {
584        // Update orchestrator state - needs implementation
585        // self.orchestrator.update_pipeline_states();
586
587        // Check for completed pipelines - needs implementation
588        // let completed_pipelines = self.orchestrator.get_completed_pipelines();
589        // for pipeline_id in completed_pipelines {
590        //     self.state.active_pipelines.remove(&pipeline_id);
591        // }
592    }
593
594    fn perform_maintenance(&mut self) {
595        let now = Instant::now();
596
597        // Perform checkpointing
598        if let Some(last_checkpoint) = self.state.last_checkpoint {
599            if now.duration_since(last_checkpoint) >= self.config.checkpoint_interval {
600                let _ = self.create_checkpoint();
601            }
602        } else {
603            let _ = self.create_checkpoint();
604        }
605
606        // Clean up completed tasks and experiments
607        self.cleanup_completed_items();
608
609        // Update adaptive parameters
610        if self.config.enable_auto_scaling {
611            self.update_adaptive_parameters();
612        }
613    }
614
615    fn create_checkpoint(&mut self) -> Result<(), String> {
616        // Create checkpoint through orchestrator - needs implementation
617        // self.orchestrator.create_system_checkpoint()?;
618        self.state.last_checkpoint = Some(Instant::now());
619        Ok(())
620    }
621
622    fn cleanup_completed_items(&mut self) {
623        // Tasks are removed from `active_tasks` synchronously as soon as
624        // `process_scheduled_tasks` finishes executing them (success or
625        // failure) -- see the `self.state.active_tasks.remove(&task_id)`
626        // calls there -- so anything still here has been submitted but not
627        // yet picked up by the scheduler. Drop entries that have sat
628        // unscheduled longer than `threshold` instead of retaining every
629        // task forever (the previous `retain(|_, _| true)` never removed
630        // anything, regardless of age).
631        let threshold = Duration::from_secs(3600); // 1 hour
632        let now = SystemTime::now();
633
634        self.state.active_tasks.retain(|_, task| {
635            now.duration_since(task.created_at)
636                .map(|age| age < threshold)
637                .unwrap_or(true)
638        });
639    }
640
641    fn update_adaptive_parameters(&mut self) {
642        // Adaptive resource allocation based on performance
643        let current_metrics = &self.metrics;
644
645        if current_metrics.resource_utilization > T::from(0.9).unwrap_or_else(|| T::zero()) {
646            // High utilization - consider scaling up
647            let _ = self.request_additional_resources();
648        } else if current_metrics.resource_utilization < T::from(0.3).unwrap_or_else(|| T::zero()) {
649            // Low utilization - consider scaling down
650            let _ = self.release_excess_resources();
651        }
652    }
653
654    fn request_additional_resources(&mut self) -> Result<(), String> {
655        // Implementation would request additional compute resources
656        Ok(())
657    }
658
659    fn release_excess_resources(&mut self) -> Result<(), String> {
660        // Implementation would release unused resources
661        Ok(())
662    }
663
664    fn update_metrics(&mut self) {
665        let current_time = Instant::now();
666        let uptime = current_time.duration_since(self.state.coordination_start_time);
667
668        // Update basic metrics
669        self.metrics.uptime = uptime;
670        self.metrics.total_processed_tasks = self.state.total_tasks_processed;
671
672        // Calculate throughput
673        if uptime.as_secs() > 0 {
674            self.metrics.throughput = T::from(self.state.total_tasks_processed)
675                .unwrap_or_else(|| T::zero())
676                / T::from(uptime.as_secs()).unwrap_or_else(|| T::one());
677        }
678
679        // Fraction of the coordinator's configured task-concurrency
680        // capacity currently in use. `self.state.resource_usage` (CPU/
681        // memory/etc. from `research::experiments::ResourceUsage`) is never
682        // populated by any real sampling anywhere in this coordinator, so
683        // deriving from it would still be a fabricated number; this is
684        // real, live data instead of the previous hardcoded `T::zero()`.
685        self.metrics.resource_utilization = if self.config.max_concurrent_tasks > 0 {
686            (T::from(self.state.active_tasks.len()).unwrap_or_else(|| T::zero())
687                / T::from(self.config.max_concurrent_tasks).unwrap_or_else(|| T::one()))
688            .min(T::one())
689        } else {
690            T::zero()
691        };
692
693        // Update convergence and anomaly rates
694        self.metrics.convergence_rate = self.calculate_convergence_rate();
695        self.metrics.anomaly_detection_rate = self.calculate_anomaly_rate();
696    }
697
698    /// Fraction of `monitor_optimization_value` calls that reported
699    /// convergence, computed from real state history rather than a
700    /// hardcoded constant. Returns `T::zero()` when no checks have been
701    /// performed yet (honest "no data" rather than a fabricated rate).
702    fn calculate_convergence_rate(&self) -> T {
703        if self.state.convergence_checks_total == 0 {
704            return T::zero();
705        }
706        T::from(self.state.convergence_checks_passed).unwrap_or_else(|| T::zero())
707            / T::from(self.state.convergence_checks_total).unwrap_or_else(|| T::one())
708    }
709
710    /// Fraction of `monitor_optimization_value` calls that flagged an
711    /// anomaly, computed from real state history rather than a hardcoded
712    /// constant. Returns `T::zero()` when no checks have been performed yet.
713    fn calculate_anomaly_rate(&self) -> T {
714        if self.state.anomaly_checks_total == 0 {
715            return T::zero();
716        }
717        T::from(self.state.anomaly_checks_flagged).unwrap_or_else(|| T::zero())
718            / T::from(self.state.anomaly_checks_total).unwrap_or_else(|| T::one())
719    }
720
721    fn collect_system_metrics(&self) -> Vec<T> {
722        // Collect system-level metrics for anomaly detection
723        vec![
724            self.metrics.resource_utilization,
725            self.metrics.throughput,
726            T::from(self.state.active_tasks.len()).unwrap_or_else(|| T::zero()),
727            T::from(self.state.active_pipelines.len()).unwrap_or_else(|| T::zero()),
728        ]
729    }
730
731    fn experiment_to_pipeline(
732        &self,
733        experiment: &Experiment<T>,
734    ) -> Result<OptimizationPipeline<T>, String> {
735        // Convert experiment configuration to pipeline stages
736        let pipeline = OptimizationPipeline {
737            pipeline_id: experiment.experiment_id.clone(),
738            name: format!("Experiment {}", experiment.experiment_id),
739            description: "Auto-generated pipeline from experiment".to_string(),
740            stages: Vec::new(), // Will be populated with proper stages
741            dependencies: HashMap::new(),
742            configuration: PipelineConfiguration::default(),
743            global_parameters: HashMap::new(),
744            metadata: crate::coordination::orchestration::pipeline_orchestrator::PipelineMetadata {
745                created_by: "system".to_string(),
746                created_at: std::time::SystemTime::now(),
747                updated_at: std::time::SystemTime::now(),
748                tags: vec!["experiment".to_string()],
749                description: "Pipeline from experiment".to_string(),
750            },
751            version: "1.0.0".to_string(),
752        };
753        Ok(pipeline)
754    }
755
756    fn assess_health_status(&self) -> HealthStatus {
757        let error_rate = self.metrics.error_rate;
758        let resource_utilization = self.metrics.resource_utilization;
759
760        if error_rate > T::from(0.1).unwrap_or_else(|| T::zero()) {
761            HealthStatus::Unhealthy
762        } else if resource_utilization > T::from(0.95).unwrap_or_else(|| T::zero()) {
763            HealthStatus::Degraded
764        } else if error_rate > T::from(0.05).unwrap_or_else(|| T::zero())
765            || resource_utilization > T::from(0.8).unwrap_or_else(|| T::zero())
766        {
767            HealthStatus::Warning
768        } else {
769            HealthStatus::Healthy
770        }
771    }
772
773    fn complete_active_tasks(&mut self) -> Result<(), String> {
774        // Wait for active tasks to complete or timeout
775        let timeout = Duration::from_secs(60);
776        let start = Instant::now();
777
778        while !self.state.active_tasks.is_empty() && start.elapsed() < timeout {
779            let results = self.process_scheduled_tasks();
780            if results.is_empty() {
781                std::thread::sleep(Duration::from_millis(100));
782            }
783        }
784
785        if !self.state.active_tasks.is_empty() {
786            return Err(format!(
787                "Timeout waiting for {} tasks to complete",
788                self.state.active_tasks.len()
789            ));
790        }
791
792        Ok(())
793    }
794
795    fn save_final_checkpoints(&mut self) -> Result<(), String> {
796        self.create_checkpoint()
797    }
798
799    fn generate_final_report(&self) -> String {
800        format!(
801            "Optimization Coordination Final Report:\n\
802             - Total Uptime: {:?}\n\
803             - Tasks Processed: {}\n\
804             - Experiments Completed: {}\n\
805             - Average Throughput: {:.2}\n\
806             - Resource Utilization: {:.2}%\n\
807             - Error Rate: {:.2}%\n\
808             - Convergence Rate: {:.2}%\n\
809             - Health Status: {:?}",
810            self.metrics.uptime,
811            self.metrics.total_processed_tasks,
812            self.state.total_experiments_completed,
813            self.metrics.throughput.to_f64().unwrap_or(0.0),
814            (self.metrics.resource_utilization * T::from(100.0).unwrap_or_else(|| T::zero()))
815                .to_f64()
816                .unwrap_or(0.0),
817            (self.metrics.error_rate * T::from(100.0).unwrap_or_else(|| T::zero()))
818                .to_f64()
819                .unwrap_or(0.0),
820            (self.metrics.convergence_rate * T::from(100.0).unwrap_or_else(|| T::zero()))
821                .to_f64()
822                .unwrap_or(0.0),
823            self.assess_health_status(),
824        )
825    }
826}
827
828/// Result of monitoring operations
829#[derive(Debug, Clone)]
830pub struct MonitoringResult<T: Float + Debug + Send + Sync + 'static> {
831    pub task_id: String,
832    pub value: T,
833    pub convergence_result: ConvergenceResult<T>,
834    pub anomaly_result: Option<AnomalyResult<T>>,
835    pub alerts: Vec<MonitoringAlert<T>>,
836    pub timestamp: Instant,
837}
838
839/// Monitoring alert types
840#[derive(Debug, Clone)]
841pub enum MonitoringAlert<T: Float + Debug + Send + Sync + 'static> {
842    Convergence(ConvergenceResult<T>),
843    Anomaly(AnomalyResult<T>),
844    Performance(Box<PerformanceAlert<T>>),
845    Resource(String),
846}
847
848/// Current status of the coordination system
849#[derive(Debug, Clone)]
850pub struct CoordinationStatus<T: Float + Debug + Send + Sync + 'static> {
851    pub active_tasks: usize,
852    pub active_pipelines: usize,
853    pub active_experiments: usize,
854    pub resource_utilization: ResourceUsage,
855    pub metrics: CoordinatorMetrics<T>,
856    pub uptime: Duration,
857    pub health_status: HealthStatus,
858}
859
860/// Health status of the coordination system
861#[derive(Debug, Clone, PartialEq)]
862pub enum HealthStatus {
863    Healthy,
864    Warning,
865    Degraded,
866    Unhealthy,
867}
868
869/// Builder for creating optimization coordinators
870pub struct CoordinatorBuilder<T: Float + Debug + Send + Sync + 'static> {
871    config: CoordinatorConfig<T>,
872}
873
874impl<T: Float + Debug + Send + Sync + 'static + Default> CoordinatorBuilder<T> {
875    pub fn new() -> Self {
876        Self {
877            config: CoordinatorConfig::default(),
878        }
879    }
880
881    pub fn max_concurrent_tasks(mut self, max: usize) -> Self {
882        self.config.max_concurrent_tasks = max;
883        self
884    }
885
886    pub fn monitoring_interval(mut self, interval: Duration) -> Self {
887        self.config.monitoring_interval = interval;
888        self
889    }
890
891    pub fn enable_anomaly_detection(mut self, enable: bool) -> Self {
892        self.config.enable_anomaly_detection = enable;
893        self
894    }
895
896    pub fn enable_fault_tolerance(mut self, enable: bool) -> Self {
897        self.config.enable_fault_tolerance = enable;
898        self
899    }
900
901    pub fn convergence_criteria(mut self, criteria: ConvergenceCriteria<T>) -> Self {
902        self.config.convergence_criteria = criteria;
903        self
904    }
905
906    pub fn build(self) -> Result<OptimizationCoordinator<T>, String> {
907        OptimizationCoordinator::new(self.config)
908    }
909}
910
911impl<T: Float + Debug + Send + Sync + 'static + Default> Default for CoordinatorBuilder<T> {
912    fn default() -> Self {
913        Self::new()
914    }
915}
916
917#[cfg(test)]
918mod tests {
919    use super::*;
920
921    #[test]
922    fn test_coordinator_creation() {
923        let coordinator = CoordinatorBuilder::<f64>::new()
924            .max_concurrent_tasks(5)
925            .enable_anomaly_detection(true)
926            .build()
927            .expect("unwrap failed");
928
929        let status = coordinator.get_status();
930        assert_eq!(status.active_tasks, 0);
931        assert_eq!(status.health_status, HealthStatus::Healthy);
932    }
933
934    #[test]
935    fn test_task_submission() {
936        let mut coordinator = OptimizationCoordinator::<f64>::new(CoordinatorConfig::default())
937            .expect("unwrap failed");
938        let task = OptimizationTask::new("test_task".to_string());
939
940        let task_id = coordinator.submit_task(task).expect("unwrap failed");
941        assert!(!task_id.is_empty());
942
943        let status = coordinator.get_status();
944        assert!(status.active_tasks > 0);
945    }
946
947    #[test]
948    fn test_monitoring() {
949        let mut coordinator = OptimizationCoordinator::<f64>::new(CoordinatorConfig::default())
950            .expect("unwrap failed");
951        let result = coordinator.monitor_optimization_value("test_task", 1.0);
952
953        assert_eq!(result.task_id, "test_task");
954        assert_eq!(result.value, 1.0);
955    }
956
957    #[test]
958    fn convergence_and_anomaly_rates_reflect_real_history() {
959        // Regression test for F62: rates must be computed from actual
960        // monitor_optimization_value history, not hardcoded constants.
961        let mut coordinator = OptimizationCoordinator::<f64>::new(CoordinatorConfig::default())
962            .expect("unwrap failed");
963
964        // No checks performed yet: rates must honestly report zero, not a
965        // fabricated "everything is fine" placeholder.
966        assert_eq!(coordinator.calculate_convergence_rate(), 0.0);
967        assert_eq!(coordinator.calculate_anomaly_rate(), 0.0);
968
969        for _ in 0..4 {
970            coordinator.monitor_optimization_value("t", 1.0);
971        }
972        // Rates must now be derived from the recorded history: exactly 4
973        // convergence checks were performed.
974        assert_eq!(coordinator.state.convergence_checks_total, 4);
975    }
976
977    #[test]
978    fn execute_cycle_processes_submitted_tasks_through_the_scheduler() {
979        // Regression test for F62: process_scheduled_tasks must actually
980        // pull tasks from the scheduler and execute them, instead of
981        // iterating over a permanently-empty list.
982        let mut coordinator = OptimizationCoordinator::<f64>::new(CoordinatorConfig::default())
983            .expect("unwrap failed");
984        let task = OptimizationTask::new("cycle_task".to_string());
985        coordinator.submit_task(task).expect("unwrap failed");
986
987        let results = coordinator.execute_cycle();
988        assert!(
989            !results.is_empty(),
990            "execute_cycle must dispatch the submitted task instead of processing nothing"
991        );
992        // The task must have been finalized through the scheduler's real
993        // lifecycle (either completed or failed), not left in limbo by a
994        // no-op `execute_task` that never calls `complete_task`.
995        let stats = coordinator.scheduler.get_statistics();
996        assert_eq!(
997            stats.total_tasks_completed + stats.total_tasks_failed,
998            1,
999            "the dispatched task must be reflected in scheduler statistics"
1000        );
1001    }
1002
1003    // Regression test for F62 (additional fix beyond the pre-existing
1004    // execute_task/rates fixes above): `resource_utilization` was
1005    // hardcoded to `T::zero()` on every call to `update_metrics`
1006    // ("Needs proper implementation"), which fed into
1007    // `assess_health_status`'s Degraded threshold and
1008    // `update_adaptive_parameters`'s scale-up/down decisions -- both of
1009    // which could therefore never observe anything but "0% utilized".
1010    #[test]
1011    fn resource_utilization_reflects_real_active_task_load() {
1012        let mut coordinator = CoordinatorBuilder::<f64>::new()
1013            .max_concurrent_tasks(4)
1014            .build()
1015            .expect("unwrap failed");
1016
1017        coordinator.update_metrics();
1018        assert_eq!(coordinator.metrics.resource_utilization, 0.0);
1019
1020        for i in 0..2 {
1021            let task = OptimizationTask::new(format!("util_task_{i}"));
1022            coordinator
1023                .submit_task(task)
1024                .expect("submit should succeed");
1025        }
1026        coordinator.update_metrics();
1027
1028        assert_eq!(
1029            coordinator.metrics.resource_utilization, 0.5,
1030            "2 active tasks out of a configured max of 4 must report 50% utilization, \
1031             not the old hardcoded 0.0"
1032        );
1033    }
1034
1035    // Regression test for F62 (additional fix): `cleanup_completed_items`
1036    // always retained every task (`retain(|_, _| true)`, "Need proper
1037    // completion check implementation"), so nothing was ever actually
1038    // cleaned up regardless of age.
1039    #[test]
1040    fn cleanup_completed_items_removes_only_stale_tasks() {
1041        let mut coordinator = OptimizationCoordinator::<f64>::new(CoordinatorConfig::default())
1042            .expect("unwrap failed");
1043
1044        let mut old_task = OptimizationTask::new("old_task".to_string());
1045        old_task.created_at = SystemTime::now() - Duration::from_secs(7200); // 2h old
1046        coordinator
1047            .state
1048            .active_tasks
1049            .insert(old_task.task_id.clone(), old_task);
1050
1051        let fresh_task = OptimizationTask::new("fresh_task".to_string());
1052        let fresh_id = fresh_task.task_id.clone();
1053        coordinator
1054            .state
1055            .active_tasks
1056            .insert(fresh_id.clone(), fresh_task);
1057
1058        assert_eq!(coordinator.state.active_tasks.len(), 2);
1059        coordinator.cleanup_completed_items();
1060
1061        assert_eq!(
1062            coordinator.state.active_tasks.len(),
1063            1,
1064            "the stale (>1h old) task must be removed"
1065        );
1066        assert!(
1067            coordinator.state.active_tasks.contains_key(&fresh_id),
1068            "the fresh task must be retained"
1069        );
1070    }
1071}