use alloc::{vec, vec::Vec};
use core::fmt;
use slatec_core::to_fortran_integer;
use super::JacobianMut;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum JacobianCheckError {
EmptySystem,
NonFinitePoint {
index: usize,
},
FunctionReturnedNonFinite {
evaluation: &'static str,
index: usize,
},
JacobianReturnedNonFinite {
row: usize,
column: usize,
},
IntegerOverflow,
WorkspaceOverflow,
NativeContractViolation,
}
impl fmt::Display for JacobianCheckError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmptySystem => write!(formatter, "Jacobian checks require a nonempty system"),
Self::NonFinitePoint { index } => {
write!(
formatter,
"Jacobian-check point at index {index} must be finite"
)
}
Self::FunctionReturnedNonFinite { evaluation, index } => write!(
formatter,
"function evaluation at {evaluation} left component {index} non-finite"
),
Self::JacobianReturnedNonFinite { row, column } => write!(
formatter,
"Jacobian entry ({row}, {column}) is non-finite or unwritten"
),
Self::IntegerOverflow => write!(
formatter,
"Jacobian-check dimension exceeds Fortran INTEGER"
),
Self::WorkspaceOverflow => write!(formatter, "Jacobian-check matrix size overflowed"),
Self::NativeContractViolation => write!(
formatter,
"native Jacobian checker returned an invalid score"
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for JacobianCheckError {}
#[derive(Clone, Debug, PartialEq)]
pub struct JacobianCheckResult<T = f64> {
pub scores: Vec<T>,
pub suspicious_rows: Vec<usize>,
}
fn check_input_f64(point: &[f64]) -> Result<usize, JacobianCheckError> {
if point.is_empty() {
return Err(JacobianCheckError::EmptySystem);
}
if let Some((index, _)) = point
.iter()
.enumerate()
.find(|(_, value)| !value.is_finite())
{
return Err(JacobianCheckError::NonFinitePoint { index });
}
point
.len()
.checked_mul(point.len())
.ok_or(JacobianCheckError::WorkspaceOverflow)
}
fn check_input_f32(point: &[f32]) -> Result<usize, JacobianCheckError> {
if point.is_empty() {
return Err(JacobianCheckError::EmptySystem);
}
if let Some((index, _)) = point
.iter()
.enumerate()
.find(|(_, value)| !value.is_finite())
{
return Err(JacobianCheckError::NonFinitePoint { index });
}
point
.len()
.checked_mul(point.len())
.ok_or(JacobianCheckError::WorkspaceOverflow)
}
pub fn check_jacobian<F, J>(
point: &[f64],
mut function: F,
mut jacobian: J,
) -> Result<JacobianCheckResult<f64>, JacobianCheckError>
where
F: FnMut(&[f64], &mut [f64]),
J: FnMut(&[f64], &[f64], JacobianMut<'_, f64>),
{
let matrix_len = check_input_f64(point)?;
let dimension = point.len();
let mut x = point.to_vec();
let mut fvec = vec![f64::NAN; dimension];
function(point, &mut fvec);
if let Some((index, _)) = fvec
.iter()
.enumerate()
.find(|(_, value)| !value.is_finite())
{
return Err(JacobianCheckError::FunctionReturnedNonFinite {
evaluation: "X",
index,
});
}
let mut fjac = vec![f64::NAN; matrix_len];
let view = JacobianMut::new(&mut fjac, dimension, dimension, dimension)
.ok_or(JacobianCheckError::WorkspaceOverflow)?;
jacobian(point, &fvec, view);
for column in 0..dimension {
for row in 0..dimension {
if !fjac[row + column * dimension].is_finite() {
return Err(JacobianCheckError::JacobianReturnedNonFinite { row, column });
}
}
}
let mut xp = vec![0.0; dimension];
let mut fvecp = vec![0.0; dimension];
let mut scores = vec![0.0; dimension];
let mut n = to_fortran_integer(dimension).map_err(|_| JacobianCheckError::IntegerOverflow)?;
let mut m = n;
let mut ldfjac = n;
let mut mode = 1;
unsafe {
slatec_sys::nonlinear::dckder(
&mut m,
&mut n,
x.as_mut_ptr(),
fvec.as_mut_ptr(),
fjac.as_mut_ptr(),
&mut ldfjac,
xp.as_mut_ptr(),
fvecp.as_mut_ptr(),
&mut mode,
scores.as_mut_ptr(),
);
}
fvecp.fill(f64::NAN);
function(&xp, &mut fvecp);
if let Some((index, _)) = fvecp
.iter()
.enumerate()
.find(|(_, value)| !value.is_finite())
{
return Err(JacobianCheckError::FunctionReturnedNonFinite {
evaluation: "XP",
index,
});
}
mode = 2;
unsafe {
slatec_sys::nonlinear::dckder(
&mut m,
&mut n,
x.as_mut_ptr(),
fvec.as_mut_ptr(),
fjac.as_mut_ptr(),
&mut ldfjac,
xp.as_mut_ptr(),
fvecp.as_mut_ptr(),
&mut mode,
scores.as_mut_ptr(),
);
}
if scores
.iter()
.any(|score| !score.is_finite() || !(0.0..=1.0).contains(score))
{
return Err(JacobianCheckError::NativeContractViolation);
}
let suspicious_rows = scores
.iter()
.enumerate()
.filter_map(|(index, score)| (*score < 0.5).then_some(index))
.collect();
Ok(JacobianCheckResult {
scores,
suspicious_rows,
})
}
pub fn check_jacobian_f32<F, J>(
point: &[f32],
mut function: F,
mut jacobian: J,
) -> Result<JacobianCheckResult<f32>, JacobianCheckError>
where
F: FnMut(&[f32], &mut [f32]),
J: FnMut(&[f32], &[f32], JacobianMut<'_, f32>),
{
let matrix_len = check_input_f32(point)?;
let dimension = point.len();
let mut x = point.to_vec();
let mut fvec = vec![f32::NAN; dimension];
function(point, &mut fvec);
if let Some((index, _)) = fvec
.iter()
.enumerate()
.find(|(_, value)| !value.is_finite())
{
return Err(JacobianCheckError::FunctionReturnedNonFinite {
evaluation: "X",
index,
});
}
let mut fjac = vec![f32::NAN; matrix_len];
let view = JacobianMut::new(&mut fjac, dimension, dimension, dimension)
.ok_or(JacobianCheckError::WorkspaceOverflow)?;
jacobian(point, &fvec, view);
for column in 0..dimension {
for row in 0..dimension {
if !fjac[row + column * dimension].is_finite() {
return Err(JacobianCheckError::JacobianReturnedNonFinite { row, column });
}
}
}
let mut xp = vec![0.0; dimension];
let mut fvecp = vec![0.0; dimension];
let mut scores = vec![0.0; dimension];
let mut n = to_fortran_integer(dimension).map_err(|_| JacobianCheckError::IntegerOverflow)?;
let mut m = n;
let mut ldfjac = n;
let mut mode = 1;
unsafe {
slatec_sys::nonlinear::chkder(
&mut m,
&mut n,
x.as_mut_ptr(),
fvec.as_mut_ptr(),
fjac.as_mut_ptr(),
&mut ldfjac,
xp.as_mut_ptr(),
fvecp.as_mut_ptr(),
&mut mode,
scores.as_mut_ptr(),
);
}
fvecp.fill(f32::NAN);
function(&xp, &mut fvecp);
if let Some((index, _)) = fvecp
.iter()
.enumerate()
.find(|(_, value)| !value.is_finite())
{
return Err(JacobianCheckError::FunctionReturnedNonFinite {
evaluation: "XP",
index,
});
}
mode = 2;
unsafe {
slatec_sys::nonlinear::chkder(
&mut m,
&mut n,
x.as_mut_ptr(),
fvec.as_mut_ptr(),
fjac.as_mut_ptr(),
&mut ldfjac,
xp.as_mut_ptr(),
fvecp.as_mut_ptr(),
&mut mode,
scores.as_mut_ptr(),
);
}
if scores
.iter()
.any(|score| !score.is_finite() || !(0.0..=1.0).contains(score))
{
return Err(JacobianCheckError::NativeContractViolation);
}
let suspicious_rows = scores
.iter()
.enumerate()
.filter_map(|(index, score)| (*score < 0.5).then_some(index))
.collect();
Ok(JacobianCheckResult {
scores,
suspicious_rows,
})
}
#[cfg(test)]
mod tests {
use super::{JacobianCheckError, check_input_f64};
#[test]
fn checker_input_validation_is_alloc_only() {
assert_eq!(check_input_f64(&[]), Err(JacobianCheckError::EmptySystem));
assert_eq!(
check_input_f64(&[f64::NAN]),
Err(JacobianCheckError::NonFinitePoint { index: 0 })
);
assert_eq!(check_input_f64(&[1.0, 2.0]), Ok(4));
}
}