1use crate::error::OptimizeError;
8use crate::sparse_numdiff::SparseFiniteDiffOptions;
9use crate::unconstrained::memory_efficient::MemoryOptions;
10use crate::unconstrained::result::OptimizeResult;
11use scirs2_core::ndarray::{Array1, ArrayView1};
13use scirs2_sparse::csr_array::CsrArray;
14use std::fs::{File, OpenOptions};
15use std::io::{Read, Seek, SeekFrom, Write};
16use std::path::PathBuf;
17
18#[derive(Debug, Clone)]
20pub struct AdvancedScaleOptions {
21 pub memory_options: MemoryOptions,
23 pub sparse_options: SparseFiniteDiffOptions,
25 pub max_variables_in_memory: usize,
27 pub block_size: usize,
29 pub refinement_passes: usize,
31 pub use_disk_storage: bool,
33 pub mmap_threshold: usize,
35 pub compression_level: u32,
37}
38
39impl Default for AdvancedScaleOptions {
40 fn default() -> Self {
41 Self {
42 memory_options: MemoryOptions::default(),
43 sparse_options: SparseFiniteDiffOptions::default(),
44 max_variables_in_memory: 100_000,
45 block_size: 10_000,
46 refinement_passes: 3,
47 use_disk_storage: false,
48 mmap_threshold: 1_000_000,
49 compression_level: 3,
50 }
51 }
52}
53
54struct AdvancedScaleState {
56 variables: VariableStorage,
58 gradient: Option<CsrArray<f64>>,
60 blocks: Vec<VariableBlock>,
62 #[allow(dead_code)]
64 active_variables: Vec<usize>,
65 #[allow(dead_code)]
67 temp_dir: Option<PathBuf>,
68}
69
70enum VariableStorage {
72 Memory(Array1<f64>),
74 Disk {
76 file_path: PathBuf,
77 size: usize,
78 #[allow(dead_code)]
79 buffer: Vec<f64>, #[allow(dead_code)]
81 active_indices: Vec<usize>,
82 },
83}
84
85#[derive(Debug, Clone)]
87struct VariableBlock {
88 start_idx: usize,
90 size: usize,
92 priority: f64,
94 last_updated: usize,
96}
97
98impl AdvancedScaleState {
99 fn new(x0: Array1<f64>, options: &AdvancedScaleOptions) -> Result<Self, OptimizeError> {
100 let n = x0.len();
101
102 let variables = if options.use_disk_storage || n > options.mmap_threshold {
104 let temp_dir = options
106 .memory_options
107 .temp_dir
108 .clone()
109 .unwrap_or_else(std::env::temp_dir);
110
111 std::fs::create_dir_all(&temp_dir).map_err(|e| {
112 OptimizeError::ComputationError(format!("Failed to create temp directory: {}", e))
113 })?;
114
115 let file_path = temp_dir.join(format!("advancedscale_vars_{}.dat", std::process::id()));
116
117 let mut file = File::create(&file_path).map_err(|e| {
119 OptimizeError::ComputationError(format!("Failed to create temp file: {}", e))
120 })?;
121
122 let bytes: Vec<u8> = x0
123 .as_slice()
124 .unwrap()
125 .iter()
126 .flat_map(|&x| x.to_le_bytes())
127 .collect();
128 file.write_all(&bytes).map_err(|e| {
129 OptimizeError::ComputationError(format!("Failed to write to temp file: {}", e))
130 })?;
131
132 VariableStorage::Disk {
133 file_path,
134 size: n,
135 buffer: vec![0.0; options.max_variables_in_memory.min(n)],
136 active_indices: Vec::new(),
137 }
138 } else {
139 VariableStorage::Memory(x0)
140 };
141
142 let blocks = create_variable_blocks(n, options.block_size);
144
145 Ok(Self {
146 variables,
147 gradient: None,
148 blocks,
149 active_variables: Vec::new(),
150 temp_dir: options.memory_options.temp_dir.clone(),
151 })
152 }
153
154 fn get_variables(&mut self) -> Result<Array1<f64>, OptimizeError> {
156 match &mut self.variables {
157 VariableStorage::Memory(x) => Ok(x.clone()),
158 VariableStorage::Disk {
159 file_path,
160 size,
161 buffer: _,
162 active_indices: _,
163 } => {
164 let mut file = File::open(file_path).map_err(|e| {
166 OptimizeError::ComputationError(format!("Failed to open temp file: {}", e))
167 })?;
168
169 let mut bytes = vec![0u8; *size * 8];
170 file.read_exact(&mut bytes).map_err(|e| {
171 OptimizeError::ComputationError(format!("Failed to read from temp file: {}", e))
172 })?;
173
174 let values: Result<Vec<f64>, _> = bytes
175 .chunks_exact(8)
176 .map(|chunk| {
177 let array: [u8; 8] = chunk.try_into().unwrap();
178 Ok(f64::from_le_bytes(array))
179 })
180 .collect();
181
182 let values = values.map_err(|_: std::io::Error| {
183 OptimizeError::ComputationError("Failed to deserialize variables".to_string())
184 })?;
185
186 Ok(Array1::from_vec(values))
187 }
188 }
189 }
190
191 fn update_variables(&mut self, new_x: &Array1<f64>) -> Result<(), OptimizeError> {
193 match &mut self.variables {
194 VariableStorage::Memory(x) => {
195 x.assign(new_x);
196 Ok(())
197 }
198 VariableStorage::Disk {
199 file_path,
200 size: _,
201 buffer: _,
202 active_indices: _,
203 } => {
204 let mut file = OpenOptions::new()
206 .write(true)
207 .open(file_path)
208 .map_err(|e| {
209 OptimizeError::ComputationError(format!("Failed to open temp file: {}", e))
210 })?;
211
212 let bytes: Vec<u8> = new_x
213 .as_slice()
214 .unwrap()
215 .iter()
216 .flat_map(|&x| x.to_le_bytes())
217 .collect();
218
219 file.seek(SeekFrom::Start(0)).map_err(|e| {
220 OptimizeError::ComputationError(format!("Failed to seek in temp file: {}", e))
221 })?;
222
223 file.write_all(&bytes).map_err(|e| {
224 OptimizeError::ComputationError(format!("Failed to write to temp file: {}", e))
225 })?;
226
227 Ok(())
228 }
229 }
230 }
231}
232
233impl Drop for AdvancedScaleState {
234 fn drop(&mut self) {
235 if let VariableStorage::Disk { file_path, .. } = &self.variables {
237 let _ = std::fs::remove_file(file_path);
238 }
239 }
240}
241
242#[allow(dead_code)]
244fn create_variable_blocks(n: usize, block_size: usize) -> Vec<VariableBlock> {
245 let mut _blocks = Vec::new();
246 let num_blocks = n.div_ceil(block_size);
247
248 for i in 0..num_blocks {
249 let start_idx = i * block_size;
250 let end_idx = std::cmp::min((i + 1) * block_size, n);
251 let size = end_idx - start_idx;
252
253 _blocks.push(VariableBlock {
254 start_idx,
255 size,
256 priority: 1.0, last_updated: 0,
258 });
259 }
260
261 _blocks
262}
263
264#[allow(dead_code)]
266pub fn minimize_advanced_scale<F, S>(
267 fun: F,
268 x0: Array1<f64>,
269 options: &AdvancedScaleOptions,
270) -> Result<OptimizeResult<S>, OptimizeError>
271where
272 F: Fn(&ArrayView1<f64>) -> S + Clone + Sync,
273 S: Into<f64> + Clone + Send,
274{
275 let n = x0.len();
276
277 if n < options.max_variables_in_memory {
278 return super::memory_efficient::minimize_memory_efficient_lbfgs(
280 fun,
281 x0,
282 &options.memory_options,
283 );
284 }
285
286 println!(
287 "Starting advanced-large-scale optimization for {} variables",
288 n
289 );
290
291 let mut state = AdvancedScaleState::new(x0, options)?;
293 let mut iteration = 0;
294 let mut total_nfev = 0;
295
296 for pass in 0..options.refinement_passes {
298 println!(
299 "Progressive refinement pass {}/{}",
300 pass + 1,
301 options.refinement_passes
302 );
303
304 if let Some(ref gradient) = state.gradient {
306 update_block_priorities(&mut state.blocks, gradient);
307 }
308
309 state
311 .blocks
312 .sort_by(|a, b| b.priority.partial_cmp(&a.priority).unwrap());
313
314 let num_blocks = state.blocks.len();
316 for i in 0..num_blocks {
317 iteration += 1;
318
319 let current_x = state.get_variables()?;
321 let block_start = state.blocks[i].start_idx;
322 let block_size = state.blocks[i].size;
323 let block_x = current_x
324 .slice(scirs2_core::ndarray::s![
325 block_start..block_start + block_size
326 ])
327 .to_owned();
328
329 let block_result = optimize_block(
331 &fun,
332 ¤t_x,
333 block_start,
334 block_x,
335 &options.memory_options,
336 )?;
337
338 total_nfev += block_result.nfev;
339
340 let mut new_x = current_x.clone();
342 new_x
343 .slice_mut(scirs2_core::ndarray::s![
344 block_start..block_start + block_size
345 ])
346 .assign(&block_result.x);
347
348 state.update_variables(&new_x)?;
349 state.blocks[i].last_updated = iteration;
350
351 if iteration % 10 == 0 {
353 let f_val = fun(&new_x.view()).into();
354 let grad_norm = estimate_sparse_gradient_norm(&fun, &new_x.view(), options)?;
355
356 if grad_norm < options.memory_options.base_options.gtol {
357 println!("Converged at pass {}, iteration {}", pass + 1, iteration);
358
359 return Ok(OptimizeResult {
360 x: new_x.clone(),
361 fun: fun(&new_x.view()),
362 nit: iteration,
363 func_evals: total_nfev,
364 nfev: total_nfev,
365 success: true,
366 message: "Advanced-scale optimization converged successfully.".to_string(),
367 jacobian: None,
368 hessian: None,
369 });
370 }
371
372 println!(
373 "Iteration {}: f = {:.6e}, ||g|| ≈ {:.6e}",
374 iteration, f_val, grad_norm
375 );
376 }
377
378 if iteration >= options.memory_options.base_options.max_iter {
379 break;
380 }
381 }
382 }
383
384 let final_x = state.get_variables()?;
385
386 Ok(OptimizeResult {
387 x: final_x.clone(),
388 fun: fun(&final_x.view()),
389 nit: iteration,
390 func_evals: total_nfev,
391 nfev: total_nfev,
392 success: iteration < options.memory_options.base_options.max_iter,
393 message: if iteration < options.memory_options.base_options.max_iter {
394 "Advanced-scale optimization completed successfully.".to_string()
395 } else {
396 "Maximum iterations reached.".to_string()
397 },
398 jacobian: None,
399 hessian: None,
400 })
401}
402
403#[allow(dead_code)]
405fn optimize_block<F, S>(
406 fun: &F,
407 full_x: &Array1<f64>,
408 block_start: usize,
409 block_x0: Array1<f64>,
410 options: &MemoryOptions,
411) -> Result<OptimizeResult<f64>, OptimizeError>
412where
413 F: Fn(&ArrayView1<f64>) -> S,
414 S: Into<f64> + Clone,
415{
416 let block_fun = |block_vars: &ArrayView1<f64>| -> f64 {
418 let mut temp_x = full_x.clone();
419 temp_x
420 .slice_mut(scirs2_core::ndarray::s![
421 block_start..block_start + block_vars.len()
422 ])
423 .assign(block_vars);
424 fun(&temp_x.view()).into()
425 };
426
427 super::memory_efficient::minimize_memory_efficient_lbfgs(block_fun, block_x0, options)
429}
430
431#[allow(dead_code)]
433fn update_block_priorities(blocks: &mut [VariableBlock], gradient: &CsrArray<f64>) {
434 for block in blocks {
435 let mut total_grad_mag = 0.0;
437 let mut count = 0;
438
439 for idx in block.start_idx..block.start_idx + block.size {
441 if let Some(&grad_val) = gradient.get_data().get(idx) {
442 total_grad_mag += grad_val.abs();
443 count += 1;
444 }
445 }
446
447 if count > 0 {
448 block.priority = total_grad_mag / count as f64;
449 } else {
450 block.priority *= 0.9; }
452 }
453}
454
455#[allow(dead_code)]
457fn estimate_sparse_gradient_norm<F, S>(
458 fun: &F,
459 x: &ArrayView1<f64>,
460 options: &AdvancedScaleOptions,
461) -> Result<f64, OptimizeError>
462where
463 F: Fn(&ArrayView1<f64>) -> S,
464 S: Into<f64>,
465{
466 let n = x.len();
467
468 if n > options.max_variables_in_memory {
470 let sample_size = (options.max_variables_in_memory / 10).min(1000).max(10);
472 let step_size = options.sparse_options.rel_step.unwrap_or(1e-8);
473
474 let mut gradient_norm_squared = 0.0;
475 let f0 = fun(x).into();
476
477 let step = n / sample_size;
479 for i in (0..n).step_by(step).take(sample_size) {
480 let mut x_pert = x.to_owned();
482 let h = step_size * (1.0 + x[i].abs());
483 x_pert[i] += h;
484
485 let f_pert = fun(&x_pert.view()).into();
486 let grad_i = (f_pert - f0) / h;
487 gradient_norm_squared += grad_i * grad_i;
488 }
489
490 let scaling_factor = n as f64 / sample_size as f64;
492 Ok((gradient_norm_squared * scaling_factor).sqrt())
493 } else {
494 compute_sparse_gradient_norm(fun, x, &options.sparse_options)
496 }
497}
498
499#[allow(dead_code)]
501fn compute_sparse_gradient_norm<F, S>(
502 fun: &F,
503 x: &ArrayView1<f64>,
504 sparse_options: &SparseFiniteDiffOptions,
505) -> Result<f64, OptimizeError>
506where
507 F: Fn(&ArrayView1<f64>) -> S,
508 S: Into<f64>,
509{
510 let n = x.len();
511 let step_size = sparse_options.rel_step.unwrap_or(1e-8);
512 let _f0 = fun(x).into();
513
514 let mut gradient_norm_squared = 0.0;
516
517 for i in 0..n {
518 let h = step_size * (1.0 + x[i].abs());
519
520 let mut x_forward = x.to_owned();
522 x_forward[i] += h;
523 let f_forward = fun(&x_forward.view()).into();
524
525 let mut x_backward = x.to_owned();
527 x_backward[i] -= h;
528 let f_backward = fun(&x_backward.view()).into();
529
530 let grad_i = (f_forward - f_backward) / (2.0 * h);
532 gradient_norm_squared += grad_i * grad_i;
533 }
534
535 Ok(gradient_norm_squared.sqrt())
536}
537
538#[allow(dead_code)]
540pub fn create_advanced_scale_optimizer(
541 problem_size: usize,
542 available_memory_mb: usize,
543 estimated_sparsity: f64, ) -> AdvancedScaleOptions {
545 let available_bytes = available_memory_mb * 1024 * 1024;
546
547 let bytes_per_var = std::mem::size_of::<f64>() * 8; let max_vars_in_memory = (available_bytes / bytes_per_var).min(problem_size);
550
551 let block_size = if estimated_sparsity < 0.1 {
553 (max_vars_in_memory / 4).max(1000)
555 } else {
556 (max_vars_in_memory / 10).max(100)
558 };
559
560 let use_disk = problem_size >= 1_000_000 || available_memory_mb < 512;
562
563 let refinement_passes = if problem_size > 10_000_000 {
565 5
566 } else if problem_size > 1_000_000 {
567 4
568 } else {
569 3
570 };
571
572 AdvancedScaleOptions {
573 memory_options: super::memory_efficient::create_memory_efficient_optimizer(
574 max_vars_in_memory,
575 available_memory_mb / 2,
576 ),
577 sparse_options: SparseFiniteDiffOptions {
578 max_group_size: (estimated_sparsity * 1000.0) as usize,
579 ..Default::default()
580 },
581 max_variables_in_memory: max_vars_in_memory,
582 block_size,
583 refinement_passes,
584 use_disk_storage: use_disk,
585 mmap_threshold: available_bytes / (2 * std::mem::size_of::<f64>()),
586 compression_level: if available_memory_mb < 256 { 6 } else { 3 },
587 }
588}
589
590#[cfg(test)]
591mod tests {
592 use super::*;
593 use approx::assert_abs_diff_eq;
594
595 #[test]
596 fn test_advanced_scale_small_problem() {
597 let quadratic = |x: &ArrayView1<f64>| -> f64 { x.mapv(|xi| xi.powi(2)).sum() };
599
600 let n = 50; let x0 = Array1::ones(n);
602 let options = AdvancedScaleOptions::default();
603
604 let result = minimize_advanced_scale(quadratic, x0, &options).unwrap();
605
606 assert!(result.success);
607 for i in 0..n {
609 assert_abs_diff_eq!(result.x[i], 0.0, epsilon = 1e-3);
610 }
611 }
612
613 #[test]
614 fn test_variable_blocks() {
615 let blocks = create_variable_blocks(1000, 100);
616 assert_eq!(blocks.len(), 10);
617 assert_eq!(blocks[0].start_idx, 0);
618 assert_eq!(blocks[0].size, 100);
619 assert_eq!(blocks[9].start_idx, 900);
620 assert_eq!(blocks[9].size, 100);
621 }
622
623 #[test]
624 fn test_auto_parameter_selection() {
625 let options = create_advanced_scale_optimizer(1_000_000, 1024, 0.05);
626
627 assert!(options.max_variables_in_memory > 0);
628 assert!(options.block_size > 0);
629 assert!(options.refinement_passes >= 3);
630 assert!(options.use_disk_storage); }
632}