use num_complex::{Complex32, Complex64};
use crate::{
DType, DotGeneralConfig, Error, ErrorKind, Result, ShapeMismatch, Tensor, TensorScalar,
TypedTensor, ValidationError,
};
#[derive(Debug, thiserror::Error)]
pub enum DiagonalError {
#[error("singular or non-finite diagonal at position [{index},{index}]")]
SingularOrNonFinite { index: usize },
#[error("singular or non-finite diagonal at batch {batch}, position [{index},{index}]")]
BatchedSingularOrNonFinite { batch: usize, index: usize },
#[error("triangular solve does not support dtype {dtype:?}")]
UnsupportedDType { dtype: DType },
}
pub fn promote_dtype(lhs: DType, rhs: DType) -> DType {
use DType::*;
match (lhs, rhs) {
(Bool, Bool) => Bool,
(Bool, other) | (other, Bool) => other,
(I32, I32) => I32,
(I32, I64) | (I64, I32) | (I64, I64) => I64,
(I32 | I64, F32 | F64) | (F32 | F64, I32 | I64) => F64,
(I32 | I64, C32 | C64) | (C32 | C64, I32 | I64) => C64,
(F32, F32) => F32,
(F32, F64) | (F64, F32) | (F64, F64) => F64,
(F32, C32) | (C32, F32) | (C32, C32) => C32,
(F32, C64) | (C64, F32) => C64,
(F64, C32 | C64) | (C32 | C64, F64) => C64,
(C32, C64) | (C64, C32) | (C64, C64) => C64,
}
}
pub fn can_convert_dtype(from: DType, to: DType) -> bool {
promote_dtype(from, to) == to
}
pub fn validate_convert_dtype(op: &'static str, from: DType, to: DType) -> Result<()> {
if can_convert_dtype(from, to) {
return Ok(());
}
Err(Error::unsupported_dtype_conversion(
op,
from,
to,
"checked convert only accepts conversions allowed by dtype promotion; use explicit cast for lossy dtype projection",
))
}
pub fn checked_shape_product(
op: &'static str,
role: &'static str,
shape: &[usize],
) -> Result<usize> {
if shape.contains(&0) {
return Ok(0);
}
shape
.iter()
.try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
.ok_or_else(|| {
Error::invalid_argument(op, role, format!("product overflows for shape {shape:?}"))
})
}
pub fn validate_permutation_axes(op: &'static str, rank: usize, perm: &[usize]) -> Result<()> {
if perm.len() != rank {
return Err(Error::validation(
op,
ValidationError::RankMismatch {
expected: rank,
actual: perm.len(),
},
));
}
let mut seen = vec![false; rank];
for &axis in perm {
if axis >= rank {
return Err(Error::validation(
op,
ValidationError::AxisOutOfBounds { axis, rank },
));
}
if seen[axis] {
return Err(Error::validation(
op,
ValidationError::DuplicateAxis {
axis,
role: "permutation",
},
));
}
seen[axis] = true;
}
Ok(())
}
pub fn validate_unique_axes(
op: &'static str,
role: &'static str,
rank: usize,
axes: &[usize],
) -> Result<()> {
let mut seen = vec![false; rank];
for &axis in axes {
if axis >= rank {
return Err(Error::validation(
op,
ValidationError::AxisOutOfBounds { axis, rank },
));
}
if seen[axis] {
return Err(Error::validation(
op,
ValidationError::DuplicateAxis { axis, role },
));
}
seen[axis] = true;
}
Ok(())
}
pub fn matmul_config_for_shapes(
op: &'static str,
lhs_shape: &[usize],
rhs_shape: &[usize],
) -> Result<DotGeneralConfig> {
if lhs_shape.len() != 2 {
return Err(Error::validation(
op,
ValidationError::RankMismatch {
expected: 2,
actual: lhs_shape.len(),
},
));
}
if rhs_shape.len() != 2 {
return Err(Error::validation(
op,
ValidationError::RankMismatch {
expected: 2,
actual: rhs_shape.len(),
},
));
}
if lhs_shape[1] != rhs_shape[0] {
return Err(Error::validation(
op,
ShapeMismatch::IncompatibleShapes {
lhs: lhs_shape.to_vec().into(),
rhs: rhs_shape.to_vec().into(),
}
.into(),
));
}
Ok(DotGeneralConfig {
lhs_contracting_dims: vec![1],
rhs_contracting_dims: vec![0],
lhs_batch_dims: vec![],
rhs_batch_dims: vec![],
})
}
pub trait DiagSingularity {
fn is_singular_or_nonfinite(&self) -> bool;
}
macro_rules! impl_diag_singularity_float {
($($t:ty),* $(,)?) => {
$(
impl DiagSingularity for $t {
fn is_singular_or_nonfinite(&self) -> bool {
!self.is_finite() || *self == 0.0
}
}
)*
};
}
impl_diag_singularity_float!(f64, f32);
macro_rules! impl_diag_singularity_complex {
($($t:ty),* $(,)?) => {
$(
impl DiagSingularity for $t {
fn is_singular_or_nonfinite(&self) -> bool {
!self.re.is_finite()
|| !self.im.is_finite()
|| (self.re == 0.0 && self.im == 0.0)
}
}
)*
};
}
impl_diag_singularity_complex!(Complex64, Complex32);
pub fn check_singular_diagonal<T: DiagSingularity + TensorScalar + std::fmt::Debug>(
t: &TypedTensor<T>,
) -> Result<()> {
if t.shape().len() < 2 {
return Err(Error::validation(
"solve",
ValidationError::RankMismatch {
expected: 2,
actual: t.shape().len(),
},
));
}
let rows = t.shape()[0];
let cols = t.shape()[1];
let n = rows.min(cols);
let batch_total = checked_shape_product("solve", "batch shape", &t.shape()[2..])?;
let slice_size = checked_shape_product("solve", "matrix shape", &t.shape()[..2])?;
let data = t.host_data()?;
for batch_idx in 0..batch_total {
let batch = &data[batch_idx * slice_size..(batch_idx + 1) * slice_size];
for i in 0..n {
let diag = batch[i + i * rows];
if diag.is_singular_or_nonfinite() {
return Err(Error::extension(
"solve",
"tensor-validation",
ErrorKind::NumericalFailure,
if batch_total > 1 {
DiagonalError::BatchedSingularOrNonFinite {
batch: batch_idx,
index: i,
}
} else {
DiagonalError::SingularOrNonFinite { index: i }
},
));
}
}
}
Ok(())
}
pub fn validate_nonsingular_u(u: &Tensor) -> Result<()> {
match u {
Tensor::F64(t) => check_singular_diagonal(t),
Tensor::F32(t) => check_singular_diagonal(t),
Tensor::C64(t) => check_singular_diagonal(t),
Tensor::C32(t) => check_singular_diagonal(t),
Tensor::I32(_) | Tensor::I64(_) | Tensor::Bool(_) => Err(Error::extension(
"solve",
"tensor-validation",
ErrorKind::Unsupported,
DiagonalError::UnsupportedDType { dtype: u.dtype() },
)),
}
}
#[cfg(test)]
mod tests;