//! Optimizer: Maintenance Planning and Actions //! //! Provides optimization actions and maintenance planning based on //! structural monitor signals. use super::structural_monitor::{BrittlenessSignal, StructuralMonitor, TriggerType}; use std::collections::HashMap; /// Optimization action types #[derive(Debug, Clone, PartialEq)] pub enum OptimizerAction { /// Reindex: rebuild vector similarity edges Reindex { /// Affected nodes nodes: Vec, /// New similarity threshold new_threshold: Option, }, /// Rewire: adjust edge capacities Rewire { /// Edges to strengthen strengthen: Vec<(u64, u64, f64)>, /// Edges to weaken weaken: Vec<(u64, u64, f64)>, }, /// Split shard: divide a partition SplitShard { /// Shard ID to split shard_id: u64, /// Split point (if applicable) split_at: Option, }, /// Merge shards: combine partitions MergeShards { /// Shard IDs to merge shard_ids: Vec, }, /// Learning gate: enable/disable self-learning LearningGate { /// Whether to enable learning enable: bool, /// Learning rate adjustment rate_multiplier: f64, }, /// No operation needed NoOp, } /// Learning gate controller #[derive(Debug, Clone)] pub struct LearningGate { /// Whether learning is enabled pub enabled: bool, /// Current learning rate pub learning_rate: f64, /// Base learning rate pub base_rate: f64, /// Minimum rate before disabling pub min_rate: f64, /// Maximum rate pub max_rate: f64, } impl Default for LearningGate { fn default() -> Self { Self { enabled: true, learning_rate: 0.01, base_rate: 0.01, min_rate: 0.001, max_rate: 0.1, } } } impl LearningGate { /// Create new learning gate pub fn new(base_rate: f64) -> Self { Self { learning_rate: base_rate, base_rate, ..Default::default() } } /// Adjust learning rate based on signal pub fn adjust(&mut self, signal: BrittlenessSignal) { match signal { BrittlenessSignal::Healthy => { // Increase learning rate when stable self.learning_rate = (self.learning_rate * 1.1).min(self.max_rate); } BrittlenessSignal::Warning => { // Keep current rate } BrittlenessSignal::Critical | BrittlenessSignal::Disconnected => { // Reduce learning to avoid further instability self.learning_rate = (self.learning_rate * 0.5).max(self.min_rate); if self.learning_rate <= self.min_rate { self.enabled = false; } } } } /// Reset to defaults pub fn reset(&mut self) { self.enabled = true; self.learning_rate = self.base_rate; } } /// A maintenance task #[derive(Debug, Clone)] pub struct MaintenanceTask { /// Task ID pub id: u64, /// Action to perform pub action: OptimizerAction, /// Priority (higher = more urgent) pub priority: u8, /// Estimated cost (1-10) pub cost: u8, /// Expected benefit description pub benefit: String, /// Whether the task is critical pub critical: bool, } impl MaintenanceTask { /// Create new maintenance task pub fn new(id: u64, action: OptimizerAction, priority: u8) -> Self { let (cost, critical) = match &action { OptimizerAction::Reindex { nodes, .. } => { (if nodes.len() > 100 { 8 } else { 4 }, false) } OptimizerAction::Rewire { strengthen, weaken, .. } => ((strengthen.len() + weaken.len()).min(10) as u8, false), OptimizerAction::SplitShard { .. } => (6, false), OptimizerAction::MergeShards { shard_ids } => (shard_ids.len().min(10) as u8, false), OptimizerAction::LearningGate { enable, .. } => { if *enable { (1, false) } else { (2, true) } } OptimizerAction::NoOp => (0, false), }; let benefit = match &action { OptimizerAction::Reindex { .. } => "Refresh vector similarity edges".to_string(), OptimizerAction::Rewire { .. } => "Adjust edge weights for better balance".to_string(), OptimizerAction::SplitShard { .. } => { "Reduce partition size for better locality".to_string() } OptimizerAction::MergeShards { .. } => { "Combine sparse partitions for density".to_string() } OptimizerAction::LearningGate { enable, .. } => { if *enable { "Re-enable learning for adaptation".to_string() } else { "Pause learning to stabilize".to_string() } } OptimizerAction::NoOp => "No action needed".to_string(), }; Self { id, action, priority, cost, benefit, critical, } } } /// A maintenance plan #[derive(Debug, Clone, Default)] pub struct MaintenancePlan { /// Ordered list of tasks pub tasks: Vec, /// Total estimated cost pub total_cost: u32, /// Plan generation timestamp pub created_at: u64, /// Human-readable summary pub summary: String, } impl MaintenancePlan { /// Create a new plan pub fn new() -> Self { Self { created_at: std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() .as_secs(), ..Default::default() } } /// Add a task to the plan pub fn add_task(&mut self, task: MaintenanceTask) { self.total_cost += u32::from(task.cost); self.tasks.push(task); self.update_summary(); } /// Sort tasks by priority (highest first) pub fn prioritize(&mut self) { self.tasks.sort_by(|a, b| b.priority.cmp(&a.priority)); } /// Get critical tasks only pub fn critical_tasks(&self) -> Vec<&MaintenanceTask> { self.tasks.iter().filter(|t| t.critical).collect() } /// Check if plan is empty pub fn is_empty(&self) -> bool { self.tasks.is_empty() } fn update_summary(&mut self) { let critical_count = self.tasks.iter().filter(|t| t.critical).count(); self.summary = format!( "{} tasks ({} critical), total cost: {}", self.tasks.len(), critical_count, self.total_cost ); } } /// Result of optimization analysis #[derive(Debug, Clone)] pub struct OptimizationResult { /// Current graph health signal pub signal: BrittlenessSignal, /// Recommended immediate action pub immediate_action: OptimizerAction, /// Full maintenance plan pub plan: MaintenancePlan, /// Metrics snapshot pub metrics: HashMap, } /// The optimizer that plans maintenance actions #[derive(Debug)] pub struct Optimizer { /// Learning gate controller learning_gate: LearningGate, /// Task ID counter next_task_id: u64, /// Last optimization result last_result: Option, } impl Optimizer { /// Create new optimizer pub fn new() -> Self { Self { learning_gate: LearningGate::default(), next_task_id: 1, last_result: None, } } /// Get the learning gate pub fn learning_gate(&self) -> &LearningGate { &self.learning_gate } /// Get mutable learning gate pub fn learning_gate_mut(&mut self) -> &mut LearningGate { &mut self.learning_gate } /// Analyze monitor state and generate optimization plan pub fn analyze(&mut self, monitor: &StructuralMonitor) -> OptimizationResult { let signal = monitor.signal(); let state = monitor.state(); // Adjust learning gate based on signal self.learning_gate.adjust(signal); // Build maintenance plan let mut plan = MaintenancePlan::new(); let mut immediate_action = OptimizerAction::NoOp; // Check triggers and add tasks for trigger in monitor.triggers() { let (action, priority) = self.action_for_trigger(trigger.trigger_type, state); if priority >= 8 && matches!(immediate_action, OptimizerAction::NoOp) { immediate_action = action.clone(); } let task = MaintenanceTask::new(self.next_task_id, action, priority); self.next_task_id += 1; plan.add_task(task); } // Add proactive maintenance based on signal if matches!(signal, BrittlenessSignal::Warning) && plan.is_empty() { let task = MaintenanceTask::new( self.next_task_id, OptimizerAction::Rewire { strengthen: state .boundary_edges .iter() .map(|&(u, v)| (u, v, 1.2)) .collect(), weaken: Vec::new(), }, 5, ); self.next_task_id += 1; plan.add_task(task); } // Sort by priority plan.prioritize(); // Collect metrics let mut metrics = HashMap::new(); metrics.insert("lambda_est".to_string(), state.lambda_est); metrics.insert("lambda_trend".to_string(), state.lambda_trend); metrics.insert("cut_volatility".to_string(), state.cut_volatility); metrics.insert( "boundary_edges".to_string(), state.boundary_edges.len() as f64, ); metrics.insert( "learning_rate".to_string(), self.learning_gate.learning_rate, ); let result = OptimizationResult { signal, immediate_action, plan, metrics, }; self.last_result = Some(result.clone()); result } /// Get the last optimization result pub fn last_result(&self) -> Option<&OptimizationResult> { self.last_result.as_ref() } /// Generate action for a trigger type fn action_for_trigger( &self, trigger_type: TriggerType, state: &super::structural_monitor::MonitorState, ) -> (OptimizerAction, u8) { match trigger_type { TriggerType::IslandingRisk => { // Strengthen boundary edges to prevent islanding let strengthen: Vec<_> = state .boundary_edges .iter() .map(|&(u, v)| (u, v, 1.5)) .collect(); ( OptimizerAction::Rewire { strengthen, weaken: Vec::new(), }, 9, ) } TriggerType::Instability => { // Pause learning to stabilize ( OptimizerAction::LearningGate { enable: false, rate_multiplier: 0.5, }, 7, ) } TriggerType::Degradation => { // Reindex to refresh connections ( OptimizerAction::Reindex { nodes: Vec::new(), // All nodes new_threshold: Some(0.6), // Lower threshold }, 6, ) } TriggerType::OverClustering => { // Merge shards ( OptimizerAction::MergeShards { shard_ids: vec![0, 1], // Placeholder }, 4, ) } TriggerType::Disconnected => { // Critical: attempt to reconnect ( OptimizerAction::Reindex { nodes: Vec::new(), new_threshold: Some(0.5), // Very low threshold }, 10, ) } } } } impl Default for Optimizer { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_optimizer_creation() { let optimizer = Optimizer::new(); assert!(optimizer.learning_gate().enabled); } #[test] fn test_learning_gate_adjustment() { let mut gate = LearningGate::default(); // Healthy should increase rate let initial_rate = gate.learning_rate; gate.adjust(BrittlenessSignal::Healthy); assert!(gate.learning_rate > initial_rate); // Critical should decrease rate gate.adjust(BrittlenessSignal::Critical); assert!(gate.learning_rate < initial_rate); } #[test] fn test_maintenance_plan() { let mut plan = MaintenancePlan::new(); assert!(plan.is_empty()); let task = MaintenanceTask::new(1, OptimizerAction::NoOp, 5); plan.add_task(task); assert!(!plan.is_empty()); assert_eq!(plan.tasks.len(), 1); } #[test] fn test_plan_prioritization() { let mut plan = MaintenancePlan::new(); plan.add_task(MaintenanceTask::new(1, OptimizerAction::NoOp, 3)); plan.add_task(MaintenanceTask::new(2, OptimizerAction::NoOp, 9)); plan.add_task(MaintenanceTask::new(3, OptimizerAction::NoOp, 5)); plan.prioritize(); assert_eq!(plan.tasks[0].priority, 9); assert_eq!(plan.tasks[1].priority, 5); assert_eq!(plan.tasks[2].priority, 3); } #[test] fn test_optimizer_analyze() { let mut optimizer = Optimizer::new(); let mut monitor = StructuralMonitor::new(); // Healthy observation monitor.observe(5.0, vec![]); let result = optimizer.analyze(&monitor); assert_eq!(result.signal, BrittlenessSignal::Healthy); // Critical observation monitor.observe(0.5, vec![(1, 2)]); let result = optimizer.analyze(&monitor); assert_eq!(result.signal, BrittlenessSignal::Critical); assert!(!result.plan.is_empty()); } #[test] fn test_action_generation() { let optimizer = Optimizer::new(); let state = super::super::structural_monitor::MonitorState { lambda_est: 0.5, boundary_edges: vec![(1, 2)], ..Default::default() }; let (action, priority) = optimizer.action_for_trigger(TriggerType::IslandingRisk, &state); assert!(priority >= 8); assert!(matches!(action, OptimizerAction::Rewire { .. })); } }