Skip to content

Add xtb_fukui_indices: per-atom reactivity via xtb --vfukui - #123

Merged
galjos merged 2 commits into
mainfrom
xtb-fukui-indices
Jul 13, 2026
Merged

Add xtb_fukui_indices: per-atom reactivity via xtb --vfukui#123
galjos merged 2 commits into
mainfrom
xtb-fukui-indices

Conversation

@galjos

@galjos galjos commented Jul 13, 2026

Copy link
Copy Markdown
Member

`reaction_free_energy`/`reduction_potential` answer whether a reaction is favorable for a molecule as a whole -- not where on it, or whether some other site reacts first (a side reaction competing with the intended one). Getting that kind of site-resolved reactivity data out of GFN-xTB is trickier than it sounds: `xtb`'s `--pop` flag only exposes Mulliken partial charges, not spin density, for GFN-xTB, so there's no clean way to ask "where would extra electron density localize."

The fix

`xtb` has a purpose-built flag for exactly this: `--vfukui` runs the base calculation plus its automatically-perturbed +1/-1-electron states and reports per-atom Fukui indices -- f(+) (susceptibility to electron gain / reduction), f(-) (electron loss / oxidation), f(0) (radical attack, their average) -- in one CLI call, no extra postprocessing tool, and it works with the default GFN2-xTB Hamiltonian (unlike `--vipea`/`--vomega`, which need GFN1 + special parameters).

`xtb_fukui_indices(atoms, charge=0.0, spin=None, method="GFN2-xTB", solvent=None, directory=None)` wraps this as a single-point analysis on an already-optimised geometry (pair it with `xtb_cli_thermo`), returning `(symbol, f_plus, f_minus, f_zero)` per atom in input order. Standalone -- follows the `pka.py`/`reactions.py`/`ensemble.py` pattern of not touching the `Thermo` class, since this is inherently xtb-specific (not every engine exposes this).

Verification

Unit tests mock the subprocess call (matching the existing `test_xtb_cli.py` pattern for `run_xtb`) covering command construction, output parsing, and failure modes. A real end-to-end test (skippable, gated on the native `xtb` binary) runs on water and checks a real, verified chemical result: oxygen dominates f(-) (electron loss, from its lone pairs) while the two symmetric H atoms dominate f(+) (electron gain) -- and that both columns are normalised (sum to ~1 over all atoms, the expected property of a Fukui function).

Full suite: 416 passed, 10 skipped with a real `xtb` binary. New code fully covered. Docs build clean.

Closes #122

reaction_free_energy/reduction_potential answer whether a reaction is
favorable for a molecule as a whole, not where on it, or whether some
other site reacts first. xtb --vfukui gives per-atom Fukui indices
(susceptibility to electron gain/loss/either) in a single CLI call, no
extra postprocessing tool needed -- unlike Mulliken spin density, which
GFN-xTB's CLI doesn't cleanly expose via --pop.

Standalone, following the pka.py/reactions.py/ensemble.py pattern of not
touching the Thermo class.
@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.39%. Comparing base (7793902) to head (b1432d1).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #123      +/-   ##
==========================================
+ Coverage   97.34%   97.39%   +0.05%     
==========================================
  Files          32       32              
  Lines        1957     1995      +38     
==========================================
+ Hits         1905     1943      +38     
  Misses         52       52              
Flag Coverage Δ
unittests 97.39% <100.00%> (+0.05%) ⬆️

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.

The wrapper's spin-to-unpaired conversion and directory handling were
only exercised by the real-xtb-gated end-to-end test, so CI (no xtb
binary) saw it as uncovered. Mock run_xtb_fukui directly, matching the
existing test_xtb_cli_thermo_pipeline pattern.
@galjos
galjos merged commit 8d532e1 into main Jul 13, 2026
5 checks passed
@galjos
galjos deleted the xtb-fukui-indices branch July 13, 2026 06:49
galjos added a commit that referenced this pull request Jul 13, 2026
\`_parse_fukui\` (added in #123) only checked token count (\`len(tokens)
!= 4\`) to detect the end of the \`Fukui functions:\` table. A code
review flagged this as an "acceptable risk" of stdout-scraping, but on a
second look it's a cheap fix, not something to just accept.

Verified xtb writes this data \*only\* to stdout: a clean-directory
\`--vfukui\` run writes no dedicated file for it, only
\`charges\`/\`wbo\`/\`xtbrestart\`/\`xtbtopo.mol\` -- so stdout-scraping
itself isn't avoidable. But the end-of-table check can be tightened: a
coincidental 4-token line right after the real table (before the blank
line that actually ends it) would previously get silently misparsed as a
data row.

## The fix

Also verify the label matches "digits then letters"
(\`^(\d+)([A-Za-z]+)$\`) and the last 3 tokens parse as floats before
accepting a row; either check failing now correctly ends the table
instead of raising deep inside a malformed row.

## Verification

Two new regression tests reproduce the exact failure mode this fixes (a
coincidental 4-token line with a bad label, and one with non-numeric
values), plus a test for multi-digit atom indices with multi-letter
element symbols (e.g. \`14Cl\`). Full suite: 421 passed, 10 skipped with
a real \`xtb\` binary. New code fully covered (only remaining gap in the
file pre-dates this change). Docs build clean.

Closes #124
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.

Add per-atom Fukui reactivity indices via xtb --vfukui

1 participant