Skip to content

Commit 1805779

Browse files
committed
Fixed: report infinite vs finite off-diagonal pairs as asymmetric
Update Matrix::first_asymmetry to flag any non-finite difference between off-diagonal pairs as an asymmetry. This prevents cases where a single infinite entry paired with a finite entry would incorrectly pass as symmetric because the matrix's infinite norm blew the tolerance up to infinity, making the comparison `diff > eps` return false. Refs: #84
1 parent 87d426f commit 1805779

1 file changed

Lines changed: 30 additions & 9 deletions

File tree

src/matrix.rs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,15 @@ impl<const D: usize> Matrix<D> {
166166
/// offending pair when this returns `false`.
167167
///
168168
/// # NaN / infinity handling
169-
/// Any NaN off-diagonal entry causes this predicate to return `false`
170-
/// (because `NaN <= eps` is `false`). A matrix whose [`inf_norm`](Self::inf_norm)
171-
/// is `+∞` can produce an `eps` of `+∞`, under which any finite asymmetry
172-
/// is tolerated — callers who need strict equality on infinite entries
173-
/// should validate finiteness separately.
169+
/// Any non-finite `|self[r][c] - self[c][r]|` (NaN or ±∞) causes this
170+
/// predicate to return `false`. This catches both NaN off-diagonals and
171+
/// asymmetric pairs where one side is infinite and the other is finite
172+
/// (which would otherwise slip through when `inf_norm()` blows `eps` up
173+
/// to `+∞` and makes `diff > eps` trivially false). A matrix whose
174+
/// [`inf_norm`](Self::inf_norm) is `+∞` can still tolerate *finite*
175+
/// asymmetries under an infinite `eps` — callers who need strict equality
176+
/// on large-magnitude finite entries should validate finiteness
177+
/// separately.
174178
///
175179
/// # Panics
176180
/// In debug builds, panics if `rel_tol` is negative or NaN; in release
@@ -228,10 +232,15 @@ impl<const D: usize> Matrix<D> {
228232
for r in 0..D {
229233
for c in (r + 1)..D {
230234
let diff = (self.rows[r][c] - self.rows[c][r]).abs();
231-
// NaN is reported as asymmetric: a NaN entry contaminates `diff`,
232-
// and `diff > eps` alone would silently skip it because ordered
233-
// comparisons against NaN are always `false`.
234-
if diff.is_nan() || diff > eps {
235+
// Any non-finite `diff` is reported as asymmetric:
236+
// * NaN contaminates one side only, and `diff > eps` would
237+
// silently skip it because ordered comparisons against NaN
238+
// are always `false`.
239+
// * ±∞ arises when exactly one of `self[r][c]` / `self[c][r]`
240+
// is infinite; a naive `diff > eps` misses this when the
241+
// matrix's `inf_norm()` pushes `eps` to `+∞` (because
242+
// `∞ > ∞` is `false`).
243+
if !diff.is_finite() || diff > eps {
235244
cold_path();
236245
return Some((r, c));
237246
}
@@ -1027,6 +1036,18 @@ mod tests {
10271036
assert_eq!(a.first_asymmetry(1e-12), Some((0, 2)));
10281037
}
10291038

1039+
/// Regression: a single infinite off-diagonal paired with a finite entry
1040+
/// used to slip through as "symmetric" because `inf_norm()` blew `eps` up
1041+
/// to `+∞` and `∞ > ∞` evaluates to `false`. After the fix, any
1042+
/// non-finite `|a[r][c] - a[c][r]|` is reported as an asymmetry regardless
1043+
/// of `eps`.
1044+
#[test]
1045+
fn first_asymmetry_flags_infinite_offdiagonal_against_finite() {
1046+
let a = Matrix::<2>::from_rows([[1.0, f64::INFINITY], [0.0, 1.0]]);
1047+
assert_eq!(a.first_asymmetry(1e-12), Some((0, 1)));
1048+
assert!(!a.is_symmetric(1e-12));
1049+
}
1050+
10301051
#[cfg(debug_assertions)]
10311052
#[test]
10321053
#[should_panic(expected = "rel_tol must be non-negative")]

0 commit comments

Comments
 (0)