Skip to content

Reject imaginary vibrational frequencies instead of returning NaN - #62

Merged
galjos merged 1 commit into
mainfrom
guard-imaginary-vibrational-modes
Jun 30, 2026
Merged

Reject imaginary vibrational frequencies instead of returning NaN#62
galjos merged 1 commit into
mainfrom
guard-imaginary-vibrational-modes

Conversation

@galjos

@galjos galjos commented Jun 29, 2026

Copy link
Copy Markdown
Member

Closes #60.

An imaginary (non-positive) vibrational mode in the kept dof set made the harmonic-oscillator formulas return NaN entropy/Gibbs silently (np.log(1 - exp(-theta/T)) of a negative argument). For the screening framework that means a NaN row instead of a clean error.

Fix

_vibrational_contribution raises TSValueError when real_vibrational_frequencies contains a non-positive value — a non-minimum geometry is now reported as an error (and isolated by the screening loop) instead of producing NaN.

Why the kept set, not has_imaginary_frequencies

Real DFTB+ output carries small negative translation/rotation frequencies in the input (e.g. frequency.txt starts -35.7, -14.34, -3.23) that frequency_dof correctly drops. Guarding on System.has_imaginary_frequencies (the full input) would wrongly reject every real molecule; the kept set of a true minimum is strictly positive (verified: the anthraquinone fixture keeps 66 modes, min 40.6 cm⁻¹, and still computes S = 103.743).

Tests

A 3-atom system with an imaginary mode in the kept set now raises TSValueError; the existing nonlinear/anthraquinone regressions (negative input, positive kept) are unchanged. 163 passed; pylint 8.90.

Found by the thermochemistry audit alongside #59; #61 (spin/multiplicity) remains.

An imaginary (non-positive) mode in the kept vibrational set made the harmonic
formulas return NaN entropy/Gibbs silently. _vibrational_contribution now raises
TSValueError when real_vibrational_frequencies contains a non-positive value, so
a non-minimum geometry is reported as an error (and isolated by the screening
loop) rather than producing a NaN result.

The check is on the kept set, not System.has_imaginary_frequencies: real DFTB+
input carries small negative translation/rotation frequencies that frequency_dof
correctly drops, so the kept set of a true minimum is strictly positive.
@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.16%. Comparing base (0b25e99) to head (36c8cc3).

Additional details and impacted files
@@           Coverage Diff           @@
##             main      #62   +/-   ##
=======================================
  Coverage   96.15%   96.16%           
=======================================
  Files          22       22           
  Lines        1301     1303    +2     
=======================================
+ Hits         1251     1253    +2     
  Misses         50       50           
Flag Coverage Δ
unittests 96.16% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@galjos
galjos merged commit 199c3eb into main Jun 30, 2026
3 checks passed
@galjos
galjos deleted the guard-imaginary-vibrational-modes branch June 30, 2026 09:52
galjos added a commit that referenced this pull request Jul 2, 2026
Closes #61.

Electronic entropy (`q_elec = 2S+1`, `S_elec = R ln q`) took its spin
from `spin(charge)` — **charge parity** — which is wrong for open-shell
species.

## Change
- Replace it with `default_spin(atoms, charge)`: the **minimum-spin
ground state** from the electron-count parity — `even → singlet` (S=0),
`odd → doublet` (S=0.5). Even electron count is a singlet ~99% of the
time, and an odd count is essentially always a doublet, so this
auto-handles closed-shell molecules **and** simple radicals (e.g. every
1-electron-reduced redox species) correctly.
- Add an explicit, validated (non-negative) `spin` argument to `System`,
threaded through `run_thermo`, `dftbplus_thermo`, and `screen` (optional
`spin` manifest column) for the cases the guess cannot know:
**even-electron high-spin ground states** (triplet O2 → `spin=1`, →
`S_elec = R ln 3`).

```python
System(atoms, charge=0, spin=1.0, ...)   # triplet O2 (even electrons, user decides)
# radicals (odd electrons) are auto-guessed as doublets
```

## Tests
- `default_spin` electron-count parametrized (water/O2/hydroxide → 0;
methyl radical → 0.5); explicit-spin override; negative-spin rejection.
- Triplet O2 (`spin=1`) → `S_elec = R ln 3`.
- Manifest `spin` column → `ScreeningJob.spin`; `screen` passes it to
`dftbplus_thermo`.

172 passed; pylint 8.86.

Follow-up (separate PR, once this lands): opt-in **spin-polarised
DFTB+** so a declared/guessed open-shell spin also drives the
calculation (verified 3ob spin constants ready). Closes out the
thermochemistry audit (#59 rotational, #62 imaginary modes, #61 spin).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Imaginary vibrational modes silently produce NaN thermochemistry

1 participant