use scirs2_core::ndarray::{Array1, ArrayView1};
use scirs2_core::parallel_ops::*;
use scirs2_sparse::{csr_array::CsrArray, sparray::SparseArray};
use super::coloring::determine_column_groups;
use super::finite_diff::{compute_step_sizes, SparseFiniteDiffOptions};
use crate::error::OptimizeError;
#[allow(dead_code)]
fn update_sparse_value(matrix: &mut CsrArray<f64>, row: usize, col: usize, value: f64) {
if matrix.get(row, col) != 0.0 && matrix.set(row, col, value).is_err() {
}
}
#[allow(dead_code)]
pub fn sparse_jacobian<F>(
func: F,
x: &ArrayView1<f64>,
f0: Option<&Array1<f64>>,
sparsity_pattern: Option<&CsrArray<f64>>,
options: Option<SparseFiniteDiffOptions>,
) -> Result<CsrArray<f64>, OptimizeError>
where
F: Fn(&ArrayView1<f64>) -> Array1<f64> + Sync,
{
let options = options.unwrap_or_default();
let f0_owned: Array1<f64>;
let f0_ref = match f0 {
Some(f) => f,
None => {
f0_owned = func(x);
&f0_owned
}
};
let n = x.len(); let m = f0_ref.len();
let sparsity_owned: CsrArray<f64>;
let sparsity = match sparsity_pattern {
Some(p) => p,
None => {
let mut data = Vec::with_capacity(m * n);
let mut rows = Vec::with_capacity(m * n);
let mut cols = Vec::with_capacity(m * n);
for i in 0..m {
for j in 0..n {
data.push(1.0);
rows.push(i);
cols.push(j);
}
}
sparsity_owned = CsrArray::from_triplets(&rows, &cols, &data, (m, n), false)?;
&sparsity_owned
}
};
match options.method.as_str() {
"2-point" => compute_jacobian_2point(func, x, f0_ref, sparsity, &options),
"3-point" => compute_jacobian_3point(func, x, sparsity, &options),
"cs" => compute_jacobian_complex_step(func, x, sparsity, &options),
_ => Err(OptimizeError::ValueError(format!(
"Unknown method: {}. Valid options are '2-point', '3-point', and 'cs'",
options.method
))),
}
}
#[allow(dead_code)]
fn compute_jacobian_2point<F>(
func: F,
x: &ArrayView1<f64>,
f0: &Array1<f64>,
sparsity: &CsrArray<f64>,
options: &SparseFiniteDiffOptions,
) -> Result<CsrArray<f64>, OptimizeError>
where
F: Fn(&ArrayView1<f64>) -> Array1<f64> + Sync,
{
let _n = x.len();
let _m = f0.len();
let transposed = sparsity.transpose()?;
let transposed_csr = match transposed.as_any().downcast_ref::<CsrArray<f64>>() {
Some(csr) => csr,
None => {
return Err(OptimizeError::ValueError(
"Failed to downcast to CsrArray".to_string(),
))
}
};
let groups = determine_column_groups(transposed_csr, None, None)?;
let h = compute_step_sizes(x, options);
let (rows, cols, _) = sparsity.find();
let m = sparsity.shape().0;
let n = sparsity.shape().1;
let zeros = vec![0.0; rows.len()];
let mut jac = CsrArray::from_triplets(&rows.to_vec(), &cols.to_vec(), &zeros, (m, n), false)?;
let mut x_perturbed = x.to_owned();
let parallel = options
.parallel
.as_ref()
.map(|p| p.num_workers.unwrap_or(1) > 1)
.unwrap_or(false);
if parallel {
let derivatives: Vec<(usize, usize, f64)> = groups
.par_iter()
.flat_map(|group| {
let mut derivatives = Vec::new();
let mut x_local = x.to_owned();
for &col in group {
x_local[col] += h[col];
let f_plus = func(&x_local.view());
x_local[col] = x[col];
for (row, &f0_val) in f0.iter().enumerate() {
let derivative = (f_plus[row] - f0_val) / h[col];
if jac.get(row, col) != 0.0 {
derivatives.push((row, col, derivative));
}
}
}
derivatives
})
.collect();
for (row, col, derivative) in derivatives {
if jac.set(row, col, derivative).is_err() {
}
}
} else {
for group in &groups {
for &col in group {
x_perturbed[col] += h[col];
let f_plus = func(&x_perturbed.view());
x_perturbed[col] = x[col];
for (row, &f0_val) in f0.iter().enumerate() {
let derivative = (f_plus[row] - f0_val) / h[col];
update_sparse_value(&mut jac, row, col, derivative);
}
}
}
}
Ok(jac)
}
#[allow(dead_code)]
fn compute_jacobian_3point<F>(
func: F,
x: &ArrayView1<f64>,
sparsity: &CsrArray<f64>,
options: &SparseFiniteDiffOptions,
) -> Result<CsrArray<f64>, OptimizeError>
where
F: Fn(&ArrayView1<f64>) -> Array1<f64> + Sync,
{
let _n = x.len();
let _m = sparsity.shape().0;
let transposed = sparsity.transpose()?;
let transposed_csr = match transposed.as_any().downcast_ref::<CsrArray<f64>>() {
Some(csr) => csr,
None => {
return Err(OptimizeError::ValueError(
"Failed to downcast to CsrArray".to_string(),
))
}
};
let groups = determine_column_groups(transposed_csr, None, None)?;
let h = compute_step_sizes(x, options);
let (rows, cols, _) = sparsity.find();
let m = sparsity.shape().0;
let n = sparsity.shape().1;
let zeros = vec![0.0; rows.len()];
let mut jac = CsrArray::from_triplets(&rows.to_vec(), &cols.to_vec(), &zeros, (m, n), false)?;
let mut x_perturbed = x.to_owned();
let parallel = options
.parallel
.as_ref()
.map(|p| p.num_workers.unwrap_or(1) > 1)
.unwrap_or(false);
if parallel {
let derivatives: Vec<(usize, usize, f64)> = groups
.par_iter()
.flat_map(|group| {
let mut derivatives = Vec::new();
let mut x_local = x.to_owned();
for &col in group {
x_local[col] += h[col];
let f_plus = func(&x_local.view());
x_local[col] = x[col] - h[col];
let f_minus = func(&x_local.view());
x_local[col] = x[col];
for row in 0..m {
let derivative = (f_plus[row] - f_minus[row]) / (2.0 * h[col]);
if jac.get(row, col) != 0.0 {
derivatives.push((row, col, derivative));
}
}
}
derivatives
})
.collect();
for (row, col, derivative) in derivatives {
if jac.set(row, col, derivative).is_err() {
}
}
} else {
for group in &groups {
for &col in group {
x_perturbed[col] += h[col];
let f_plus = func(&x_perturbed.view());
x_perturbed[col] = x[col] - h[col];
let f_minus = func(&x_perturbed.view());
x_perturbed[col] = x[col];
for row in 0..m {
let derivative = (f_plus[row] - f_minus[row]) / (2.0 * h[col]);
update_sparse_value(&mut jac, row, col, derivative);
}
}
}
}
Ok(jac)
}
#[allow(dead_code)]
fn compute_jacobian_complex_step<F>(
func: F,
x: &ArrayView1<f64>,
sparsity: &CsrArray<f64>,
options: &SparseFiniteDiffOptions,
) -> Result<CsrArray<f64>, OptimizeError>
where
F: Fn(&ArrayView1<f64>) -> Array1<f64> + Sync,
{
let n = x.len();
let m = sparsity.shape().0;
let h = options.abs_step.unwrap_or(1e-20);
let transposed = sparsity.transpose()?;
let transposed_csr = match transposed.as_any().downcast_ref::<CsrArray<f64>>() {
Some(csr) => csr,
None => {
return Err(OptimizeError::ValueError(
"Failed to downcast to CsrArray".to_string(),
))
}
};
let groups = determine_column_groups(transposed_csr, None, None)?;
let (rows, cols, _) = sparsity.find();
let zeros = vec![0.0; rows.len()];
let mut jac = CsrArray::from_triplets(&rows.to_vec(), &cols.to_vec(), &zeros, (m, n), false)?;
let parallel = options
.parallel
.as_ref()
.map(|p| p.num_workers.unwrap_or(1) > 1)
.unwrap_or(false);
if parallel {
let derivatives: Vec<(usize, usize, f64)> = groups
.par_iter()
.flat_map(|group| {
let mut derivatives = Vec::new();
for &col in group {
let jac_col = compute_jacobian_column_complex_step(&func, x, col, h);
for row in 0..m {
if jac.get(row, col) != 0.0 {
derivatives.push((row, col, jac_col[row]));
}
}
}
derivatives
})
.collect();
for (row, col, derivative) in derivatives {
if jac.set(row, col, derivative).is_err() {
}
}
} else {
for group in &groups {
for &col in group {
let jac_col = compute_jacobian_column_complex_step(&func, x, col, h);
for row in 0..m {
if jac.get(row, col) != 0.0 {
update_sparse_value(&mut jac, row, col, jac_col[row]);
}
}
}
}
}
Ok(jac)
}
#[allow(dead_code)]
fn compute_jacobian_column_complex_step<F>(
func: &F,
x: &ArrayView1<f64>,
col: usize,
h: f64,
) -> Array1<f64>
where
F: Fn(&ArrayView1<f64>) -> Array1<f64>,
{
let mut x_plus = x.to_owned();
let mut x_minus = x.to_owned();
x_plus[col] += h;
x_minus[col] -= h;
let f_plus = func(&x_plus.view());
let f_minus = func(&x_minus.view());
let mut x_plus2 = x.to_owned();
let mut x_minus2 = x.to_owned();
x_plus2[col] += 2.0 * h;
x_minus2[col] -= 2.0 * h;
let f_plus2 = func(&x_plus2.view());
let f_minus2 = func(&x_minus2.view());
let mut result = Array1::zeros(f_plus.len());
for i in 0..result.len() {
result[i] = (-f_plus2[i] + 8.0 * f_plus[i] - 8.0 * f_minus[i] + f_minus2[i]) / (12.0 * h);
}
result
}