scirs2-optimize 0.6.0

Optimization module for SciRS2 (scirs2-optimize)
Documentation
//! Event-Driven Optimization
//!
//! Implementation of event-driven optimization algorithms for neuromorphic computing.

use scirs2_core::error::CoreResult as Result;
use scirs2_core::ndarray::{Array1, ArrayView1};
use std::cmp::Ordering;
use std::collections::BinaryHeap;

/// Event in the optimization process
#[derive(Debug, Clone)]
pub struct OptimizationEvent {
    /// Time of the event
    pub time: f64,
    /// Type of event
    pub event_type: EventType,
    /// Associated data
    pub data: Array1<f64>,
}

/// Types of optimization events
#[derive(Debug, Clone)]
pub enum EventType {
    /// Parameter update event
    ParameterUpdate,
    /// Gradient computation event
    GradientComputation,
    /// Objective evaluation event
    ObjectiveEvaluation,
}

impl PartialEq for OptimizationEvent {
    fn eq(&self, other: &Self) -> bool {
        self.time == other.time
    }
}

impl Eq for OptimizationEvent {}

impl PartialOrd for OptimizationEvent {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        // Reverse ordering for min-heap behavior
        other.time.partial_cmp(&self.time)
    }
}

impl Ord for OptimizationEvent {
    fn cmp(&self, other: &Self) -> Ordering {
        self.partial_cmp(other).unwrap_or(Ordering::Equal)
    }
}

/// Event-driven optimization scheduler
#[derive(Debug, Clone)]
pub struct EventDrivenOptimizer {
    /// Event queue
    pub event_queue: BinaryHeap<OptimizationEvent>,
    /// Current time
    pub current_time: f64,
    /// Current parameters
    pub parameters: Array1<f64>,
    /// Best parameters found so far
    pub best_params: Array1<f64>,
    /// Objective value at `best_params`
    pub best_value: f64,
    /// Learning rate applied on gradient-computation events
    pub learning_rate: f64,
}

impl EventDrivenOptimizer {
    /// Create new event-driven optimizer
    pub fn new(initial_params: Array1<f64>) -> Self {
        Self {
            event_queue: BinaryHeap::new(),
            current_time: 0.0,
            best_params: initial_params.clone(),
            parameters: initial_params,
            best_value: f64::INFINITY,
            learning_rate: 0.05,
        }
    }

    /// Schedule an event
    pub fn schedule_event(&mut self, event: OptimizationEvent) {
        self.event_queue.push(event);
    }

    /// Process next event
    pub fn process_next_event<F>(&mut self, objective: &F) -> Result<bool>
    where
        F: Fn(&ArrayView1<f64>) -> f64,
    {
        if let Some(event) = self.event_queue.pop() {
            self.current_time = event.time;

            match event.event_type {
                EventType::ParameterUpdate => {
                    // Update parameters with event data
                    for (i, &update) in event.data.iter().enumerate() {
                        if i < self.parameters.len() {
                            self.parameters[i] += update;
                        }
                    }
                }
                EventType::GradientComputation => {
                    // Compute the objective gradient and take a real descent
                    // step, so the event genuinely improves the parameters with
                    // respect to the objective.
                    let gradient = self.compute_finite_difference_gradient(objective);
                    for i in 0..self.parameters.len() {
                        self.parameters[i] -= self.learning_rate * gradient[i];
                    }
                }
                EventType::ObjectiveEvaluation => {
                    // Evaluation events only refresh the incumbent, which is
                    // handled uniformly below.
                }
            }

            // Track the best objective value observed after applying the event.
            self.record_current(objective);

            Ok(true)
        } else {
            Ok(false)
        }
    }

    /// Evaluate the objective at the current parameters and update the
    /// incumbent best solution when it improves.
    fn record_current<F>(&mut self, objective: &F)
    where
        F: Fn(&ArrayView1<f64>) -> f64,
    {
        let value = objective(&self.parameters.view());
        if value < self.best_value {
            self.best_value = value;
            self.best_params = self.parameters.clone();
        }
    }

    /// Compute finite difference gradient
    fn compute_finite_difference_gradient<F>(&self, objective: &F) -> Array1<f64>
    where
        F: Fn(&ArrayView1<f64>) -> f64,
    {
        let n = self.parameters.len();
        let mut gradient = Array1::zeros(n);
        let h = 1e-6;
        let f0 = objective(&self.parameters.view());

        for i in 0..n {
            let mut params_plus = self.parameters.clone();
            params_plus[i] += h;
            let f_plus = objective(&params_plus.view());
            gradient[i] = (f_plus - f0) / h;
        }

        gradient
    }
}

/// Event-driven optimization function
#[allow(dead_code)]
pub fn event_driven_optimize<F>(
    objective: F,
    initial_params: &ArrayView1<f64>,
    max_events: usize,
) -> Result<Array1<f64>>
where
    F: Fn(&ArrayView1<f64>) -> f64,
{
    let mut optimizer = EventDrivenOptimizer::new(initial_params.to_owned());

    // Seed the incumbent with the initial point.
    optimizer.record_current(&objective);

    // Schedule a sequence of gradient-computation events. Each event performs a
    // real objective-gradient descent step, so the result is driven by the
    // objective rather than by a fixed perturbation schedule.
    for i in 0..max_events {
        let event = OptimizationEvent {
            time: i as f64 * 0.1,
            event_type: EventType::GradientComputation,
            data: Array1::zeros(initial_params.len()),
        };
        optimizer.schedule_event(event);
    }

    // Process events
    for _ in 0..max_events {
        if !optimizer.process_next_event(&objective)? {
            break;
        }
    }

    Ok(optimizer.best_params)
}