Skip to content

Support explicit spin; default to the minimum-spin ground state - #63

Merged
galjos merged 1 commit into
mainfrom
add-explicit-spin
Jul 2, 2026
Merged

Support explicit spin; default to the minimum-spin ground state#63
galjos merged 1 commit into
mainfrom
add-explicit-spin

Conversation

@galjos

@galjos galjos commented Jun 29, 2026

Copy link
Copy Markdown
Member

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).
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).

@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.17%. Comparing base (199c3eb) to head (4f7fbe5).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #63      +/-   ##
==========================================
+ Coverage   96.16%   96.17%   +0.01%     
==========================================
  Files          22       22              
  Lines        1303     1308       +5     
==========================================
+ Hits         1253     1258       +5     
  Misses         50       50              
Flag Coverage Δ
unittests 96.17% <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 force-pushed the add-explicit-spin branch 2 times, most recently from 51f49d4 to 8b85fde Compare July 1, 2026 09:53
@galjos galjos changed the title Support explicit spin and default it from electron count Support explicit spin; default to a closed-shell singlet Jul 1, 2026
Electronic entropy (q_elec = 2S+1) took its spin from charge parity, which is
wrong for open-shell species. Replace it with default_spin(atoms, charge): the
minimum-spin ground state from the electron-count parity - even -> singlet
(~99% of even-electron molecules), odd -> doublet (essentially always the
radical ground state). This reliably auto-handles closed-shell molecules and
simple radicals; even-electron high-spin ground states (triplet O2) are set with
the explicit, validated spin argument added to System and threaded through
run_thermo, dftbplus_thermo and screen (optional manifest 'spin' column).
@galjos
galjos force-pushed the add-explicit-spin branch from 8b85fde to 4f7fbe5 Compare July 2, 2026 10:14
@galjos galjos changed the title Support explicit spin; default to a closed-shell singlet Support explicit spin; default to the minimum-spin ground state Jul 2, 2026
@galjos
galjos merged commit 1395885 into main Jul 2, 2026
3 checks passed
@galjos
galjos deleted the add-explicit-spin branch July 2, 2026 10:40
galjos added a commit that referenced this pull request Jul 2, 2026
Follow-up to #63. #63 made the user spin drive the electronic-degeneracy
*entropy*; this makes it drive the DFTB+ *calculation*, so open-shell
species are treated consistently (open-shell energy **and** the matching
multiplicity), with **no extra input**.

## Behaviour (charge is the only per-molecule input)
`dftbplus_thermo` resolves the spin — the electron-count guess when not
given (even→singlet, odd→doublet) — and:
- **S > 0 → colinear spin-polarised** DFTB+: `SpinPolarisation =
Colinear { UnpairedElectrons = round(2S) }` + the 3ob `SpinConstants`
for the atoms present, injected into `Geoopt`/`Hessian`.
- **S = 0 → restricted** closed-shell calculation, byte-for-byte
unchanged (the default methodology).

So a **radical (odd electrons) is auto-detected and run spin-polarised**
— e.g. an anthraquinone radical anion (charge −1) just works. The same S
drives the calculation and the entropy, so they stay consistent. Only
even-electron **triplets** (O₂) need an explicit `spin=1`.

## Spin constants
`SPIN_CONSTANTS` is the **3ob-3-1 PBE** set — the compact single-shell
form (HOMO `W`, `ShellResolvedSpin = No`) matching this tool’s
atom-resolved 3ob SCC. Cross-verified across three independent sources
(3ob Elstner list ↔ QUASINANO2015 arXiv:1605.01360 ↔ DFTB+
manual/mio/Köhler). Covers H, C, N, O, F, P, S, Cl; elements without an
authoritative constant (Br, I, metals) raise a clear error rather than a
guessed number. The constants are parameters tied to the SK
set/functional — see the code comment.

## Tests (offline; the slako files are absent here)
- `_spin_kwargs`: restricted for S∈(None,0) and fractional S; colinear
block for a doublet; error for an untabulated element.
- `dftbplus_thermo` (mocked pipeline): an OH radical auto-spin-polarises
(`UnpairedElectrons=1`, S passed to the analysis as 0.5); closed-shell
water stays restricted; explicit O₂ `spin=1` → `UnpairedElectrons=2`.

178 passed, 5 skipped; pylint 8.88.

## Caveat
This is a methodology change gated behind an open-shell spin (restricted
stays the default). I could not validate the *energies* here — the 3ob
slako files are gone from this machine — so the wiring and constants are
verified but the numbers should be confirmed with a real spin-polarised
run (restored slako). The S d-shell constant is contested upstream, but
only s/p are used for organic sulfur.
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.

Spin/multiplicity inferred from charge parity only; open-shell species wrong

1 participant