@@ -195,6 +195,10 @@ impl<const D: usize> Matrix<D> {
195195 /// Non-finite entries are rejected with source coordinates instead of
196196 /// silently propagating NaN or infinity through the norm.
197197 ///
198+ /// Row sums are accumulated in `f64` with ordinary addition. This method
199+ /// checks for non-finite inputs and overflowed accumulators, but it does not
200+ /// provide a certified absolute rounding bound for the returned norm.
201+ ///
198202 /// # Examples
199203 /// ```
200204 /// use la_stack::prelude::*;
@@ -263,9 +267,17 @@ impl<const D: usize> Matrix<D> {
263267 /// Use [`first_asymmetry`](Self::first_asymmetry) to locate the first
264268 /// offending pair when this returns `Ok(false)`.
265269 ///
270+ /// The `rel_tol` argument is a [`Tolerance`], so raw caller input must be
271+ /// finite and non-negative before it can reach this predicate. Use
272+ /// [`Tolerance::new`] or [`LaError::validate_tolerance`] when accepting a
273+ /// raw `f64`; negative, NaN, and infinite tolerances return
274+ /// [`LaError::InvalidTolerance`].
275+ ///
266276 /// # NaN / infinity handling
267277 /// Stored NaN or ±∞ entries return [`LaError::NonFinite`] with the
268- /// offending matrix coordinates. If both stored entries are finite but
278+ /// offending matrix coordinates. A finite matrix can still return
279+ /// [`LaError::NonFinite`] if computing the scaled symmetry tolerance
280+ /// overflows to NaN or infinity. If both stored entries are finite but
269281 /// their difference overflows to ±∞, the pair is reported as asymmetric.
270282 ///
271283 /// # Examples
@@ -284,7 +296,9 @@ impl<const D: usize> Matrix<D> {
284296 /// ```
285297 ///
286298 /// # Errors
287- /// Returns [`LaError::NonFinite`] when any matrix entry is NaN or infinite.
299+ /// Returns [`LaError::NonFinite`] when any matrix entry is NaN or infinite,
300+ /// or when computing the scaled symmetry tolerance overflows to NaN or
301+ /// infinity.
288302 #[ inline]
289303 pub fn is_symmetric ( & self , rel_tol : Tolerance ) -> Result < bool , LaError > {
290304 Ok ( self . first_asymmetry ( rel_tol) ?. is_none ( ) )
@@ -299,6 +313,18 @@ impl<const D: usize> Matrix<D> {
299313 /// predicate is the same as [`is_symmetric`](Self::is_symmetric):
300314 /// `|self[r][c] - self[c][r]| <= rel_tol * max(1.0, inf_norm(self))`.
301315 ///
316+ /// Stored NaN or ±∞ entries return [`LaError::NonFinite`] with the
317+ /// offending matrix coordinates. A finite matrix can still return
318+ /// [`LaError::NonFinite`] if computing the scaled symmetry tolerance
319+ /// overflows to NaN or infinity. If both stored entries are finite but
320+ /// their difference overflows to ±∞, the pair is reported as asymmetric.
321+ ///
322+ /// The `rel_tol` argument is a [`Tolerance`], so raw caller input must be
323+ /// finite and non-negative before it can reach this predicate. Use
324+ /// [`Tolerance::new`] or [`LaError::validate_tolerance`] when accepting a
325+ /// raw `f64`; negative, NaN, and infinite tolerances return
326+ /// [`LaError::InvalidTolerance`].
327+ ///
302328 /// # Examples
303329 /// ```
304330 /// use la_stack::prelude::*;
@@ -317,7 +343,9 @@ impl<const D: usize> Matrix<D> {
317343 /// ```
318344 ///
319345 /// # Errors
320- /// Returns [`LaError::NonFinite`] when any matrix entry is NaN or infinite.
346+ /// Returns [`LaError::NonFinite`] when any matrix entry is NaN or infinite,
347+ /// or when computing the scaled symmetry tolerance overflows to NaN or
348+ /// infinity.
321349 #[ inline]
322350 pub fn first_asymmetry ( & self , rel_tol : Tolerance ) -> Result < Option < ( usize , usize ) > , LaError > {
323351 let eps = self . symmetry_epsilon ( rel_tol) ?;
@@ -351,7 +379,9 @@ impl<const D: usize> Matrix<D> {
351379 /// off-diagonal mismatches.
352380 ///
353381 /// # Errors
354- /// Returns [`LaError::NonFinite`] when any matrix entry is NaN or infinite.
382+ /// Returns [`LaError::NonFinite`] when any matrix entry is NaN or infinite,
383+ /// or when computing the scaled symmetry tolerance overflows to NaN or
384+ /// infinity.
355385 fn symmetry_epsilon ( & self , rel_tol : Tolerance ) -> Result < f64 , LaError > {
356386 let rel_tol = rel_tol. get ( ) ;
357387 let mut eps = rel_tol;
@@ -365,6 +395,10 @@ impl<const D: usize> Matrix<D> {
365395 return Err ( LaError :: non_finite_cell ( r, c) ) ;
366396 }
367397 row_eps = rel_tol. mul_add ( entry. abs ( ) , row_eps) ;
398+ if !row_eps. is_finite ( ) {
399+ cold_path ( ) ;
400+ return Err ( LaError :: non_finite_at ( c) ) ;
401+ }
368402 }
369403 if row_eps > eps {
370404 eps = row_eps;
@@ -393,6 +427,12 @@ impl<const D: usize> Matrix<D> {
393427 /// # }
394428 /// ```
395429 ///
430+ /// The `tol` argument is a [`Tolerance`], so raw caller input must be
431+ /// finite and non-negative before it can reach factorization. Use
432+ /// [`Tolerance::new`] or [`LaError::validate_tolerance`] when accepting a
433+ /// raw `f64`; negative, NaN, and infinite tolerances return
434+ /// [`LaError::InvalidTolerance`].
435+ ///
396436 /// # Errors
397437 /// Returns [`LaError::Singular`] if, for some column `k`, the largest-magnitude candidate pivot
398438 /// in that column satisfies `|pivot| <= tol` (so no numerically usable pivot exists).
@@ -416,6 +456,12 @@ impl<const D: usize> Matrix<D> {
416456 /// general-purpose factorization that tolerates non-symmetric inputs, use
417457 /// [`lu`](Self::lu) instead.
418458 ///
459+ /// The `tol` argument is a [`Tolerance`], so raw caller input must be
460+ /// finite and non-negative before it can reach factorization. Use
461+ /// [`Tolerance::new`] or [`LaError::validate_tolerance`] when accepting a
462+ /// raw `f64`; negative, NaN, and infinite tolerances return
463+ /// [`LaError::InvalidTolerance`].
464+ ///
419465 /// # Examples
420466 /// ```
421467 /// use la_stack::prelude::*;
@@ -569,6 +615,12 @@ impl<const D: usize> Matrix<D> {
569615 /// speedup (see [`det_direct`](Self::det_direct)). The `tol` parameter is only used
570616 /// by the LU fallback path for D ≥ 5.
571617 ///
618+ /// The `tol` argument is a [`Tolerance`], so raw caller input must be
619+ /// finite and non-negative before it can reach the determinant path. Use
620+ /// [`Tolerance::new`] or [`LaError::validate_tolerance`] when accepting a
621+ /// raw `f64`; negative, NaN, and infinite tolerances return
622+ /// [`LaError::InvalidTolerance`].
623+ ///
572624 /// # Examples
573625 /// ```
574626 /// use la_stack::prelude::*;
@@ -1464,6 +1516,21 @@ mod tests {
14641516 assert ! ( !a. is_symmetric( Tolerance :: new( 0.0 ) . unwrap( ) ) . unwrap( ) ) ;
14651517 }
14661518
1519+ #[ test]
1520+ fn first_asymmetry_rejects_scaled_epsilon_overflow ( ) {
1521+ let a = Matrix :: < 2 > :: from_rows ( [ [ 2.0 , 0.0 ] , [ 0.0 , 1.0 ] ] ) ;
1522+ let tol = Tolerance :: new ( f64:: MAX ) . unwrap ( ) ;
1523+
1524+ assert_eq ! (
1525+ a. first_asymmetry( tol) ,
1526+ Err ( LaError :: NonFinite { row: None , col: 0 } )
1527+ ) ;
1528+ assert_eq ! (
1529+ a. is_symmetric( tol) ,
1530+ Err ( LaError :: NonFinite { row: None , col: 0 } )
1531+ ) ;
1532+ }
1533+
14671534 #[ test]
14681535 fn first_asymmetry_flags_overflowed_finite_difference ( ) {
14691536 let a = Matrix :: < 2 > :: from_rows ( [ [ 1.0 , f64:: MAX ] , [ -f64:: MAX , 1.0 ] ] ) ;
0 commit comments