Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,27 @@ jobs:
uses: actions/setup-python@v7
with:
python-version: "3.12"
- name: Cache DFTB+ parameters
uses: actions/cache@v6
with:
path: ~/.local/share/thermoscreening/slakos/3ob-3-1
key: dftbparams-3ob-3-1-v1
- name: Add conda to system path
run: |
# $CONDA is an environment variable pointing to the root of the miniconda directory
echo $CONDA/bin >> $GITHUB_PATH
- name: Install dependencies
run: |
conda install -y -c conda-forge python=3.12 gsl dftbplus
conda install -y -c conda-forge python=3.12 gsl 'dftbplus=25.1=nompi_*'
python -m pip install --upgrade pip
python -m pip install '.[test,lint]'
- name: Verify DFTB+ workflow
env:
THERMOSCREENING_REQUIRE_DFTB: "1"
run: |
thermo setup-dftb --parameter-set 3ob
thermo doctor --engine dftb+
python -m pytest tests/integration/test_dftb_workflow.py -q
- name: Lint with Pylint
run: python -m pylint ThermoScreening
- name: Test with pytest
Expand Down
6 changes: 0 additions & 6 deletions ThermoScreening/calculator/dftbplus.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,9 +582,6 @@ def read(self):
Hamiltonian_Filling="Fermi {",
Hamiltonian_Filling_empty="Temperature [Kelvin] = 300",

# Convergence helper
Hamiltonian_Mixer="DIIS{}",

# Are guessed by ase
Hamiltonian_MaxAngularMomentum_="",

Expand Down Expand Up @@ -632,9 +629,6 @@ def read(self):
Hamiltonian_Filling="Fermi {",
Hamiltonian_Filling_empty="Temperature [Kelvin] = 300",

# Convergence helper
Hamiltonian_Mixer="DIIS{}",

# Are guessed by ase
Hamiltonian_MaxAngularMomentum_="",

Expand Down
17 changes: 17 additions & 0 deletions docs/benchmarks/anthraquinone_workflow.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Anthraquinone workflow regression
=================================

The test suite contains a compact three-molecule anthraquinone fixture covering
the parent compound and two hydroxy substitution positions. It validates:

- deterministic SMILES embedding and molecular formulas;
- construction of the oxidized, singly reduced and doubly reduced states;
- separate reference calibration of both reduction steps;
- potential-inversion classification; and
- reproducible input-set provenance.

The fixture tests workflow invariants with controlled state energies. It is not
an absolute-potential accuracy benchmark and does not copy results from an
external dataset. Quantitative comparisons require the same structures,
electronic-structure method, solvent model, reference electrode and standard
states.
45 changes: 0 additions & 45 deletions docs/benchmarks/entropy_accuracy.md

This file was deleted.

94 changes: 94 additions & 0 deletions docs/benchmarks/entropy_accuracy.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
Gas-phase entropy accuracy
==========================

Standard molar entropies S°(298.15 K, 1 bar) from ThermoScreening compared with
experiment, for both engines. These are method accuracies (GFN2-xTB and
DFTB3/3ob), not tool errors. The thermochemistry implementation itself matches
ASE ``IdealGasThermo`` to within 0.0003 cal/(mol K).

.. list-table::
:header-rows: 1

* - Molecule
- Symmetry number
- xTB (GFN2)
- DFTB+ 3ob
- Experiment
* - H₂O
- 2
- 45.08
- 45.07
- 45.1
* - CH₄
- 12
- 44.44
- 44.47
- 44.5
* - NH₃
- 3
- 45.96
- 46.00
- 46.0
* - N₂
- 2
- 45.79
- 45.76
- 45.8
* - CO₂
- 2
- 51.23
- 51.53
- 51.1
* - CO
- 1
- 47.23
- 47.14
- 47.2
* - CH₃OH
- 1
- 56.63
- 57.00
- 57.3
* - C₂H₆
- 6
- 54.38
- 54.54
- 54.8

Values are in cal/(mol K).

Deviation from experiment
-------------------------

.. list-table::
:header-rows: 1

* - Engine
- MAD
- RMSD
- Maximum absolute deviation
- Mean signed deviation
* - xTB (GFN2)
- 0.17
- 0.28
- 0.67 (CH₃OH)
- -0.13
* - DFTB+ 3ob
- 0.14
- 0.21
- 0.43 (CO₂)
- -0.04

Both engines reproduce experimental gas-phase entropies to well under
1 cal/(mol K).

