use crate::error::{ScirsError, ScirsResult};
use scirs2_core::error_context;
use scirs2_core::ndarray::{Array1, ArrayView1};
use std::path::Path;
use crate::distributed::{DistributedConfig, DistributedOptimizationContext, MPIInterface};
use crate::gpu::{
acceleration::{AccelerationConfig, AccelerationManager},
GpuOptimizationConfig, GpuOptimizationContext,
};
use crate::result::OptimizeResults;
use crate::self_tuning::{
AdaptationStrategy, ParameterValue, SelfTuningConfig, SelfTuningOptimizer, TunableParameter,
};
use crate::visualization::{
tracking::TrajectoryTracker, OptimizationVisualizer, VisualizationConfig,
};
#[derive(Clone)]
pub struct UnifiedOptimizationConfig {
pub use_distributed: bool,
pub distributedconfig: Option<DistributedConfig>,
pub use_self_tuning: bool,
pub self_tuningconfig: Option<SelfTuningConfig>,
pub use_gpu: bool,
pub gpuconfig: Option<GpuOptimizationConfig>,
pub accelerationconfig: Option<AccelerationConfig>,
pub enable_visualization: bool,
pub visualizationconfig: Option<VisualizationConfig>,
pub output_directory: Option<String>,
pub max_nit: usize,
pub function_tolerance: f64,
pub gradient_tolerance: f64,
}
impl Default for UnifiedOptimizationConfig {
fn default() -> Self {
Self {
use_distributed: false,
distributedconfig: None,
use_self_tuning: true,
self_tuningconfig: Some(SelfTuningConfig::default()),
use_gpu: false,
gpuconfig: None,
accelerationconfig: None,
enable_visualization: true,
visualizationconfig: Some(VisualizationConfig::default()),
output_directory: Some("optimization_output".to_string()),
max_nit: 1000,
function_tolerance: 1e-6,
gradient_tolerance: 1e-6,
}
}
}
pub struct UnifiedOptimizer<M: MPIInterface> {
config: UnifiedOptimizationConfig,
distributed_context: Option<DistributedOptimizationContext<M>>,
self_tuning_optimizer: Option<SelfTuningOptimizer>,
gpu_context: Option<GpuOptimizationContext>,
acceleration_manager: Option<AccelerationManager>,
visualizer: Option<OptimizationVisualizer>,
trajectory_tracker: Option<TrajectoryTracker>,
previous_function_value: Option<f64>,
}
impl<M: MPIInterface> UnifiedOptimizer<M> {
pub fn new(config: UnifiedOptimizationConfig, mpi: Option<M>) -> ScirsResult<Self> {
let distributed_context = if config.use_distributed {
if let (Some(mpi_interface), Some(distconfig)) = (mpi, &config.distributedconfig) {
Some(DistributedOptimizationContext::new(
mpi_interface,
distconfig.clone(),
))
} else {
return Err(ScirsError::InvalidInput(error_context!(
"MPI interface and distributed config required for distributed optimization"
)));
}
} else {
None
};
let self_tuning_optimizer = if config.use_self_tuning {
let tuningconfig = config.self_tuningconfig.clone().unwrap_or_default();
Some(SelfTuningOptimizer::new(tuningconfig))
} else {
None
};
let (gpu_context, acceleration_manager) = if config.use_gpu {
let gpuconfig = config.gpuconfig.clone().unwrap_or_default();
let gpu_ctx = GpuOptimizationContext::new(gpuconfig)?;
let accelconfig = config.accelerationconfig.clone().unwrap_or_default();
let accel_mgr = AccelerationManager::new(accelconfig);
(Some(gpu_ctx), Some(accel_mgr))
} else {
(None, None)
};
let (visualizer, trajectory_tracker) = if config.enable_visualization {
let visconfig = config.visualizationconfig.clone().unwrap_or_default();
let vis = OptimizationVisualizer::with_config(visconfig);
let tracker = TrajectoryTracker::new();
(Some(vis), Some(tracker))
} else {
(None, None)
};
Ok(Self {
config,
distributed_context,
self_tuning_optimizer,
gpu_context,
acceleration_manager,
visualizer,
trajectory_tracker,
previous_function_value: None,
})
}
pub fn register_tunable_parameter<T>(
&mut self,
name: &str,
param: TunableParameter<T>,
) -> ScirsResult<()>
where
T: Clone + PartialOrd + std::fmt::Debug + Send + Sync + 'static,
{
if let Some(ref mut tuner) = self.self_tuning_optimizer {
tuner.register_parameter(name, param)?;
}
Ok(())
}
pub fn optimize<F, G>(
&mut self,
function: F,
gradient: Option<G>,
initial_guess: &Array1<f64>,
bounds: Option<&[(Option<f64>, Option<f64>)]>,
) -> ScirsResult<UnifiedOptimizationResults>
where
F: Fn(&ArrayView1<f64>) -> f64 + Clone + Send + Sync,
G: Fn(&ArrayView1<f64>) -> Array1<f64> + Clone + Send + Sync,
{
let mut current_x = initial_guess.clone();
let mut current_f = function(¤t_x.view());
let mut iteration = 0;
let mut function_evaluations = 1;
let mut gradient_evaluations = 0;
if let Some(ref mut tracker) = self.trajectory_tracker {
tracker.record(iteration, ¤t_x.view(), current_f);
}
let start_time = std::time::Instant::now();
while iteration < self.config.max_nit {
iteration += 1;
let current_grad = if let Some(ref grad_fn) = gradient {
gradient_evaluations += 1;
grad_fn(¤t_x.view())
} else {
self.compute_numerical_gradient(&function, ¤t_x)?
};
let grad_norm = current_grad.iter().map(|&g| g * g).sum::<f64>().sqrt();
if grad_norm < self.config.gradient_tolerance {
break;
}
let tuning_params = {
if let Some(ref mut tuner) = self.self_tuning_optimizer {
let improvement = if iteration > 1 {
let prev_f = self.previous_function_value.unwrap_or(current_f);
if prev_f.abs() > 1e-14 {
(prev_f - current_f) / prev_f.abs()
} else {
prev_f - current_f
}
} else {
0.0
};
let params_changed = tuner.update_parameters(
iteration,
current_f,
Some(grad_norm),
improvement,
)?;
if params_changed {
Some(tuner.get_parameters().clone())
} else {
None
}
} else {
None
}
};
if let Some(params) = tuning_params {
self.apply_tuned_parameters(¶ms)?;
}
let search_direction = if self.config.use_gpu {
self.compute_gpu_search_direction(¤t_grad)?
} else {
self.compute_cpu_search_direction(¤t_grad)?
};
let step_size = if self.distributed_context.is_some() {
let current_x_copy = current_x.clone();
let search_direction_copy = search_direction.clone();
let mut dist_ctx = self.distributed_context.take();
let result = if let Some(ref mut ctx) = dist_ctx {
self.distributed_line_search(
ctx,
&function,
¤t_x_copy,
&search_direction_copy,
)
} else {
unreachable!()
};
self.distributed_context = dist_ctx;
result?
} else {
self.standard_line_search(&function, ¤t_x, &search_direction)?
};
for i in 0..current_x.len() {
current_x[i] += step_size * search_direction[i];
}
if let Some(bounds) = bounds {
self.apply_bounds(&mut current_x, bounds);
}
current_f = function(¤t_x.view());
function_evaluations += 1;
if let Some(ref mut tracker) = self.trajectory_tracker {
tracker.record(iteration, ¤t_x.view(), current_f);
tracker.record_gradient_norm(grad_norm);
tracker.record_step_size(step_size);
}
if let Some(prev_f) = self.previous_function_value {
let abs_improvement = (prev_f - current_f).abs();
let rel_improvement = if prev_f.abs() > 1e-14 {
abs_improvement / prev_f.abs()
} else {
abs_improvement
};
if abs_improvement < self.config.function_tolerance
|| rel_improvement < self.config.function_tolerance
{
break;
}
}
self.previous_function_value = Some(current_f);
}
let total_time = start_time.elapsed().as_secs_f64();
let success = iteration < self.config.max_nit;
let message = if success {
"Optimization completed successfully".to_string()
} else {
"Maximum iterations reached".to_string()
};
let visualization_paths = if let (Some(ref visualizer), Some(ref tracker)) =
(&self.visualizer, &self.trajectory_tracker)
{
self.generate_visualization(visualizer, tracker.trajectory())?
} else {
Vec::new()
};
let performance_report = self.generate_performance_report(
total_time,
function_evaluations,
gradient_evaluations,
)?;
Ok(UnifiedOptimizationResults {
base_result: OptimizeResults::<f64> {
x: current_x,
fun: current_f,
success,
message,
nit: iteration,
nfev: function_evaluations,
njev: gradient_evaluations,
..OptimizeResults::<f64>::default()
},
visualization_paths,
performance_report,
self_tuning_report: self
.self_tuning_optimizer
.as_ref()
.map(|t| t.generate_report()),
distributed_stats: self.distributed_context.as_ref().map(|d| d.stats().clone()),
})
}
fn compute_numerical_gradient<F>(
&self,
function: &F,
x: &Array1<f64>,
) -> ScirsResult<Array1<f64>>
where
F: Fn(&ArrayView1<f64>) -> f64,
{
let n = x.len();
let mut gradient = Array1::zeros(n);
let h = 1e-8;
if self.config.use_gpu {
if let Some(ref gpu_ctx) = self.gpu_context {
return gpu_ctx.compute_gradient_finite_diff(function, x, h);
}
}
for i in 0..n {
let mut x_plus = x.clone();
let mut x_minus = x.clone();
x_plus[i] += h;
x_minus[i] -= h;
gradient[i] = (function(&x_plus.view()) - function(&x_minus.view())) / (2.0 * h);
}
Ok(gradient)
}
fn compute_gpu_search_direction(&self, gradient: &Array1<f64>) -> ScirsResult<Array1<f64>> {
if let Some(ref gpu_ctx) = self.gpu_context {
gpu_ctx.compute_search_direction(gradient)
} else {
Ok(-gradient.clone())
}
}
fn compute_cpu_search_direction(&self, gradient: &Array1<f64>) -> ScirsResult<Array1<f64>> {
Ok(-gradient.clone())
}
fn distributed_line_search<F>(
&mut self,
_dist_ctx: &mut DistributedOptimizationContext<M>,
function: &F,
x: &Array1<f64>,
direction: &Array1<f64>,
) -> ScirsResult<f64>
where
F: Fn(&ArrayView1<f64>) -> f64 + Clone + Send + Sync,
{
let _step_sizes = Array1::from(vec![0.001, 0.01, 0.1, 1.0, 10.0]);
self.standard_line_search(function, x, direction)
}
fn standard_line_search<F>(
&self,
function: &F,
x: &Array1<f64>,
direction: &Array1<f64>,
) -> ScirsResult<f64>
where
F: Fn(&ArrayView1<f64>) -> f64,
{
let mut alpha = 1.0;
let c1 = 1e-4;
let rho = 0.5;
let f0 = function(&x.view());
let grad_dot_dir = -direction.iter().map(|&d| d * d).sum::<f64>();
for _ in 0..20 {
let mut x_new = x.clone();
for i in 0..x.len() {
x_new[i] += alpha * direction[i];
}
let f_new = function(&x_new.view());
if f_new <= f0 + c1 * alpha * grad_dot_dir {
return Ok(alpha);
}
alpha *= rho;
}
Ok(alpha)
}
fn apply_bounds(&self, x: &mut Array1<f64>, bounds: &[(Option<f64>, Option<f64>)]) {
for (i, &mut ref mut xi) in x.iter_mut().enumerate() {
if i < bounds.len() {
if let Some(lower) = bounds[i].0 {
if *xi < lower {
*xi = lower;
}
}
if let Some(upper) = bounds[i].1 {
if *xi > upper {
*xi = upper;
}
}
}
}
}
fn apply_tuned_parameters(
&mut self,
parameters: &std::collections::HashMap<String, ParameterValue>,
) -> ScirsResult<()> {
for (name, value) in parameters {
match name.as_str() {
"learning_rate" | "step_size" => {
if let Some(f_val) = value.as_f64() {
self.update_algorithm_parameter("step_size", f_val)?;
}
}
"tolerance" => {
if let Some(f_val) = value.as_f64() {
self.update_algorithm_parameter("tolerance", f_val)?;
}
}
_ => {
}
}
}
Ok(())
}
fn update_algorithm_parameter(&mut self, name: &str, value: f64) -> ScirsResult<()> {
Ok(())
}
fn generate_visualization(
&self,
visualizer: &OptimizationVisualizer,
trajectory: &crate::visualization::OptimizationTrajectory,
) -> ScirsResult<Vec<String>> {
let mut paths = Vec::new();
if let Some(ref output_dir) = self.config.output_directory {
let output_path = Path::new(output_dir);
std::fs::create_dir_all(output_path)?;
let convergence_path = output_path.join("convergence.svg");
visualizer.plot_convergence(trajectory, &convergence_path)?;
paths.push(convergence_path.to_string_lossy().to_string());
if !trajectory.parameters.is_empty() && trajectory.parameters[0].len() == 2 {
let trajectory_path = output_path.join("trajectory.svg");
visualizer.plot_parameter_trajectory(trajectory, &trajectory_path)?;
paths.push(trajectory_path.to_string_lossy().to_string());
}
visualizer.create_optimization_report(trajectory, output_path)?;
paths.push(
output_path
.join("summary.html")
.to_string_lossy()
.to_string(),
);
}
Ok(paths)
}
fn generate_performance_report(
&self,
total_time: f64,
function_evaluations: usize,
gradient_evaluations: usize,
) -> ScirsResult<String> {
let mut report = String::from("Unified Optimization Performance Report\n");
report.push_str("=========================================\n\n");
report.push_str(&format!("Total Time: {:.3}s\n", total_time));
report.push_str(&format!("Function Evaluations: {}\n", function_evaluations));
report.push_str(&format!("Gradient Evaluations: {}\n", gradient_evaluations));
if total_time > 0.0 {
report.push_str(&format!(
"Function Evaluations per Second: {:.2}\n",
function_evaluations as f64 / total_time
));
}
if let Some(ref dist_ctx) = self.distributed_context {
report.push_str("\nDistributed Performance:\n");
report.push_str(&dist_ctx.stats().generate_report());
}
if let Some(ref accel_mgr) = self.acceleration_manager {
report.push_str("\nGPU Acceleration Performance:\n");
report.push_str("GPU acceleration metrics available\n");
}
Ok(report)
}
}
#[derive(Debug, Clone)]
pub struct UnifiedOptimizationResults {
pub base_result: OptimizeResults<f64>,
pub visualization_paths: Vec<String>,
pub performance_report: String,
pub self_tuning_report: Option<String>,
pub distributed_stats: Option<crate::distributed::DistributedStats>,
}
impl UnifiedOptimizationResults {
pub fn x(&self) -> &Array1<f64> {
&self.base_result.x
}
pub fn fun(&self) -> f64 {
self.base_result.fun
}
pub fn success(&self) -> bool {
self.base_result.success
}
pub fn message(&self) -> &str {
&self.base_result.message
}
pub fn iterations(&self) -> usize {
self.base_result.nit
}
pub fn nit(&self) -> usize {
self.base_result.nit
}
pub fn print_summary(&self) {
println!("Unified Optimization Results");
println!("============================");
println!("Success: {}", self.success());
println!("Final function value: {:.6e}", self.fun());
println!("Iterations: {}", self.nit());
println!("Function evaluations: {}", self.base_result.nfev);
if self.base_result.njev > 0 {
println!("Gradient evaluations: {}", self.base_result.njev);
}
if !self.visualization_paths.is_empty() {
println!("\nGenerated visualizations:");
for path in &self.visualization_paths {
println!(" {}", path);
}
}
if let Some(ref self_tuning) = self.self_tuning_report {
println!("\nSelf-Tuning Report:");
println!("{}", self_tuning);
}
if let Some(ref dist_stats) = self.distributed_stats {
println!("\nDistributed Performance:");
println!("{}", dist_stats.generate_report());
}
println!("\nPerformance Report:");
println!("{}", self.performance_report);
}
}
pub mod presets {
use super::*;
pub fn distributed_gpuconfig(_numprocesses: usize) -> UnifiedOptimizationConfig {
UnifiedOptimizationConfig {
use_distributed: true,
distributedconfig: Some(crate::distributed::DistributedConfig {
distribution_strategy: crate::distributed::DistributionStrategy::DataParallel,
load_balancing: crate::distributed::LoadBalancingConfig::default(),
communication: crate::distributed::CommunicationConfig::default(),
fault_tolerance: crate::distributed::FaultToleranceConfig::default(),
}),
use_gpu: true,
gpuconfig: Some(GpuOptimizationConfig::default()),
accelerationconfig: Some(AccelerationConfig::default()),
use_self_tuning: true,
self_tuningconfig: Some(SelfTuningConfig {
adaptation_strategy: AdaptationStrategy::Hybrid,
update_frequency: 25,
learning_rate: 0.1,
memory_window: 100,
use_bayesian_tuning: true,
exploration_factor: 0.15,
}),
enable_visualization: true,
visualizationconfig: Some(VisualizationConfig::default()),
output_directory: Some("distributed_gpu_optimization".to_string()),
max_nit: 2000,
function_tolerance: 1e-8,
gradient_tolerance: 1e-8,
}
}
pub fn large_scaleconfig() -> UnifiedOptimizationConfig {
UnifiedOptimizationConfig {
use_distributed: false,
distributedconfig: None,
use_gpu: true,
gpuconfig: Some(GpuOptimizationConfig::default()),
accelerationconfig: Some(AccelerationConfig::default()),
use_self_tuning: true,
self_tuningconfig: Some(SelfTuningConfig {
adaptation_strategy: AdaptationStrategy::PerformanceBased,
update_frequency: 50,
learning_rate: 0.05,
memory_window: 200,
use_bayesian_tuning: false,
exploration_factor: 0.1,
}),
enable_visualization: true,
visualizationconfig: Some(VisualizationConfig::default()),
output_directory: Some("large_scale_optimization".to_string()),
max_nit: 5000,
function_tolerance: 1e-6,
gradient_tolerance: 1e-6,
}
}
pub fn interactiveconfig() -> UnifiedOptimizationConfig {
UnifiedOptimizationConfig {
use_distributed: false,
distributedconfig: None,
use_gpu: false,
gpuconfig: None,
accelerationconfig: None,
use_self_tuning: true,
self_tuningconfig: Some(SelfTuningConfig {
adaptation_strategy: AdaptationStrategy::ConvergenceBased,
update_frequency: 10,
learning_rate: 0.2,
memory_window: 50,
use_bayesian_tuning: true,
exploration_factor: 0.2,
}),
enable_visualization: true,
visualizationconfig: Some(VisualizationConfig {
format: crate::visualization::OutputFormat::Html,
width: 1200,
height: 800,
title: Some("Interactive Optimization".to_string()),
show_grid: true,
log_scale_y: false,
color_scheme: crate::visualization::ColorScheme::Scientific,
show_legend: true,
custom_style: None,
}),
output_directory: Some("interactive_optimization".to_string()),
max_nit: 500,
function_tolerance: 1e-4,
gradient_tolerance: 1e-4,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::array;
#[test]
fn test_unifiedconfig_creation() {
let config = UnifiedOptimizationConfig::default();
assert!(!config.use_distributed);
assert!(config.use_self_tuning);
assert!(!config.use_gpu);
assert!(config.enable_visualization);
}
#[test]
fn test_presetconfigs() {
let large_scale = presets::large_scaleconfig();
assert!(large_scale.use_gpu);
assert!(large_scale.use_self_tuning);
assert!(!large_scale.use_distributed);
let interactive = presets::interactiveconfig();
assert!(!interactive.use_gpu);
assert!(interactive.use_self_tuning);
assert!(interactive.enable_visualization);
}
#[test]
fn test_bounds_application() {
let config = UnifiedOptimizationConfig::default();
let _optimizer: Result<UnifiedOptimizer<crate::distributed::MockMPI>, _> =
UnifiedOptimizer::new(config, None);
}
#[test]
fn test_rosenbrock_optimization() {
let config = presets::interactiveconfig();
let rosenbrock = |x: &ArrayView1<f64>| -> f64 {
let x0 = x[0];
let x1 = x[1];
(1.0 - x0).powi(2) + 100.0 * (x1 - x0.powi(2)).powi(2)
};
let initial_guess = array![-1.0, 1.0];
let mut optimizer: UnifiedOptimizer<crate::distributed::MockMPI> =
UnifiedOptimizer::new(config, None).expect("Operation failed");
optimizer
.register_tunable_parameter("step_size", TunableParameter::new(0.01, 0.001, 0.1))
.expect("Operation failed");
assert!(true);
}
}