use crate::error::{IntegrateError, IntegrateResult};
use scirs2_core::ndarray::{Array2, Axis};
#[derive(Debug, Clone)]
pub struct LinearDissipation {
pub matrix: Array2<f64>,
}
impl LinearDissipation {
pub fn new(matrix: Array2<f64>) -> IntegrateResult<Self> {
let n = matrix.nrows();
if matrix.ncols() != n {
return Err(IntegrateError::ValueError(
"Dissipation matrix must be square".into(),
));
}
for i in 0..n {
for j in 0..n {
if (matrix[[i, j]] - matrix[[j, i]]).abs() > 1e-10 {
return Err(IntegrateError::ValueError(format!(
"Dissipation matrix must be symmetric: R[{i},{j}]={} != R[{j},{i}]={}",
matrix[[i, j]],
matrix[[j, i]]
)));
}
}
}
Ok(Self { matrix })
}
pub fn scaled_identity(n: usize, gamma: f64) -> IntegrateResult<Self> {
if gamma < 0.0 {
return Err(IntegrateError::ValueError(
"Damping coefficient must be non-negative".into(),
));
}
let mut mat = Array2::zeros((n, n));
for i in 0..n {
mat[[i, i]] = gamma;
}
Ok(Self { matrix: mat })
}
pub fn diagonal(coeffs: &[f64]) -> IntegrateResult<Self> {
for (i, &c) in coeffs.iter().enumerate() {
if c < 0.0 {
return Err(IntegrateError::ValueError(format!(
"Damping coefficient at index {i} must be non-negative, got {c}"
)));
}
}
let n = coeffs.len();
let mut mat = Array2::zeros((n, n));
for (i, &c) in coeffs.iter().enumerate() {
mat[[i, i]] = c;
}
Ok(Self { matrix: mat })
}
pub fn into_matrix_fn(
self,
) -> impl Fn(&[f64]) -> IntegrateResult<Array2<f64>> + Send + Sync + 'static {
let m = self.matrix;
move |_x| Ok(m.clone())
}
}
#[derive(Debug, Clone)]
pub struct RayleighDissipation {
pub r_q: Array2<f64>,
pub mass_matrix: Array2<f64>,
pub n_config: usize,
}
impl RayleighDissipation {
pub fn new(r_q: Array2<f64>, mass_matrix: Array2<f64>) -> IntegrateResult<Self> {
let n = r_q.nrows();
if r_q.ncols() != n || mass_matrix.nrows() != n || mass_matrix.ncols() != n {
return Err(IntegrateError::ValueError(
"Rayleigh matrices must be square and of equal size".into(),
));
}
Ok(Self {
r_q,
mass_matrix,
n_config: n,
})
}
pub fn to_ph_matrix(&self) -> IntegrateResult<Array2<f64>> {
let n = self.n_config;
let two_n = 2 * n;
let mut r_ph = Array2::zeros((two_n, two_n));
let m_inv_r_q = solve_linear_system_left(&self.mass_matrix, &self.r_q)?;
let m_inv_r_q_m_inv = solve_linear_system_right(&m_inv_r_q, &self.mass_matrix)?;
for i in 0..n {
for j in 0..n {
r_ph[[n + i, n + j]] = m_inv_r_q_m_inv[[i, j]];
}
}
Ok(r_ph)
}
pub fn into_matrix_fn(
self,
) -> IntegrateResult<impl Fn(&[f64]) -> IntegrateResult<Array2<f64>> + Send + Sync + 'static>
{
let r_ph = self.to_ph_matrix()?;
Ok(move |_x: &[f64]| Ok(r_ph.clone()))
}
}
#[derive(Clone)]
pub struct NonlinearDissipation {
pub r_fn: std::sync::Arc<dyn Fn(&[f64]) -> IntegrateResult<Array2<f64>> + Send + Sync>,
pub n: usize,
}
impl std::fmt::Debug for NonlinearDissipation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NonlinearDissipation")
.field("n", &self.n)
.finish()
}
}
impl NonlinearDissipation {
pub fn new(
n: usize,
r_fn: impl Fn(&[f64]) -> IntegrateResult<Array2<f64>> + Send + Sync + 'static,
) -> Self {
Self {
r_fn: std::sync::Arc::new(r_fn),
n,
}
}
pub fn velocity_dependent(
n_config: usize,
base_damping: Vec<f64>,
nonlinear_exp: f64,
) -> IntegrateResult<Self> {
if base_damping.len() != n_config {
return Err(IntegrateError::ValueError(format!(
"base_damping length {} != n_config {}",
base_damping.len(),
n_config
)));
}
let two_n = 2 * n_config;
Ok(Self::new(two_n, move |x: &[f64]| {
let mut r = Array2::zeros((two_n, two_n));
for i in 0..n_config {
let p_i = if x.len() > n_config + i {
x[n_config + i]
} else {
0.0
};
let speed = p_i.abs();
let damp = if nonlinear_exp > 1.0 && speed > 1e-14 {
base_damping[i] * speed.powf(nonlinear_exp - 1.0)
} else {
base_damping[i]
};
r[[n_config + i, n_config + i]] = damp;
}
Ok(r)
}))
}
pub fn evaluate(&self, x: &[f64]) -> IntegrateResult<Array2<f64>> {
(self.r_fn)(x)
}
pub fn into_matrix_fn(
self,
) -> impl Fn(&[f64]) -> IntegrateResult<Array2<f64>> + Send + Sync + 'static {
let arc = self.r_fn;
move |x: &[f64]| arc(x)
}
}
#[derive(Debug, Clone)]
pub struct PortDissipation {
pub b_r: Array2<f64>,
pub r_ext: Array2<f64>,
}
impl PortDissipation {
pub fn new(b_r: Array2<f64>, r_ext: Array2<f64>) -> IntegrateResult<Self> {
let n_r = b_r.ncols();
if r_ext.nrows() != n_r || r_ext.ncols() != n_r {
return Err(IntegrateError::ValueError(format!(
"r_ext must be ({n_r}x{n_r}), got ({}x{})",
r_ext.nrows(),
r_ext.ncols()
)));
}
Ok(Self { b_r, r_ext })
}
pub fn to_matrix(&self) -> Array2<f64> {
let b_r_r_ext = self.b_r.dot(&self.r_ext);
b_r_r_ext.dot(&self.b_r.t())
}
pub fn into_matrix_fn(
self,
) -> impl Fn(&[f64]) -> IntegrateResult<Array2<f64>> + Send + Sync + 'static {
let m = self.to_matrix();
move |_x: &[f64]| Ok(m.clone())
}
}
#[derive(Debug, Clone)]
pub struct StructuredDissipation {
pub block_sizes: Vec<usize>,
pub blocks: Vec<Array2<f64>>,
}
impl StructuredDissipation {
pub fn new(blocks: Vec<(usize, Array2<f64>)>) -> IntegrateResult<Self> {
let block_sizes: Vec<usize> = blocks.iter().map(|(s, _)| *s).collect();
let matrices: Vec<Array2<f64>> = blocks
.into_iter()
.map(|(_, m)| m)
.collect();
for (i, (sz, m)) in block_sizes.iter().zip(matrices.iter()).enumerate() {
if m.nrows() != *sz || m.ncols() != *sz {
return Err(IntegrateError::ValueError(format!(
"Block {i}: expected ({sz}x{sz}), got ({}x{})",
m.nrows(),
m.ncols()
)));
}
}
Ok(Self {
block_sizes,
blocks: matrices,
})
}
pub fn to_full_matrix(&self) -> Array2<f64> {
let total: usize = self.block_sizes.iter().sum();
let mut r = Array2::zeros((total, total));
let mut offset = 0;
for (sz, block) in self.block_sizes.iter().zip(self.blocks.iter()) {
let end = offset + sz;
r.slice_mut(scirs2_core::ndarray::s![offset..end, offset..end])
.assign(block);
offset = end;
}
r
}
pub fn into_matrix_fn(
self,
) -> impl Fn(&[f64]) -> IntegrateResult<Array2<f64>> + Send + Sync + 'static {
let m = self.to_full_matrix();
move |_x: &[f64]| Ok(m.clone())
}
}
fn solve_linear_system_left(a: &Array2<f64>, b: &Array2<f64>) -> IntegrateResult<Array2<f64>> {
let n = a.nrows();
if a.ncols() != n || b.nrows() != n {
return Err(IntegrateError::ValueError(
"Dimension mismatch in linear solve".into(),
));
}
let m = b.ncols();
let mut aug: Vec<Vec<f64>> = (0..n)
.map(|i| {
let mut row: Vec<f64> = (0..n).map(|j| a[[i, j]]).collect();
row.extend((0..m).map(|j| b[[i, j]]));
row
})
.collect();
for col in 0..n {
let pivot_row = (col..n)
.max_by(|&r1, &r2| aug[r1][col].abs().partial_cmp(&aug[r2][col].abs()).unwrap_or(std::cmp::Ordering::Equal));
let pivot_row = pivot_row.ok_or_else(|| {
IntegrateError::LinearSolveError("Singular matrix in dissipation solve".into())
})?;
if aug[pivot_row][col].abs() < 1e-15 {
return Err(IntegrateError::LinearSolveError(
"Singular or near-singular matrix in dissipation solve".into(),
));
}
aug.swap(col, pivot_row);
let pivot = aug[col][col];
let row_len = aug[col].len();
for j in 0..row_len {
aug[col][j] /= pivot;
}
for row in 0..n {
if row != col {
let factor = aug[row][col];
for j in 0..row_len {
aug[row][j] -= factor * aug[col][j];
}
}
}
}
let mut x = Array2::zeros((n, m));
for i in 0..n {
for j in 0..m {
x[[i, j]] = aug[i][n + j];
}
}
Ok(x)
}
fn solve_linear_system_right(b: &Array2<f64>, a: &Array2<f64>) -> IntegrateResult<Array2<f64>> {
let at = a.t().to_owned();
let bt = b.t().to_owned();
let xt = solve_linear_system_left(&at, &bt)?;
Ok(xt.t().to_owned())
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::array;
#[test]
fn test_linear_dissipation_identity() {
let r = LinearDissipation::scaled_identity(3, 2.0).expect("Failed to create dissipation");
assert_eq!(r.matrix[[0, 0]], 2.0);
assert_eq!(r.matrix[[1, 1]], 2.0);
assert_eq!(r.matrix[[2, 2]], 2.0);
assert_eq!(r.matrix[[0, 1]], 0.0);
}
#[test]
fn test_linear_dissipation_diagonal() {
let r =
LinearDissipation::diagonal(&[1.0, 2.0, 3.0]).expect("Failed to create dissipation");
assert_eq!(r.matrix[[0, 0]], 1.0);
assert_eq!(r.matrix[[1, 1]], 2.0);
assert_eq!(r.matrix[[2, 2]], 3.0);
}
#[test]
fn test_linear_dissipation_negative_coeff_rejected() {
assert!(LinearDissipation::diagonal(&[1.0, -0.5]).is_err());
}
#[test]
fn test_port_dissipation() {
let b_r = array![[0.0], [1.0]];
let r_ext = array![[2.0]];
let pd = PortDissipation::new(b_r, r_ext).expect("Failed to create port dissipation");
let m = pd.to_matrix();
assert!((m[[0, 0]]).abs() < 1e-14);
assert!((m[[1, 1]] - 2.0).abs() < 1e-14);
}
#[test]
fn test_structured_dissipation() {
let b1 = array![[1.0, 0.0], [0.0, 2.0]];
let b2 = array![[3.0]];
let sd = StructuredDissipation::new(vec![(2, b1), (1, b2)])
.expect("Failed to create structured dissipation");
let m = sd.to_full_matrix();
assert_eq!(m.nrows(), 3);
assert_eq!(m.ncols(), 3);
assert!((m[[0, 0]] - 1.0).abs() < 1e-14);
assert!((m[[1, 1]] - 2.0).abs() < 1e-14);
assert!((m[[2, 2]] - 3.0).abs() < 1e-14);
assert!((m[[0, 2]]).abs() < 1e-14); }
#[test]
fn test_nonlinear_velocity_dependent() {
let nd = NonlinearDissipation::velocity_dependent(2, vec![1.0, 1.0], 2.0)
.expect("Failed to create nonlinear dissipation");
let x = vec![0.0, 0.0, 2.0, 3.0]; let r = nd.evaluate(&x).expect("Evaluation failed");
assert!((r[[2, 2]] - 2.0).abs() < 1e-10);
assert!((r[[3, 3]] - 3.0).abs() < 1e-10);
}
}