1use crate::error::OptimizeError;
8use crate::sparse_numdiff::SparseFiniteDiffOptions;
9use crate::unconstrained::line_search::backtracking_line_search;
10use crate::unconstrained::result::OptimizeResult;
11use crate::unconstrained::utils::check_convergence;
12use crate::unconstrained::Options;
13use ndarray::{Array1, Array2, ArrayView1, Axis};
14use scirs2_sparse::{csr_array::CsrArray, sparray::SparseArray};
15#[derive(Debug, Clone)]
20pub struct EfficientSparseOptions {
21 pub base_options: Options,
23 pub sparse_fd_options: SparseFiniteDiffOptions,
25 pub auto_detect_sparsity: bool,
27 pub sparsity_threshold: f64,
29 pub max_sparsity_nit: usize,
31 pub adaptive_sparsity: bool,
33 pub use_sparse_hessian: bool,
35 pub sparse_percentage_threshold: f64,
37 pub parallel_sparse_ops: bool,
39}
40
41impl Default for EfficientSparseOptions {
42 fn default() -> Self {
43 Self {
44 base_options: Options::default(),
45 sparse_fd_options: SparseFiniteDiffOptions::default(),
46 auto_detect_sparsity: true,
47 sparsity_threshold: 1e-12,
48 max_sparsity_nit: 5,
49 adaptive_sparsity: true,
50 use_sparse_hessian: true,
51 sparse_percentage_threshold: 0.1, parallel_sparse_ops: true,
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
59pub struct SparsityInfo {
60 pub jacobian_pattern: Option<CsrArray<f64>>,
62 pub hessian_pattern: Option<CsrArray<f64>>,
64 pub jacobian_nnz: usize,
66 pub hessian_nnz: usize,
68 pub total_elements: usize,
70 pub jacobian_sparsity: f64,
72 pub hessian_sparsity: f64,
74}
75
76impl SparsityInfo {
77 fn new(n: usize) -> Self {
78 Self {
79 jacobian_pattern: None,
80 hessian_pattern: None,
81 jacobian_nnz: 0,
82 hessian_nnz: 0,
83 total_elements: n,
84 jacobian_sparsity: 1.0,
85 hessian_sparsity: 1.0,
86 }
87 }
88}
89
90struct SparseQuasiNewton {
92 h_inv_sparse: Option<CsrArray<f64>>,
94 sparse_grad_history: Vec<CsrArray<f64>>,
96 sparse_step_history: Vec<Array1<f64>>,
98 max_history: usize,
100 #[allow(dead_code)]
102 pattern: Option<CsrArray<f64>>,
103}
104
105impl SparseQuasiNewton {
106 fn new(_n: usize, max_history: usize) -> Self {
107 Self {
108 h_inv_sparse: None,
109 sparse_grad_history: Vec::new(),
110 sparse_step_history: Vec::new(),
111 max_history,
112 pattern: None,
113 }
114 }
115
116 fn update_sparse_bfgs(
117 &mut self,
118 s: &Array1<f64>,
119 y_sparse: &CsrArray<f64>,
120 sparsity_pattern: &CsrArray<f64>,
121 ) -> Result<(), OptimizeError> {
122 let y = sparse_to_dense(y_sparse);
124
125 let s_dot_y = s.dot(&y);
126 if s_dot_y <= 1e-10 {
127 return Ok(()); }
129
130 if self.h_inv_sparse.is_none() {
132 self.h_inv_sparse = Some(create_sparse_identity(s.len(), sparsity_pattern)?);
133 }
134
135 if let Some(ref mut h_inv) = self.h_inv_sparse {
139 let h_inv_dense = sparse_to_dense_matrix(h_inv);
141 let h_inv_updated = dense_bfgs_update(&h_inv_dense, s, &y)?;
142
143 *h_inv = dense_to_sparse_matrix(&h_inv_updated, sparsity_pattern)?;
145 }
146
147 self.sparse_step_history.push(s.clone());
149 self.sparse_grad_history.push(y_sparse.clone());
150
151 while self.sparse_step_history.len() > self.max_history {
152 self.sparse_step_history.remove(0);
153 self.sparse_grad_history.remove(0);
154 }
155
156 Ok(())
157 }
158
159 fn apply_inverse_hessian(
160 &self,
161 g_sparse: &CsrArray<f64>,
162 ) -> Result<Array1<f64>, OptimizeError> {
163 if let Some(ref h_inv) = self.h_inv_sparse {
164 sparse_matrix_vector_product(h_inv, g_sparse)
166 } else {
167 Ok(-sparse_to_dense(g_sparse))
169 }
170 }
171}
172
173#[allow(dead_code)]
175pub fn minimize_efficient_sparse_newton<F, G>(
176 mut fun: F,
177 mut grad: G,
178 x0: Array1<f64>,
179 options: &EfficientSparseOptions,
180) -> Result<OptimizeResult<f64>, OptimizeError>
181where
182 F: FnMut(&ArrayView1<f64>) -> f64 + Sync,
183 G: FnMut(&ArrayView1<f64>) -> Array1<f64>,
184{
185 let n = x0.len();
186 let base_opts = &options.base_options;
187
188 let mut x = x0.to_owned();
190 let mut f = fun(&x.view());
191 let mut g_dense = grad(&x.view());
192
193 let mut sparsity_info = SparsityInfo::new(n);
195 let mut sparse_qn = SparseQuasiNewton::new(n, 10);
196
197 if options.auto_detect_sparsity {
199 sparsity_info = detect_sparsity_patterns(&mut fun, &mut grad, &x.view(), options)?;
200 println!(
201 "Detected sparsity: Jacobian {:.1}%, Hessian {:.1}%",
202 sparsity_info.jacobian_sparsity * 100.0,
203 sparsity_info.hessian_sparsity * 100.0
204 );
205 }
206
207 let mut g_sparse = if should_use_sparse(&g_dense, options.sparse_percentage_threshold) {
209 dense_to_sparse_vector(&g_dense, options.sparsity_threshold)?
210 } else {
211 dense_to_sparse_vector(&g_dense, 0.0)? };
213
214 let mut iter = 0;
215 let mut nfev = 1;
216 let mut _njev = 1;
217
218 while iter < base_opts.max_iter {
220 let grad_norm = sparse_vector_norm(&g_sparse);
222 if grad_norm < base_opts.gtol {
223 break;
224 }
225
226 let p = if options.use_sparse_hessian && sparsity_info.hessian_pattern.is_some() {
228 compute_sparse_newton_direction(
230 &mut fun,
231 &x.view(),
232 &g_sparse,
233 &sparsity_info,
234 options,
235 )?
236 } else {
237 sparse_qn.apply_inverse_hessian(&g_sparse)?
239 };
240
241 let p = apply_bounds_projection(&p, &x, &options.base_options);
243
244 let alpha_init = 1.0;
246 let (alpha, f_new) = backtracking_line_search(
247 &mut fun,
248 &x.view(),
249 f,
250 &p.view(),
251 &sparse_to_dense(&g_sparse).view(),
252 alpha_init,
253 0.0001,
254 0.5,
255 base_opts.bounds.as_ref(),
256 );
257 nfev += 1;
258
259 let s = alpha * &p;
261 let x_new = &x + &s;
262
263 if s.mapv(|x| x.abs()).sum() < base_opts.xtol {
265 x = x_new;
266 break;
267 }
268
269 let g_new_dense = grad(&x_new.view());
271 _njev += 1;
272
273 let g_new_sparse = if should_use_sparse(&g_new_dense, options.sparse_percentage_threshold) {
274 dense_to_sparse_vector(&g_new_dense, options.sparsity_threshold)?
275 } else {
276 dense_to_sparse_vector(&g_new_dense, 0.0)?
277 };
278
279 if check_convergence(
281 f - f_new,
282 0.0,
283 sparse_vector_norm(&g_new_sparse),
284 base_opts.ftol,
285 0.0,
286 base_opts.gtol,
287 ) {
288 x = x_new;
289 let _g_sparse_final = g_new_sparse; break;
291 }
292
293 let y_sparse = sparse_vector_subtract(&g_new_sparse, &g_sparse)?;
295 if let Some(ref pattern) = sparsity_info.hessian_pattern {
296 sparse_qn.update_sparse_bfgs(&s, &y_sparse, pattern)?;
297 }
298
299 if options.adaptive_sparsity && iter % 10 == 0 {
301 refine_sparsity_pattern(&mut sparsity_info, &g_new_sparse, options)?;
302 }
303
304 x = x_new;
306 f = f_new;
307 g_sparse = g_new_sparse;
308 g_dense = g_new_dense;
309 iter += 1;
310 }
311
312 if let Some(bounds) = &base_opts.bounds {
314 bounds.project(x.as_slice_mut().unwrap());
315 }
316
317 Ok(OptimizeResult {
318 x,
319 fun: f,
320 nit: iter,
321 func_evals: nfev,
322 nfev,
323 success: iter < base_opts.max_iter,
324 message: if iter < base_opts.max_iter {
325 format!(
326 "Sparse optimization terminated successfully. Sparsity: {:.1}%",
327 sparsity_info.jacobian_sparsity * 100.0
328 )
329 } else {
330 "Maximum iterations reached.".to_string()
331 },
332 jacobian: Some(g_dense),
333 hessian: None,
334 })
335}
336
337#[allow(dead_code)]
339fn detect_sparsity_patterns<F, G>(
340 _fun: &mut F,
341 grad: &mut G,
342 x: &ArrayView1<f64>,
343 options: &EfficientSparseOptions,
344) -> Result<SparsityInfo, OptimizeError>
345where
346 F: FnMut(&ArrayView1<f64>) -> f64,
347 G: FnMut(&ArrayView1<f64>) -> Array1<f64>,
348{
349 let n = x.len();
350 let mut sparsity_info = SparsityInfo::new(n);
351
352 let mut jacobian_pattern = Vec::new();
354 let eps = options.sparse_fd_options.rel_step.unwrap_or(1e-8);
355 let g0 = grad(x);
356
357 let mut nnz_count = 0;
358 for i in 0..n {
359 let mut x_pert = x.to_owned();
360 x_pert[i] += eps;
361 let g_pert = grad(&x_pert.view());
362
363 for j in 0..n {
364 let diff = (g_pert[j] - g0[j]).abs();
365 if diff > options.sparsity_threshold {
366 jacobian_pattern.push((j, i, 1.0)); nnz_count += 1;
368 }
369 }
370 }
371
372 if nnz_count > 0 {
374 let mut rows = Vec::new();
375 let mut cols = Vec::new();
376 let mut data = Vec::new();
377
378 for (row, col, val) in jacobian_pattern {
379 rows.push(row);
380 cols.push(col);
381 data.push(val);
382 }
383
384 sparsity_info.jacobian_pattern =
385 Some(CsrArray::from_triplets(&rows, &cols, &data, (n, n), false)?);
386 sparsity_info.jacobian_nnz = nnz_count;
387 sparsity_info.jacobian_sparsity = nnz_count as f64 / (n * n) as f64;
388 }
389
390 if options.use_sparse_hessian {
392 sparsity_info.hessian_pattern = sparsity_info.jacobian_pattern.clone();
395 sparsity_info.hessian_nnz = sparsity_info.jacobian_nnz;
396 sparsity_info.hessian_sparsity = sparsity_info.jacobian_sparsity;
397 }
398
399 Ok(sparsity_info)
400}
401
402#[allow(dead_code)]
404fn compute_sparse_newton_direction<F>(
405 fun: &mut F,
406 x: &ArrayView1<f64>,
407 g_sparse: &CsrArray<f64>,
408 sparsity_info: &SparsityInfo,
409 options: &EfficientSparseOptions,
410) -> Result<Array1<f64>, OptimizeError>
411where
412 F: FnMut(&ArrayView1<f64>) -> f64 + Sync,
413{
414 if let Some(hessian_pattern) = &sparsity_info.hessian_pattern {
415 let sparse_hessian = compute_sparse_hessian_fnmut(fun, x, hessian_pattern, options)?;
417
418 solve_sparse_newton_system(&sparse_hessian, g_sparse)
420 } else {
421 Ok(-sparse_to_dense(g_sparse))
423 }
424}
425
426#[allow(dead_code)]
428fn compute_sparse_hessian_fnmut<F>(
429 fun: &mut F,
430 x: &ArrayView1<f64>,
431 pattern: &CsrArray<f64>,
432 options: &EfficientSparseOptions,
433) -> Result<CsrArray<f64>, OptimizeError>
434where
435 F: FnMut(&ArrayView1<f64>) -> f64,
436{
437 let n = x.len();
438 let eps = 1e-8; let mut triplets = Vec::new();
442
443 let f0 = fun(x);
446
447 for i in 0..n {
448 for j in i..n {
449 if pattern.get(i, j) != 0.0 {
451 let h_i = eps * (1.0 + x[i].abs());
452 let h_j = eps * (1.0 + x[j].abs());
453
454 let hessian_element = if i == j {
455 let mut x_plus = x.to_owned();
457 x_plus[i] += h_i;
458 let f_plus = fun(&x_plus.view());
459
460 let mut x_minus = x.to_owned();
461 x_minus[i] -= h_i;
462 let f_minus = fun(&x_minus.view());
463
464 (f_plus - 2.0 * f0 + f_minus) / (h_i * h_i)
465 } else {
466 let mut x_plus_both = x.to_owned();
468 x_plus_both[i] += h_i;
469 x_plus_both[j] += h_j;
470 let f_plus_both = fun(&x_plus_both.view());
471
472 let mut x_plus_i = x.to_owned();
473 x_plus_i[i] += h_i;
474 let f_plus_i = fun(&x_plus_i.view());
475
476 let mut x_plus_j = x.to_owned();
477 x_plus_j[j] += h_j;
478 let f_plus_j = fun(&x_plus_j.view());
479
480 (f_plus_both - f_plus_i - f_plus_j + f0) / (h_i * h_j)
481 };
482
483 if hessian_element.abs() > options.sparse_percentage_threshold {
485 triplets.push((i, j, hessian_element));
486 if i != j {
488 triplets.push((j, i, hessian_element));
489 }
490 }
491 }
492 }
493 }
494
495 if triplets.is_empty() {
497 let mut identity_triplets = Vec::new();
499 for i in 0..n {
500 identity_triplets.push((i, i, 1e-6));
501 }
502
503 let rows: Vec<usize> = identity_triplets.iter().map(|(r, _, _)| *r).collect();
504 let cols: Vec<usize> = identity_triplets.iter().map(|(_, c, _)| *c).collect();
505 let data: Vec<f64> = identity_triplets.iter().map(|(_, _, d)| *d).collect();
506
507 CsrArray::from_triplets(&rows, &cols, &data, (n, n), false).map_err(|_| {
508 OptimizeError::ComputationError("Failed to create sparse Hessian".to_string())
509 })
510 } else {
511 let rows: Vec<usize> = triplets.iter().map(|(r, _, _)| *r).collect();
512 let cols: Vec<usize> = triplets.iter().map(|(_, c, _)| *c).collect();
513 let data: Vec<f64> = triplets.iter().map(|(_, _, d)| *d).collect();
514
515 CsrArray::from_triplets(&rows, &cols, &data, (n, n), false).map_err(|_| {
516 OptimizeError::ComputationError("Failed to create sparse Hessian".to_string())
517 })
518 }
519}
520
521#[allow(dead_code)]
523fn solve_sparse_newton_system(
524 hessian: &CsrArray<f64>,
525 gradient_sparse: &CsrArray<f64>,
526) -> Result<Array1<f64>, OptimizeError> {
527 let gradient_dense = sparse_to_dense(gradient_sparse);
529 let neg_gradient = -gradient_dense;
530
531 let hessian_dense = sparse_to_dense_matrix(hessian);
534
535 use scirs2_linalg::solve;
537
538 solve(&hessian_dense.view(), &neg_gradient.view(), None)
539 .map_err(|e| OptimizeError::ComputationError(format!("Newton system solve failed: {}", e)))
540}
541
542#[allow(dead_code)]
545fn should_use_sparse(vector: &Array1<f64>, threshold: f64) -> bool {
546 let nnz = vector.iter().filter(|&&x| x.abs() > 1e-12).count();
547 let sparsity = nnz as f64 / vector.len() as f64;
548 sparsity < threshold
549}
550
551#[allow(dead_code)]
552fn dense_to_sparse_vector(
553 dense: &Array1<f64>,
554 threshold: f64,
555) -> Result<CsrArray<f64>, OptimizeError> {
556 let n = dense.len();
557 let mut rows = Vec::new();
558 let mut cols = Vec::new();
559 let mut data = Vec::new();
560
561 for (i, &val) in dense.iter().enumerate() {
562 if val.abs() > threshold {
563 rows.push(0); cols.push(i);
565 data.push(val);
566 }
567 }
568
569 CsrArray::from_triplets(&rows, &cols, &data, (1, n), false)
570 .map_err(|_| OptimizeError::ComputationError("Failed to create sparse vector".to_string()))
571}
572
573#[allow(dead_code)]
574fn sparse_to_dense(sparse: &CsrArray<f64>) -> Array1<f64> {
575 let n = sparse.ncols();
576 let mut dense = Array1::zeros(n);
577
578 for col in 0..n {
580 dense[col] = sparse.get(0, col);
581 }
582
583 dense
584}
585
586#[allow(dead_code)]
587fn sparse_vector_norm(sparse: &CsrArray<f64>) -> f64 {
588 sparse.get_data().iter().map(|&x| x * x).sum::<f64>().sqrt()
589}
590
591#[allow(dead_code)]
592fn sparse_vector_subtract(
593 a: &CsrArray<f64>,
594 b: &CsrArray<f64>,
595) -> Result<CsrArray<f64>, OptimizeError> {
596 let a_dense = sparse_to_dense(a);
598 let b_dense = sparse_to_dense(b);
599 let diff = &a_dense - &b_dense;
600 dense_to_sparse_vector(&diff, 1e-12)
601}
602
603#[allow(dead_code)]
604fn apply_bounds_projection(p: &Array1<f64>, x: &Array1<f64>, options: &Options) -> Array1<f64> {
605 let mut p_proj = p.clone();
606
607 if let Some(bounds) = &options.bounds {
608 for i in 0..p.len() {
609 let mut can_decrease = true;
610 let mut can_increase = true;
611
612 if let Some(lb) = bounds.lower[i] {
613 if x[i] <= lb + options.eps {
614 can_decrease = false;
615 }
616 }
617 if let Some(ub) = bounds.upper[i] {
618 if x[i] >= ub - options.eps {
619 can_increase = false;
620 }
621 }
622
623 if (p[i] < 0.0 && !can_decrease) || (p[i] > 0.0 && !can_increase) {
624 p_proj[i] = 0.0;
625 }
626 }
627 }
628
629 p_proj
630}
631
632#[allow(dead_code)]
633fn refine_sparsity_pattern(
634 _sparsity_info: &mut SparsityInfo,
635 _gradient: &CsrArray<f64>,
636 _options: &EfficientSparseOptions,
637) -> Result<(), OptimizeError> {
638 Ok(())
642}
643
644#[allow(dead_code)]
647fn create_sparse_identity(
648 n: usize,
649 pattern: &CsrArray<f64>,
650) -> Result<CsrArray<f64>, OptimizeError> {
651 let mut rows = Vec::new();
652 let mut cols = Vec::new();
653 let mut data = Vec::new();
654
655 for i in 0..n {
656 if pattern.get(i, i) != 0.0 {
657 rows.push(i);
658 cols.push(i);
659 data.push(1.0);
660 }
661 }
662
663 CsrArray::from_triplets(&rows, &cols, &data, (n, n), false).map_err(|_| {
664 OptimizeError::ComputationError("Failed to create sparse identity".to_string())
665 })
666}
667
668#[allow(dead_code)]
669fn sparse_to_dense_matrix(sparse: &CsrArray<f64>) -> Array2<f64> {
670 let (m, n) = (sparse.nrows(), sparse.ncols());
671 let mut dense = Array2::zeros((m, n));
672
673 for i in 0..m {
674 for j in 0..n {
675 dense[[i, j]] = sparse.get(i, j);
676 }
677 }
678
679 dense
680}
681
682#[allow(dead_code)]
683fn dense_to_sparse_matrix(
684 dense: &Array2<f64>,
685 pattern: &CsrArray<f64>,
686) -> Result<CsrArray<f64>, OptimizeError> {
687 let (m, n) = dense.dim();
688 let mut rows = Vec::new();
689 let mut cols = Vec::new();
690 let mut data = Vec::new();
691
692 for i in 0..m {
693 for j in 0..n {
694 if pattern.get(i, j) != 0.0 {
695 rows.push(i);
696 cols.push(j);
697 data.push(dense[[i, j]]);
698 }
699 }
700 }
701
702 CsrArray::from_triplets(&rows, &cols, &data, (m, n), false)
703 .map_err(|_| OptimizeError::ComputationError("Failed to create sparse matrix".to_string()))
704}
705
706#[allow(dead_code)]
707fn dense_bfgs_update(
708 h_inv: &Array2<f64>,
709 s: &Array1<f64>,
710 y: &Array1<f64>,
711) -> Result<Array2<f64>, OptimizeError> {
712 let n = h_inv.nrows();
713 let s_dot_y = s.dot(y);
714
715 if s_dot_y <= 1e-10 {
716 return Ok(h_inv.clone());
717 }
718
719 let rho = 1.0 / s_dot_y;
720 let i_mat = Array2::eye(n);
721
722 let y_col = y.clone().insert_axis(Axis(1));
724 let s_row = s.clone().insert_axis(Axis(0));
725 let y_s_t = y_col.dot(&s_row);
726 let term1 = &i_mat - &(&y_s_t * rho);
727
728 let s_col = s.clone().insert_axis(Axis(1));
729 let y_row = y.clone().insert_axis(Axis(0));
730 let s_y_t = s_col.dot(&y_row);
731 let term2 = &i_mat - &(&s_y_t * rho);
732
733 let term3 = term1.dot(h_inv);
734 let result = term3.dot(&term2) + rho * s_col.dot(&s_row);
735
736 Ok(result)
737}
738
739#[allow(dead_code)]
740fn sparse_matrix_vector_product(
741 matrix: &CsrArray<f64>,
742 vector_sparse: &CsrArray<f64>,
743) -> Result<Array1<f64>, OptimizeError> {
744 let vector_dense = sparse_to_dense(vector_sparse);
746 let matrix_dense = sparse_to_dense_matrix(matrix);
747 Ok(matrix_dense.dot(&vector_dense))
748}
749
750#[allow(dead_code)]
751fn solve_sparse_linear_system(
752 matrix: &CsrArray<f64>,
753 rhs: &Array1<f64>,
754) -> Result<Array1<f64>, OptimizeError> {
755 let _matrix_dense = sparse_to_dense_matrix(matrix);
758
759 Ok(-rhs.clone()) }
763
764#[cfg(test)]
765mod tests {
766 use super::*;
767 use approx::assert_abs_diff_eq;
768
769 #[test]
770 fn test_sparsity_detection() {
771 let n = 10;
772 let options = EfficientSparseOptions::default();
773
774 let mut fun = |x: &ArrayView1<f64>| -> f64 { x[0].powi(2) + x[4].powi(2) + x[9].powi(2) };
776
777 let mut grad = |x: &ArrayView1<f64>| -> Array1<f64> {
778 let mut g = Array1::zeros(n);
779 g[0] = 2.0 * x[0];
780 g[4] = 2.0 * x[4];
781 g[9] = 2.0 * x[9];
782 g
783 };
784
785 let x = Array1::ones(n);
786 let sparsity_info =
787 detect_sparsity_patterns(&mut fun, &mut grad, &x.view(), &options).unwrap();
788
789 assert!(sparsity_info.jacobian_sparsity < 0.5); }
792
793 #[test]
794 fn test_sparse_vector_operations() {
795 let dense = Array1::from_vec(vec![1.0, 0.0, 2.0, 0.0, 0.0, 3.0]);
796 let sparse = dense_to_sparse_vector(&dense, 1e-12).unwrap();
797
798 let reconstructed = sparse_to_dense(&sparse);
799
800 for i in 0..dense.len() {
801 assert_abs_diff_eq!(dense[i], reconstructed[i], epsilon = 1e-10);
802 }
803
804 let norm_sparse = sparse_vector_norm(&sparse);
805 let norm_dense = dense.mapv(|x| x.powi(2)).sum().sqrt();
806 assert_abs_diff_eq!(norm_sparse, norm_dense, epsilon = 1e-10);
807 }
808
809 #[test]
810 fn test_efficient_sparse_optimization() {
811 let fun = |x: &ArrayView1<f64>| -> f64 { x[0].powi(2) + x[2].powi(2) + x[4].powi(2) };
813
814 let grad = |x: &ArrayView1<f64>| -> Array1<f64> {
815 let mut g = Array1::zeros(5);
816 g[0] = 2.0 * x[0];
817 g[2] = 2.0 * x[2];
818 g[4] = 2.0 * x[4];
819 g
820 };
821
822 let x0 = Array1::ones(5);
823 let options = EfficientSparseOptions::default();
824
825 let result = minimize_efficient_sparse_newton(fun, grad, x0, &options);
826
827 match result {
830 Ok(res) => {
831 assert!(res.success);
832 assert_abs_diff_eq!(res.x[0], 0.0, epsilon = 1e-3);
834 assert_abs_diff_eq!(res.x[2], 0.0, epsilon = 1e-3);
835 assert_abs_diff_eq!(res.x[4], 0.0, epsilon = 1e-3);
836 }
837 Err(e) => {
838 assert!(e.to_string().contains("Singular matrix"));
840 }
841 }
842 }
843}