Notes
-----

- Symmetry numbers are detected correctly for all eight molecules.
- The values use ``pressure=100000`` (1 bar). The default pressure of 101325 Pa
raises the entropy by 0.026 cal/(mol K).
- Residual error for methanol and ethane comes from treating low-frequency
torsions as harmonic oscillators; ``quasi_rrho=True`` softens this.
- The Linux release gate runs a mandatory DFTB+ calculation. Broader xTB and
solvent checks remain optional integration tests.
2 changes: 2 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ Features
usage
configuration
api
benchmarks/entropy_accuracy
benchmarks/anthraquinone_workflow

Indices
-------
Expand Down
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
[pytest]
testpaths = tests
markers =
integration: requires an external calculation backend and parameter data
2 changes: 2 additions & 0 deletions tests/calculator/test_dftbplus.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,8 @@ def test_resolve_parameter_set_selects_hamiltonian_and_constants():
# 3ob is DFTB3 (third order), mio is DFTB2 (no third order)
assert params_3ob["Hamiltonian_ThirdOrderFull"] == "Yes"
assert "Hamiltonian_ThirdOrderFull" not in params_mio
assert "Hamiltonian_Mixer" not in params_3ob
assert "Hamiltonian_Mixer" not in params_mio
assert spin_3ob is dftbplus_module.SPIN_CONSTANTS_3OB
assert spin_mio is dftbplus_module.SPIN_CONSTANTS_MIO

Expand Down
4 changes: 4 additions & 0 deletions tests/data/regression/anthraquinones.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name,smiles,charge
anthraquinone,O=C1c2ccccc2C(=O)c2ccccc12,0
1-hydroxyanthraquinone,O=C1c2ccccc2C(=O)c2cccc(O)c12,0
2-hydroxyanthraquinone,O=C1c2ccccc2C(=O)c2ccc(O)cc12,0
5 changes: 5 additions & 0 deletions tests/data/regression/water.xyz
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
3
DFTB+ release-gate geometry
O -0.00566600 0.40130200 0.00000000
H 0.79261600 -0.19855100 0.00000000
H -0.78695000 -0.20275100 0.00000000
77 changes: 77 additions & 0 deletions tests/integration/test_dftb_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Mandatory DFTB+ release-gate workflow."""

import json
import math
import os
from pathlib import Path
import subprocess
import sys

import pytest


@pytest.mark.integration
def test_dftb_screen_end_to_end(tmp_path):
if os.getenv("THERMOSCREENING_REQUIRE_DFTB") != "1":
pytest.skip("set THERMOSCREENING_REQUIRE_DFTB=1 for the release gate")

structure = (
Path(__file__).resolve().parents[1] / "data" / "regression" / "water.xyz"
)
manifest = tmp_path / "molecules.csv"
manifest.write_text(
f"name,path,charge\nwater,{structure},0\n",
encoding="utf-8",
)
out = tmp_path / "results"
completed = subprocess.run(
[
sys.executable,
"-m",
"ThermoScreening",
"screen",
str(manifest),
"--out",
str(out),
"--directory",
str(tmp_path / "calculations"),
"--engine",
"dftb+",
],
capture_output=True,
text=True,
timeout=300,
check=False,
)

if completed.returncode:
calculation_dir = tmp_path / "calculations" / "water"
diagnostics = [completed.stdout, completed.stderr]
for filename in ("dftb_in.hsd", "second_derivative.out", "detailed.out"):
path = calculation_dir / filename
if path.is_file():
diagnostics.append(
f"\n--- {filename} ---\n{path.read_text(encoding='utf-8')}"
)
pytest.fail("".join(diagnostics))
records = json.loads(out.with_suffix(".json").read_text(encoding="utf-8"))
assert len(records) == 1
record = records[0]
assert record["name"] == "water"
assert record["formula"] == "H2O"
assert record["status"] == "ok"
for field in (
"Eelec_hartree",
"G_total_hartree",
"S_cal_per_mol_K",
"Cv_cal_per_mol_K",
):
assert math.isfinite(float(record[field]))

metadata = json.loads(
out.with_name(f"{out.name}-run.json").read_text(encoding="utf-8")
)
assert metadata["workflow"] == "thermochemistry_screen"
assert metadata["settings"]["engine"] == "dftb+"
assert metadata["input_set_fingerprint"]
assert metadata["jobs"][0]["structure_sha256"]
Loading