scirs2_optimize/sparse_numdiff/
jacobian.rs1use scirs2_core::ndarray::{Array1, ArrayView1};
7use scirs2_core::parallel_ops::*;
8use scirs2_sparse::{csr_array::CsrArray, sparray::SparseArray};
9
10use super::coloring::determine_column_groups;
11use super::finite_diff::{compute_step_sizes, SparseFiniteDiffOptions};
12use crate::error::OptimizeError;
13
14#[allow(dead_code)]
16fn update_sparse_value(matrix: &mut CsrArray<f64>, row: usize, col: usize, value: f64) {
17 if matrix.get(row, col) != 0.0 && matrix.set(row, col, value).is_err() {
19 }
21}
22
23#[allow(dead_code)]
38pub fn sparse_jacobian<F>(
39 func: F,
40 x: &ArrayView1<f64>,
41 f0: Option<&Array1<f64>>,
42 sparsity_pattern: Option<&CsrArray<f64>>,
43 options: Option<SparseFiniteDiffOptions>,
44) -> Result<CsrArray<f64>, OptimizeError>
45where
46 F: Fn(&ArrayView1<f64>) -> Array1<f64> + Sync,
47{
48 let options = options.unwrap_or_default();
49
50 let f0_owned: Array1<f64>;
52 let f0_ref = match f0 {
53 Some(f) => f,
54 None => {
55 f0_owned = func(x);
56 &f0_owned
57 }
58 };
59
60 let n = x.len(); let m = f0_ref.len(); let sparsity_owned: CsrArray<f64>;
65 let sparsity = match sparsity_pattern {
66 Some(p) => p,
67 None => {
68 let mut data = Vec::with_capacity(m * n);
70 let mut rows = Vec::with_capacity(m * n);
71 let mut cols = Vec::with_capacity(m * n);
72
73 for i in 0..m {
74 for j in 0..n {
75 data.push(1.0);
76 rows.push(i);
77 cols.push(j);
78 }
79 }
80
81 sparsity_owned = CsrArray::from_triplets(&rows, &cols, &data, (m, n), false)?;
82 &sparsity_owned
83 }
84 };
85
86 match options.method.as_str() {
88 "2-point" => compute_jacobian_2point(func, x, f0_ref, sparsity, &options),
89 "3-point" => compute_jacobian_3point(func, x, sparsity, &options),
90 "cs" => compute_jacobian_complex_step(func, x, sparsity, &options),
91 _ => Err(OptimizeError::ValueError(format!(
92 "Unknown method: {}. Valid options are '2-point', '3-point', and 'cs'",
93 options.method
94 ))),
95 }
96}
97
98#[allow(dead_code)]
100fn compute_jacobian_2point<F>(
101 func: F,
102 x: &ArrayView1<f64>,
103 f0: &Array1<f64>,
104 sparsity: &CsrArray<f64>,
105 options: &SparseFiniteDiffOptions,
106) -> Result<CsrArray<f64>, OptimizeError>
107where
108 F: Fn(&ArrayView1<f64>) -> Array1<f64> + Sync,
109{
110 let _n = x.len();
111 let _m = f0.len();
112
113 let transposed = sparsity.transpose()?;
116 let transposed_csr = match transposed.as_any().downcast_ref::<CsrArray<f64>>() {
117 Some(csr) => csr,
118 None => {
119 return Err(OptimizeError::ValueError(
120 "Failed to downcast to CsrArray".to_string(),
121 ))
122 }
123 };
124 let groups = determine_column_groups(transposed_csr, None, None)?;
125
126 let h = compute_step_sizes(x, options);
128
129 let (rows, cols, _) = sparsity.find();
131 let m = sparsity.shape().0;
132 let n = sparsity.shape().1;
133 let zeros = vec![0.0; rows.len()];
134 let mut jac = CsrArray::from_triplets(&rows.to_vec(), &cols.to_vec(), &zeros, (m, n), false)?;
135
136 let mut x_perturbed = x.to_owned();
138
139 let parallel = options
141 .parallel
142 .as_ref()
143 .map(|p| p.num_workers.unwrap_or(1) > 1)
144 .unwrap_or(false);
145
146 if parallel {
147 let derivatives: Vec<(usize, usize, f64)> = groups
149 .par_iter()
150 .flat_map(|group| {
151 let mut derivatives = Vec::new();
152 let mut x_local = x.to_owned();
153
154 for &col in group {
155 x_local[col] += h[col];
157
158 let f_plus = func(&x_local.view());
160
161 x_local[col] = x[col];
163
164 for (row, &f0_val) in f0.iter().enumerate() {
166 let derivative = (f_plus[row] - f0_val) / h[col];
168
169 if jac.get(row, col) != 0.0 {
171 derivatives.push((row, col, derivative));
172 }
173 }
174 }
175
176 derivatives
177 })
178 .collect();
179
180 for (row, col, derivative) in derivatives {
182 if jac.set(row, col, derivative).is_err() {
183 }
185 }
186 } else {
187 for group in &groups {
189 for &col in group {
190 x_perturbed[col] += h[col];
192
193 let f_plus = func(&x_perturbed.view());
195
196 x_perturbed[col] = x[col];
198
199 for (row, &f0_val) in f0.iter().enumerate() {
201 let derivative = (f_plus[row] - f0_val) / h[col];
203 update_sparse_value(&mut jac, row, col, derivative);
204 }
205 }
206 }
207 }
208
209 Ok(jac)
210}
211
212#[allow(dead_code)]
214fn compute_jacobian_3point<F>(
215 func: F,
216 x: &ArrayView1<f64>,
217 sparsity: &CsrArray<f64>,
218 options: &SparseFiniteDiffOptions,
219) -> Result<CsrArray<f64>, OptimizeError>
220where
221 F: Fn(&ArrayView1<f64>) -> Array1<f64> + Sync,
222{
223 let _n = x.len();
224 let _m = sparsity.shape().0;
225
226 let transposed = sparsity.transpose()?;
229 let transposed_csr = match transposed.as_any().downcast_ref::<CsrArray<f64>>() {
230 Some(csr) => csr,
231 None => {
232 return Err(OptimizeError::ValueError(
233 "Failed to downcast to CsrArray".to_string(),
234 ))
235 }
236 };
237 let groups = determine_column_groups(transposed_csr, None, None)?;
238
239 let h = compute_step_sizes(x, options);
241
242 let (rows, cols, _) = sparsity.find();
244 let m = sparsity.shape().0;
245 let n = sparsity.shape().1;
246 let zeros = vec![0.0; rows.len()];
247 let mut jac = CsrArray::from_triplets(&rows.to_vec(), &cols.to_vec(), &zeros, (m, n), false)?;
248
249 let mut x_perturbed = x.to_owned();
251
252 let parallel = options
254 .parallel
255 .as_ref()
256 .map(|p| p.num_workers.unwrap_or(1) > 1)
257 .unwrap_or(false);
258
259 if parallel {
260 let derivatives: Vec<(usize, usize, f64)> = groups
262 .par_iter()
263 .flat_map(|group| {
264 let mut derivatives = Vec::new();
265 let mut x_local = x.to_owned();
266
267 for &col in group {
268 x_local[col] += h[col];
270 let f_plus = func(&x_local.view());
271
272 x_local[col] = x[col] - h[col];
274 let f_minus = func(&x_local.view());
275
276 x_local[col] = x[col];
278
279 for row in 0..m {
281 let derivative = (f_plus[row] - f_minus[row]) / (2.0 * h[col]);
283
284 if jac.get(row, col) != 0.0 {
286 derivatives.push((row, col, derivative));
287 }
288 }
289 }
290
291 derivatives
292 })
293 .collect();
294
295 for (row, col, derivative) in derivatives {
297 if jac.set(row, col, derivative).is_err() {
298 }
300 }
301 } else {
302 for group in &groups {
303 for &col in group {
304 x_perturbed[col] += h[col];
306 let f_plus = func(&x_perturbed.view());
307
308 x_perturbed[col] = x[col] - h[col];
310 let f_minus = func(&x_perturbed.view());
311
312 x_perturbed[col] = x[col];
314
315 for row in 0..m {
317 let derivative = (f_plus[row] - f_minus[row]) / (2.0 * h[col]);
319 update_sparse_value(&mut jac, row, col, derivative);
320 }
321 }
322 }
323 }
324
325 Ok(jac)
326}
327
328#[allow(dead_code)]
337fn compute_jacobian_complex_step<F>(
338 func: F,
339 x: &ArrayView1<f64>,
340 sparsity: &CsrArray<f64>,
341 options: &SparseFiniteDiffOptions,
342) -> Result<CsrArray<f64>, OptimizeError>
343where
344 F: Fn(&ArrayView1<f64>) -> Array1<f64> + Sync,
345{
346 let n = x.len();
347 let m = sparsity.shape().0;
348
349 let h = options.abs_step.unwrap_or(1e-20);
351
352 let transposed = sparsity.transpose()?;
354 let transposed_csr = match transposed.as_any().downcast_ref::<CsrArray<f64>>() {
355 Some(csr) => csr,
356 None => {
357 return Err(OptimizeError::ValueError(
358 "Failed to downcast to CsrArray".to_string(),
359 ))
360 }
361 };
362 let groups = determine_column_groups(transposed_csr, None, None)?;
363
364 let (rows, cols, _) = sparsity.find();
366 let zeros = vec![0.0; rows.len()];
367 let mut jac = CsrArray::from_triplets(&rows.to_vec(), &cols.to_vec(), &zeros, (m, n), false)?;
368
369 let parallel = options
371 .parallel
372 .as_ref()
373 .map(|p| p.num_workers.unwrap_or(1) > 1)
374 .unwrap_or(false);
375
376 if parallel {
377 let derivatives: Vec<(usize, usize, f64)> = groups
379 .par_iter()
380 .flat_map(|group| {
381 let mut derivatives = Vec::new();
382
383 for &col in group {
384 let jac_col = compute_jacobian_column_complex_step(&func, x, col, h);
386
387 for row in 0..m {
388 if jac.get(row, col) != 0.0 {
390 derivatives.push((row, col, jac_col[row]));
391 }
392 }
393 }
394
395 derivatives
396 })
397 .collect();
398
399 for (row, col, derivative) in derivatives {
401 if jac.set(row, col, derivative).is_err() {
402 }
404 }
405 } else {
406 for group in &groups {
408 for &col in group {
409 let jac_col = compute_jacobian_column_complex_step(&func, x, col, h);
410
411 for row in 0..m {
412 if jac.get(row, col) != 0.0 {
413 update_sparse_value(&mut jac, row, col, jac_col[row]);
414 }
415 }
416 }
417 }
418 }
419
420 Ok(jac)
421}
422
423#[allow(dead_code)]
425fn compute_jacobian_column_complex_step<F>(
426 func: &F,
427 x: &ArrayView1<f64>,
428 col: usize,
429 h: f64,
430) -> Array1<f64>
431where
432 F: Fn(&ArrayView1<f64>) -> Array1<f64>,
433{
434 let mut x_plus = x.to_owned();
439 let mut x_minus = x.to_owned();
440
441 x_plus[col] += h;
442 x_minus[col] -= h;
443
444 let f_plus = func(&x_plus.view());
445 let f_minus = func(&x_minus.view());
446
447 let mut x_plus2 = x.to_owned();
450 let mut x_minus2 = x.to_owned();
451
452 x_plus2[col] += 2.0 * h;
453 x_minus2[col] -= 2.0 * h;
454
455 let f_plus2 = func(&x_plus2.view());
456 let f_minus2 = func(&x_minus2.view());
457
458 let mut result = Array1::zeros(f_plus.len());
460 for i in 0..result.len() {
461 result[i] = (-f_plus2[i] + 8.0 * f_plus[i] - 8.0 * f_minus[i] + f_minus2[i]) / (12.0 * h);
462 }
463
464 result
465}