Skip to content

Commit 182cda5

Browse files
committed
fix: use scale-relative tolerance for null-mode filtering in vibrational entropy
1 parent e80b532 commit 182cda5

3 files changed

Lines changed: 27 additions & 17 deletions

File tree

‎CodeEntropy/entropy/vibrational.py‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def _matrix_eigenvalues(matrix: np.ndarray) -> np.ndarray:
148148
Eigenvalues as a NumPy array.
149149
"""
150150
matrix = np.asarray(matrix, dtype=float)
151-
return la.eigvals(matrix)
151+
return la.eigvalsh(matrix)
152152

153153
def _convert_lambda_units(self, lambdas: np.ndarray) -> np.ndarray:
154154
"""Convert eigenvalues into SI units using run_manager.
@@ -185,7 +185,10 @@ def _frequencies_from_lambdas(self, lambdas: np.ndarray, temp: float) -> np.ndar
185185
"""Convert eigenvalues to frequencies with robust filtering.
186186
187187
Filters out eigenvalues that are complex, non-positive, or near-zero to
188-
avoid invalid frequencies and unstable entropies.
188+
avoid invalid frequencies and unstable entropies. The near-zero
189+
tolerance scales with the spectrum's own magnitude (as in
190+
``numpy.linalg.matrix_rank``) rather than using a fixed cutoff, since
191+
the eigensolver noise floor around a true null mode is not constant.
189192
190193
Args:
191194
lambdas: Eigenvalues (post unit conversion).
@@ -197,8 +200,11 @@ def _frequencies_from_lambdas(self, lambdas: np.ndarray, temp: float) -> np.ndar
197200
lambdas = np.asarray(lambdas)
198201
lambdas = np.real_if_close(lambdas, tol=1000)
199202

203+
scale = float(np.max(np.abs(lambdas))) if lambdas.size else 0.0
204+
atol = lambdas.size * np.finfo(float).eps * scale
205+
200206
valid_mask = (
201-
np.isreal(lambdas) & (lambdas > 0) & (~np.isclose(lambdas, 0, atol=1e-7))
207+
np.isreal(lambdas) & (lambdas > 0) & (~np.isclose(lambdas, 0, atol=atol))
202208
)
203209

204210
removed = int(len(lambdas) - np.count_nonzero(valid_mask))

‎tests/regression/baselines/benzaldehyde/grouping_each.json‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"0": {
44
"components": {
55
"united_atom:Transvibrational": 0.0,
6-
"united_atom:Rovibrational": 0.062499436482478445,
6+
"united_atom:Rovibrational": 0.062499436482478535,
77
"residue:FTmat-Transvibrational": 16.83857036769833,
88
"residue:FTmat-Rovibrational": 0.0,
99
"united_atom:Conformational": 0.0,
@@ -27,26 +27,26 @@
2727
"2": {
2828
"components": {
2929
"united_atom:Transvibrational": 0.0,
30-
"united_atom:Rovibrational": 0.0022147884278620462,
31-
"residue:FTmat-Transvibrational": 17.449372254125862,
30+
"united_atom:Rovibrational": 0.0022147884278620493,
31+
"residue:FTmat-Transvibrational": 17.449372254125855,
3232
"residue:FTmat-Rovibrational": 0.0,
3333
"united_atom:Conformational": 0.0,
3434
"residue:Conformational": 0.0,
3535
"residue:Orientational": 31.909265242174982
3636
},
37-
"total": 49.36085228472871
37+
"total": 49.3608522847287
3838
},
3939
"3": {
4040
"components": {
4141
"united_atom:Transvibrational": 0.0,
42-
"united_atom:Rovibrational": 138.65701144028458,
42+
"united_atom:Rovibrational": 0.001983835543157824,
4343
"residue:FTmat-Transvibrational": 15.803976930845373,
4444
"residue:FTmat-Rovibrational": 0.0,
4545
"united_atom:Conformational": 0.0,
4646
"residue:Conformational": 0.0,
4747
"residue:Orientational": 0.0
4848
},
49-
"total": 154.46098837112996
49+
"total": 15.80596076638853
5050
},
5151
"4": {
5252
"components": {
@@ -63,7 +63,7 @@
6363
"5": {
6464
"components": {
6565
"united_atom:Transvibrational": 0.0,
66-
"united_atom:Rovibrational": 0.010824819910336835,
66+
"united_atom:Rovibrational": 0.010824819910336868,
6767
"residue:FTmat-Transvibrational": 11.05479779559611,
6868
"residue:FTmat-Rovibrational": 0.0,
6969
"united_atom:Conformational": 0.0,
@@ -75,7 +75,7 @@
7575
"6": {
7676
"components": {
7777
"united_atom:Transvibrational": 0.0,
78-
"united_atom:Rovibrational": 0.00018982532508837594,
78+
"united_atom:Rovibrational": 0.00018982532508837537,
7979
"residue:FTmat-Transvibrational": 19.723925127490933,
8080
"residue:FTmat-Rovibrational": 0.0,
8181
"united_atom:Conformational": 0.0,
@@ -99,7 +99,7 @@
9999
"8": {
100100
"components": {
101101
"united_atom:Transvibrational": 0.0,
102-
"united_atom:Rovibrational": 0.020362757918507005,
102+
"united_atom:Rovibrational": 0.020362757918507036,
103103
"residue:FTmat-Transvibrational": 16.13204209116967,
104104
"residue:FTmat-Rovibrational": 0.0,
105105
"united_atom:Conformational": 0.0,
@@ -111,14 +111,14 @@
111111
"9": {
112112
"components": {
113113
"united_atom:Transvibrational": 0.0,
114-
"united_atom:Rovibrational": 138.65525900218074,
114+
"united_atom:Rovibrational": 0.00023139743934636996,
115115
"residue:FTmat-Transvibrational": 21.42548529236268,
116116
"residue:FTmat-Rovibrational": 0.0,
117117
"united_atom:Conformational": 0.0,
118118
"residue:Conformational": 0.0,
119119
"residue:Orientational": 0.0
120120
},
121-
"total": 160.08074429454342
121+
"total": 21.425716689802027
122122
}
123123
}
124124
}

‎tests/unit/CodeEntropy/entropy/test_vibrational_entropy_math.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,21 @@ def get_KT2J(self, temp):
1616
return RM()
1717

1818

19-
def test_matrix_eigenvalues_returns_complex_dtype_possible(run_manager):
19+
def test_matrix_eigenvalues_returns_real_dtype_for_symmetric_matrix(run_manager):
2020
ve = VibrationalEntropy(run_manager=run_manager)
21-
m = np.array([[0.0, -1.0], [1.0, 0.0]])
21+
m = np.array([[2.0, -1.0], [-1.0, 2.0]])
2222
eigs = ve._matrix_eigenvalues(m)
2323
assert eigs.shape == (2,)
24+
assert not np.iscomplexobj(eigs)
2425

2526

2627
def test_frequencies_from_lambdas_filters_nonpositive_and_near_zero(run_manager):
28+
"""Near-zero eigenvalue is scaled to the spectrum's magnitude, not fixed."""
2729
ve = VibrationalEntropy(run_manager=run_manager)
2830

29-
lambdas = np.array([-1.0, 0.0, 1e-12, 1.0, 4.0])
31+
scale = 4.0
32+
noise = np.finfo(float).eps * scale
33+
lambdas = np.array([-1.0, 0.0, noise, 1.0, scale])
3034
freqs = ve._frequencies_from_lambdas(lambdas, temp=298.0)
3135

3236
assert freqs.size == 2

0 commit comments

Comments
 (0)