1use crate::error::OptimizeError;
8use crate::sparse_numdiff::SparseFiniteDiffOptions;
9use crate::unconstrained::memory_efficient::MemoryOptions;
10use crate::unconstrained::result::OptimizeResult;
11use 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 UltraScaleOptions {
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 UltraScaleOptions {
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 UltraScaleState {
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 UltraScaleState {
99 fn new(x0: Array1<f64>, options: &UltraScaleOptions) -> 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!("ultrascale_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 UltraScaleState {
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
242fn create_variable_blocks(n: usize, block_size: usize) -> Vec<VariableBlock> {
244 let mut blocks = Vec::new();
245 let num_blocks = n.div_ceil(block_size);
246
247 for i in 0..num_blocks {
248 let start_idx = i * block_size;
249 let end_idx = std::cmp::min((i + 1) * block_size, n);
250 let size = end_idx - start_idx;
251
252 blocks.push(VariableBlock {
253 start_idx,
254 size,
255 priority: 1.0, last_updated: 0,
257 });
258 }
259
260 blocks
261}
262
263pub fn minimize_ultra_scale<F, S>(
265 fun: F,
266 x0: Array1<f64>,
267 options: &UltraScaleOptions,
268) -> Result<OptimizeResult<S>, OptimizeError>
269where
270 F: Fn(&ArrayView1<f64>) -> S + Clone + Sync,
271 S: Into<f64> + Clone + Send,
272{
273 let n = x0.len();
274
275 if n < options.max_variables_in_memory {
276 return super::memory_efficient::minimize_memory_efficient_lbfgs(
278 fun,
279 x0,
280 &options.memory_options,
281 );
282 }
283
284 println!(
285 "Starting ultra-large-scale optimization for {} variables",
286 n
287 );
288
289 let mut state = UltraScaleState::new(x0, options)?;
291 let mut iteration = 0;
292 let mut total_nfev = 0;
293
294 for pass in 0..options.refinement_passes {
296 println!(
297 "Progressive refinement pass {}/{}",
298 pass + 1,
299 options.refinement_passes
300 );
301
302 if let Some(ref gradient) = state.gradient {
304 update_block_priorities(&mut state.blocks, gradient);
305 }
306
307 state
309 .blocks
310 .sort_by(|a, b| b.priority.partial_cmp(&a.priority).unwrap());
311
312 let num_blocks = state.blocks.len();
314 for i in 0..num_blocks {
315 iteration += 1;
316
317 let current_x = state.get_variables()?;
319 let block_start = state.blocks[i].start_idx;
320 let block_size = state.blocks[i].size;
321 let block_x = current_x
322 .slice(ndarray::s![block_start..block_start + block_size])
323 .to_owned();
324
325 let block_result = optimize_block(
327 &fun,
328 ¤t_x,
329 block_start,
330 block_x,
331 &options.memory_options,
332 )?;
333
334 total_nfev += block_result.nfev;
335
336 let mut new_x = current_x.clone();
338 new_x
339 .slice_mut(ndarray::s![block_start..block_start + block_size])
340 .assign(&block_result.x);
341
342 state.update_variables(&new_x)?;
343 state.blocks[i].last_updated = iteration;
344
345 if iteration % 10 == 0 {
347 let f_val = fun(&new_x.view()).into();
348 let grad_norm = estimate_sparse_gradient_norm(&fun, &new_x.view(), options)?;
349
350 if grad_norm < options.memory_options.base_options.gtol {
351 println!("Converged at pass {}, iteration {}", pass + 1, iteration);
352
353 return Ok(OptimizeResult {
354 x: new_x.clone(),
355 fun: fun(&new_x.view()),
356 iterations: iteration,
357 nit: iteration,
358 func_evals: total_nfev,
359 nfev: total_nfev,
360 success: true,
361 message: "Ultra-scale optimization converged successfully.".to_string(),
362 jacobian: None,
363 hessian: None,
364 });
365 }
366
367 println!(
368 "Iteration {}: f = {:.6e}, ||g|| ≈ {:.6e}",
369 iteration, f_val, grad_norm
370 );
371 }
372
373 if iteration >= options.memory_options.base_options.max_iter {
374 break;
375 }
376 }
377 }
378
379 let final_x = state.get_variables()?;
380
381 Ok(OptimizeResult {
382 x: final_x.clone(),
383 fun: fun(&final_x.view()),
384 iterations: iteration,
385 nit: iteration,
386 func_evals: total_nfev,
387 nfev: total_nfev,
388 success: iteration < options.memory_options.base_options.max_iter,
389 message: if iteration < options.memory_options.base_options.max_iter {
390 "Ultra-scale optimization completed successfully.".to_string()
391 } else {
392 "Maximum iterations reached.".to_string()
393 },
394 jacobian: None,
395 hessian: None,
396 })
397}
398
399fn optimize_block<F, S>(
401 fun: &F,
402 full_x: &Array1<f64>,
403 block_start: usize,
404 block_x0: Array1<f64>,
405 options: &MemoryOptions,
406) -> Result<OptimizeResult<f64>, OptimizeError>
407where
408 F: Fn(&ArrayView1<f64>) -> S,
409 S: Into<f64> + Clone,
410{
411 let block_fun = |block_vars: &ArrayView1<f64>| -> f64 {
413 let mut temp_x = full_x.clone();
414 temp_x
415 .slice_mut(ndarray::s![block_start..block_start + block_vars.len()])
416 .assign(block_vars);
417 fun(&temp_x.view()).into()
418 };
419
420 super::memory_efficient::minimize_memory_efficient_lbfgs(block_fun, block_x0, options)
422}
423
424fn update_block_priorities(blocks: &mut [VariableBlock], gradient: &CsrArray<f64>) {
426 for block in blocks {
427 let mut total_grad_mag = 0.0;
429 let mut count = 0;
430
431 for idx in block.start_idx..block.start_idx + block.size {
433 if let Some(&grad_val) = gradient.get_data().get(idx) {
434 total_grad_mag += grad_val.abs();
435 count += 1;
436 }
437 }
438
439 if count > 0 {
440 block.priority = total_grad_mag / count as f64;
441 } else {
442 block.priority *= 0.9; }
444 }
445}
446
447fn estimate_sparse_gradient_norm<F, S>(
449 fun: &F,
450 x: &ArrayView1<f64>,
451 options: &UltraScaleOptions,
452) -> Result<f64, OptimizeError>
453where
454 F: Fn(&ArrayView1<f64>) -> S,
455 S: Into<f64>,
456{
457 let _sparse_options = &options.sparse_options;
460 let _fun_val = fun(x).into();
461 Ok(1e-6) }
463
464pub fn create_ultra_scale_optimizer(
466 problem_size: usize,
467 available_memory_mb: usize,
468 estimated_sparsity: f64, ) -> UltraScaleOptions {
470 let available_bytes = available_memory_mb * 1024 * 1024;
471
472 let bytes_per_var = std::mem::size_of::<f64>() * 8; let max_vars_in_memory = (available_bytes / bytes_per_var).min(problem_size);
475
476 let block_size = if estimated_sparsity < 0.1 {
478 (max_vars_in_memory / 4).max(1000)
480 } else {
481 (max_vars_in_memory / 10).max(100)
483 };
484
485 let use_disk = problem_size >= 1_000_000 || available_memory_mb < 512;
487
488 let refinement_passes = if problem_size > 10_000_000 {
490 5
491 } else if problem_size > 1_000_000 {
492 4
493 } else {
494 3
495 };
496
497 UltraScaleOptions {
498 memory_options: super::memory_efficient::create_memory_efficient_optimizer(
499 max_vars_in_memory,
500 available_memory_mb / 2,
501 ),
502 sparse_options: SparseFiniteDiffOptions {
503 max_group_size: (estimated_sparsity * 1000.0) as usize,
504 ..Default::default()
505 },
506 max_variables_in_memory: max_vars_in_memory,
507 block_size,
508 refinement_passes,
509 use_disk_storage: use_disk,
510 mmap_threshold: available_bytes / (2 * std::mem::size_of::<f64>()),
511 compression_level: if available_memory_mb < 256 { 6 } else { 3 },
512 }
513}
514
515#[cfg(test)]
516mod tests {
517 use super::*;
518 use approx::assert_abs_diff_eq;
519
520 #[test]
521 fn test_ultra_scale_small_problem() {
522 let quadratic = |x: &ArrayView1<f64>| -> f64 { x.mapv(|xi| xi.powi(2)).sum() };
524
525 let n = 50; let x0 = Array1::ones(n);
527 let options = UltraScaleOptions::default();
528
529 let result = minimize_ultra_scale(quadratic, x0, &options).unwrap();
530
531 assert!(result.success);
532 for i in 0..n {
534 assert_abs_diff_eq!(result.x[i], 0.0, epsilon = 1e-3);
535 }
536 }
537
538 #[test]
539 fn test_variable_blocks() {
540 let blocks = create_variable_blocks(1000, 100);
541 assert_eq!(blocks.len(), 10);
542 assert_eq!(blocks[0].start_idx, 0);
543 assert_eq!(blocks[0].size, 100);
544 assert_eq!(blocks[9].start_idx, 900);
545 assert_eq!(blocks[9].size, 100);
546 }
547
548 #[test]
549 fn test_auto_parameter_selection() {
550 let options = create_ultra_scale_optimizer(1_000_000, 1024, 0.05);
551
552 assert!(options.max_variables_in_memory > 0);
553 assert!(options.block_size > 0);
554 assert!(options.refinement_passes >= 3);
555 assert!(options.use_disk_storage); }
557}