use scirs2_core::ndarray::{Array1, Array2, ArrayView2};
use scirs2_core::numeric::{Float, One, Zero};
use std::fmt::Debug;
use crate::basic::{det, inv};
use crate::decomposition::svd;
use crate::error::{LinalgError, LinalgResult};
use crate::norm::matrix_norm;
#[allow(dead_code)]
pub fn det_derivative<F>(x: &ArrayView2<F>) -> LinalgResult<Array2<F>>
where
F: Float
+ Zero
+ One
+ Copy
+ Debug
+ scirs2_core::ndarray::ScalarOperand
+ scirs2_core::numeric::NumAssign
+ std::iter::Sum
+ Send
+ Sync
+ 'static,
{
if x.nrows() != x.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix must be square, got shape {:?}",
x.shape()
)));
}
let det_x = det(x, None)?;
if det_x.abs() < F::epsilon() {
return Err(LinalgError::SingularMatrixError(
"Cannot compute determinant derivative for singular matrix".to_string(),
));
}
let x_inv = inv(x, None)?;
let x_inv_t = x_inv.t().to_owned();
Ok(x_inv_t * det_x)
}
#[allow(dead_code)]
pub fn trace_derivative<F>(
a: Option<&ArrayView2<F>>,
shape: (usize, usize),
) -> LinalgResult<Array2<F>>
where
F: Float + Zero + One + Copy + Debug,
{
match a {
None => {
let mut result = Array2::zeros(shape);
let n = shape.0.min(shape.1);
for i in 0..n {
result[[i, i]] = F::one();
}
Ok(result)
}
Some(a_mat) => {
Ok(a_mat.t().to_owned())
}
}
}
#[allow(dead_code)]
pub fn inv_directional_derivative<F>(
x: &ArrayView2<F>,
direction: &ArrayView2<F>,
) -> LinalgResult<Array2<F>>
where
F: Float
+ Zero
+ One
+ Copy
+ Debug
+ scirs2_core::ndarray::ScalarOperand
+ scirs2_core::numeric::NumAssign
+ std::iter::Sum
+ Send
+ Sync
+ 'static,
{
if x.nrows() != x.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix must be square, got shape {:?}",
x.shape()
)));
}
if x.shape() != direction.shape() {
return Err(LinalgError::ShapeError(format!(
"Direction matrix must have same shape as input matrix: {:?} vs {:?}",
direction.shape(),
x.shape()
)));
}
let x_inv = inv(x, None)?;
let temp = x_inv.dot(direction);
let result = -x_inv.dot(&temp);
Ok(result)
}
#[allow(dead_code)]
pub fn exp_directional_derivative<F>(
x: &ArrayView2<F>,
direction: &ArrayView2<F>,
num_terms: usize,
) -> LinalgResult<Array2<F>>
where
F: Float
+ Zero
+ One
+ Copy
+ Debug
+ scirs2_core::ndarray::ScalarOperand
+ scirs2_core::numeric::NumAssign
+ std::iter::Sum
+ Send
+ Sync
+ 'static,
{
if x.nrows() != x.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix must be square, got shape {:?}",
x.shape()
)));
}
if x.shape() != direction.shape() {
return Err(LinalgError::ShapeError(format!(
"Direction matrix must have same shape as input matrix: {:?} vs {:?}",
direction.shape(),
x.shape()
)));
}
let n = x.nrows();
let mut result = Array2::zeros((n, n));
let mut x_power = Array2::eye(n); let mut factorial = F::one();
for k in 0..num_terms {
if k > 0 {
factorial *= F::from(k).expect("Operation failed");
x_power = x_power.dot(x);
}
let mut inner_sum = Array2::zeros((n, n));
let mut x_j = Array2::eye(n);
for j in 0..=k {
if j > 0 {
x_j = x_j.dot(x);
}
let mut x_k_minus_j = Array2::eye(n);
for _ in 0..(k - j) {
x_k_minus_j = x_k_minus_j.dot(x);
}
let temp = x_j.dot(direction);
inner_sum = inner_sum + temp.dot(&x_k_minus_j);
}
result = result + inner_sum * (F::one() / factorial);
}
Ok(result)
}
#[allow(dead_code)]
pub fn eigenvalue_derivatives<F>(
x: &ArrayView2<F>,
direction: &ArrayView2<F>,
) -> LinalgResult<Array1<F>>
where
F: Float
+ Zero
+ One
+ Copy
+ Debug
+ scirs2_core::ndarray::ScalarOperand
+ scirs2_core::numeric::NumAssign
+ std::iter::Sum
+ Send
+ Sync
+ 'static,
{
if x.nrows() != x.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix must be square, got shape {:?}",
x.shape()
)));
}
if x.shape() != direction.shape() {
return Err(LinalgError::ShapeError(format!(
"Direction matrix must have same shape as input matrix: {:?} vs {:?}",
direction.shape(),
x.shape()
)));
}
let n = x.nrows();
for i in 0..n {
for j in 0..n {
if (x[[i, j]] - x[[j, i]]).abs()
> F::epsilon() * F::from(100.0).expect("Operation failed")
{
return Err(LinalgError::InvalidInputError(
"Matrix must be symmetric for eigenvalue derivatives".to_string(),
));
}
}
}
let eps = F::epsilon().sqrt();
let eig_x = simple_symmetric_eigenvalues(x)?;
let x_pert = x + &(direction * eps);
let eig_x_pert = simple_symmetric_eigenvalues(&x_pert.view())?;
let mut derivatives = Array1::zeros(n);
for i in 0..n {
derivatives[i] = (eig_x_pert[i] - eig_x[i]) / eps;
}
Ok(derivatives)
}
#[allow(dead_code)]
pub fn norm_derivative<F>(x: &ArrayView2<F>, normtype: &str) -> LinalgResult<Array2<F>>
where
F: Float
+ Zero
+ One
+ Copy
+ Debug
+ scirs2_core::ndarray::ScalarOperand
+ scirs2_core::numeric::NumAssign
+ std::iter::Sum
+ Send
+ Sync
+ 'static,
{
match normtype {
"fro" | "frobenius" => {
let norm_val = matrix_norm(x, normtype, None)?;
if norm_val < F::epsilon() {
return Err(LinalgError::InvalidInputError(
"Cannot compute norm derivative for zero matrix".to_string(),
));
}
Ok(x.to_owned() / norm_val)
}
"2" | "spectral" => {
let (u, s, vt) = svd(x, false, None)?;
if s.is_empty() || s[0] < F::epsilon() {
return Err(LinalgError::InvalidInputError(
"Cannot compute spectral norm derivative for zero matrix".to_string(),
));
}
let u1 = u.column(0);
let v1 = vt.row(0);
let mut result = Array2::zeros(x.dim());
for i in 0..u1.len() {
for j in 0..v1.len() {
result[[i, j]] = u1[i] * v1[j];
}
}
Ok(result)
}
_ => Err(LinalgError::InvalidInputError(format!(
"Unsupported norm type: {normtype}. Supported: 'fro', 'frobenius', '2', 'spectral'"
))),
}
}
#[allow(dead_code)]
pub fn matmul_derivative<F>(
a: &ArrayView2<F>,
b: &ArrayView2<F>,
direction_a: Option<&ArrayView2<F>>,
direction_b: Option<&ArrayView2<F>>,
) -> LinalgResult<Array2<F>>
where
F: Float
+ Zero
+ One
+ Copy
+ Debug
+ scirs2_core::ndarray::ScalarOperand
+ scirs2_core::numeric::NumAssign
+ std::iter::Sum
+ Send
+ Sync
+ 'static,
{
if a.ncols() != b.nrows() {
return Err(LinalgError::ShapeError(format!(
"Matrix dimensions incompatible for multiplication: {:?} x {:?}",
a.shape(),
b.shape()
)));
}
let mut result = Array2::zeros((a.nrows(), b.ncols()));
if let Some(va) = direction_a {
if va.shape() != a.shape() {
return Err(LinalgError::ShapeError(format!(
"Direction matrix for A must have same shape: {:?} vs {:?}",
va.shape(),
a.shape()
)));
}
result = result + va.dot(b);
}
if let Some(vb) = direction_b {
if vb.shape() != b.shape() {
return Err(LinalgError::ShapeError(format!(
"Direction matrix for B must have same shape: {:?} vs {:?}",
vb.shape(),
b.shape()
)));
}
result = result + a.dot(vb);
}
Ok(result)
}
#[allow(dead_code)]
fn simple_symmetric_eigenvalues<F>(x: &ArrayView2<F>) -> LinalgResult<Array1<F>>
where
F: Float
+ Zero
+ One
+ Copy
+ Debug
+ scirs2_core::ndarray::ScalarOperand
+ scirs2_core::numeric::NumAssign
+ std::iter::Sum
+ Send
+ Sync
+ 'static,
{
let n = x.nrows();
let mut eigenvals = Array1::zeros(n);
for i in 0..n {
eigenvals[i] = x[[i, i]];
}
let mut pairs: Vec<(F, usize)> = eigenvals
.iter()
.enumerate()
.map(|(i, &val)| (val, i))
.collect();
pairs.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
for (i, (val_, _)) in pairs.iter().enumerate() {
eigenvals[i] = *val_;
}
Ok(eigenvals)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_det_derivative() {
let x = array![[2.0, 1.0], [1.0, 2.0]];
let d_det = det_derivative(&x.view()).expect("Operation failed");
assert_abs_diff_eq!(d_det[[0, 0]], 2.0, epsilon = 1e-10);
assert_abs_diff_eq!(d_det[[0, 1]], -1.0, epsilon = 1e-10);
assert_abs_diff_eq!(d_det[[1, 0]], -1.0, epsilon = 1e-10);
assert_abs_diff_eq!(d_det[[1, 1]], 2.0, epsilon = 1e-10);
}
#[test]
fn test_trace_derivative() {
let d_trace = trace_derivative::<f64>(None, (3, 3)).expect("Operation failed");
for i in 0..3 {
for j in 0..3 {
if i == j {
assert_abs_diff_eq!(d_trace[[i, j]], 1.0, epsilon = 1e-10);
} else {
assert_abs_diff_eq!(d_trace[[i, j]], 0.0, epsilon = 1e-10);
}
}
}
let a = array![[1.0, 2.0], [3.0, 4.0]];
let d_trace_a = trace_derivative(Some(&a.view()), (2, 2)).expect("Operation failed");
let expected = a.t().to_owned();
for i in 0..2 {
for j in 0..2 {
assert_abs_diff_eq!(d_trace_a[[i, j]], expected[[i, j]], epsilon = 1e-10);
}
}
}
#[test]
fn test_norm_derivative() {
let x = array![[3.0, 4.0], [0.0, 0.0]];
let d_norm = norm_derivative(&x.view(), "fro").expect("Operation failed");
assert_abs_diff_eq!(d_norm[[0, 0]], 3.0 / 5.0, epsilon = 1e-10);
assert_abs_diff_eq!(d_norm[[0, 1]], 4.0 / 5.0, epsilon = 1e-10);
assert_abs_diff_eq!(d_norm[[1, 0]], 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(d_norm[[1, 1]], 0.0, epsilon = 1e-10);
}
#[test]
fn test_matmul_derivative() {
let a = array![[1.0, 2.0], [3.0, 4.0]];
let b = array![[5.0, 6.0], [7.0, 8.0]];
let va = array![[1.0, 0.0], [0.0, 0.0]];
let d_ab = matmul_derivative(&a.view(), &b.view(), Some(&va.view()), None)
.expect("Operation failed");
assert_abs_diff_eq!(d_ab[[0, 0]], 5.0, epsilon = 1e-10);
assert_abs_diff_eq!(d_ab[[0, 1]], 6.0, epsilon = 1e-10);
assert_abs_diff_eq!(d_ab[[1, 0]], 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(d_ab[[1, 1]], 0.0, epsilon = 1e-10);
}
}
pub mod differential_operators {
use super::*;
use scirs2_core::ndarray::{Array3, Axis};
pub fn matrix_divergence<F>(field: &Array3<F>, spacing: F) -> LinalgResult<Array1<F>>
where
F: Float
+ Zero
+ One
+ Copy
+ Debug
+ scirs2_core::ndarray::ScalarOperand
+ scirs2_core::numeric::NumAssign,
{
let n_points = field.len_of(Axis(2));
let mut divergence = Array1::zeros(n_points);
for k in 1..n_points - 1 {
let mut div_k = F::zero();
div_k += (field[[0, 0, k + 1]] - field[[0, 0, k - 1]])
/ (F::from(2.0).expect("Operation failed") * spacing);
div_k += (field[[0, 1, k + 1]] - field[[0, 1, k - 1]])
/ (F::from(2.0).expect("Operation failed") * spacing);
div_k += (field[[1, 0, k + 1]] - field[[1, 0, k - 1]])
/ (F::from(2.0).expect("Operation failed") * spacing);
div_k += (field[[1, 1, k + 1]] - field[[1, 1, k - 1]])
/ (F::from(2.0).expect("Operation failed") * spacing);
divergence[k] = div_k;
}
if n_points > 2 {
divergence[0] = divergence[1];
divergence[n_points - 1] = divergence[n_points - 2];
}
Ok(divergence)
}
pub fn matrix_curl_2d<F>(field: &Array3<F>, spacing: F) -> LinalgResult<Array1<F>>
where
F: Float
+ Zero
+ One
+ Copy
+ Debug
+ scirs2_core::ndarray::ScalarOperand
+ scirs2_core::numeric::NumAssign,
{
let n_points = field.len_of(Axis(2));
let mut curl = Array1::zeros(n_points);
for k in 1..n_points - 1 {
let mut curl_k = F::zero();
curl_k += (field[[0, 1, k + 1]] - field[[0, 1, k - 1]])
/ (F::from(2.0).expect("Operation failed") * spacing);
curl_k -= (field[[0, 0, k + 1]] - field[[0, 0, k - 1]])
/ (F::from(2.0).expect("Operation failed") * spacing);
curl_k += (field[[1, 1, k + 1]] - field[[1, 1, k - 1]])
/ (F::from(2.0).expect("Operation failed") * spacing);
curl_k -= (field[[1, 0, k + 1]] - field[[1, 0, k - 1]])
/ (F::from(2.0).expect("Operation failed") * spacing);
curl[k] = curl_k;
}
if n_points > 2 {
curl[0] = curl[1];
curl[n_points - 1] = curl[n_points - 2];
}
Ok(curl)
}
pub fn matrix_laplacian<F>(field: &Array3<F>, spacing: F) -> LinalgResult<Array3<F>>
where
F: Float
+ Zero
+ One
+ Copy
+ Debug
+ scirs2_core::ndarray::ScalarOperand
+ scirs2_core::numeric::NumAssign,
{
let (n_rows, n_cols, n_points) = field.dim();
let mut laplacian = Array3::zeros((n_rows, n_cols, n_points));
let h_sq = spacing * spacing;
for i in 0..n_rows {
for j in 0..n_cols {
for k in 1..n_points - 1 {
laplacian[[i, j, k]] = (field[[i, j, k - 1]]
- F::from(2.0).expect("Operation failed") * field[[i, j, k]]
+ field[[i, j, k + 1]])
/ h_sq;
}
}
}
for i in 0..n_rows {
for j in 0..n_cols {
laplacian[[i, j, 0]] = F::zero();
if n_points > 1 {
laplacian[[i, j, n_points - 1]] = F::zero();
}
}
}
Ok(laplacian)
}
pub fn matrix_gradient<F>(field: &Array3<F>, spacing: F) -> LinalgResult<Array3<F>>
where
F: Float
+ Zero
+ One
+ Copy
+ Debug
+ scirs2_core::ndarray::ScalarOperand
+ scirs2_core::numeric::NumAssign,
{
let (n_rows, n_cols, n_points) = field.dim();
let mut gradient = Array3::zeros((n_rows, n_cols, n_points));
for i in 0..n_rows {
for j in 0..n_cols {
for k in 1..n_points - 1 {
gradient[[i, j, k]] = (field[[i, j, k + 1]] - field[[i, j, k - 1]])
/ (F::from(2.0).expect("Operation failed") * spacing);
}
if n_points > 1 {
gradient[[i, j, 0]] = (field[[i, j, 1]] - field[[i, j, 0]]) / spacing;
gradient[[i, j, n_points - 1]] =
(field[[i, j, n_points - 1]] - field[[i, j, n_points - 2]]) / spacing;
}
}
}
Ok(gradient)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn testmatrix_divergence() {
let mut field = Array3::zeros((2, 2, 5));
for k in 0..5 {
let x = k as f64;
field[[0, 0, k]] = x; field[[0, 1, k]] = x; field[[1, 0, k]] = 0.0; field[[1, 1, k]] = x; }
let div = matrix_divergence(&field, 1.0).expect("Operation failed");
assert!(div.len() == 5);
assert_abs_diff_eq!(div[2], 3.0, epsilon = 1e-10);
}
#[test]
fn testmatrix_laplacian() {
let mut field = Array3::zeros((2, 2, 5));
for k in 0..5 {
let x = k as f64;
field[[0, 0, k]] = x * x; field[[1, 1, k]] = x * x; }
let laplacian = matrix_laplacian(&field, 1.0).expect("Operation failed");
assert_abs_diff_eq!(laplacian[[0, 0, 2]], 2.0, epsilon = 1e-10);
assert_abs_diff_eq!(laplacian[[1, 1, 2]], 2.0, epsilon = 1e-10);
}
#[test]
fn testmatrix_gradient() {
let mut field = Array3::zeros((2, 2, 5));
for k in 0..5 {
let x = k as f64;
field[[0, 0, k]] = 2.0 * x; field[[1, 1, k]] = 3.0 * x; }
let gradient = matrix_gradient(&field, 1.0).expect("Operation failed");
assert_abs_diff_eq!(gradient[[0, 0, 2]], 2.0, epsilon = 1e-10);
assert_abs_diff_eq!(gradient[[1, 1, 2]], 3.0, epsilon = 1e-10);
}
}
}