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
19 changes: 15 additions & 4 deletions ThermoScreening/cli/thermo.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
install_slakos,
)
from ThermoScreening.thermo.api import execute
from ThermoScreening.thermo.screening import screen
from ThermoScreening.thermo.screening import screen, rank_by_gibbs
from ThermoScreening.thermo.conformers import generate as generate_conformers, write_conformers
from ThermoScreening.version import __version__

Expand Down Expand Up @@ -245,11 +245,22 @@ def run_screen(parser_args):
resume=parser_args.resume,
)

failed = sum(1 for record in results if record["status"] != "ok")
print(f"Screened {len(results)} molecules ({failed} failed).")
ranked = rank_by_gibbs(results)
if ranked:
print("Ranked by Gibbs free energy (most stable first):")
for position, record in enumerate(ranked, start=1):
print(f" {position}. {record['name']} G = {record['G_total_hartree']:.6f} Ha")

failures = [record for record in results if record["status"] != "ok"]
if failures:
print(f"Failed ({len(failures)}):")
for record in failures:
print(f" {record['name']}: {record['error']}")

print(f"Screened {len(results)} molecules ({len(failures)} failed).")
print(f"Results: {parser_args.out}.csv, {parser_args.out}.json")

return 1 if failed else 0
return 1 if failures else 0


def run_conformers(parser_args):
Expand Down
2 changes: 1 addition & 1 deletion ThermoScreening/thermo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .inputFileReader import InputFileReader
from .system import System
from .thermo import Thermo
from .screening import screen
from .screening import screen, rank_by_gibbs
from .conformers import generate as generate_conformers, write_conformers
from .reactions import reaction_free_energy, reduction_potential
from .ensemble import boltzmann_weights, ensemble_free_energy, lowest_gibbs
24 changes: 24 additions & 0 deletions ThermoScreening/thermo/screening.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,30 @@ def _thermo_summary(thermo):
}


def rank_by_gibbs(results):
"""
Return the successful screen records sorted by Gibbs free energy.

Parameters
----------
results : list of dict
Records as returned by :func:`screen`.

Returns
-------
list of dict
The records whose ``status`` is ``"ok"`` (and that carry a
``G_total_hartree``), sorted by total Gibbs free energy ascending --
i.e. the most stable molecule first. Failed records are omitted.
"""
ranked = [
record
for record in results
if record.get("status") == "ok" and record.get("G_total_hartree") is not None
]
return sorted(ranked, key=lambda record: record["G_total_hartree"])


def _load_completed(out):
"""
Load the successfully-completed records from a prior ``<out>.json``.
Expand Down
1 change: 1 addition & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Screening
---------

.. autofunction:: ThermoScreening.thermo.screening.screen
.. autofunction:: ThermoScreening.thermo.screening.rank_by_gibbs
.. autoclass:: ThermoScreening.thermo.screening.ScreeningJob
:members:

Expand Down
47 changes: 45 additions & 2 deletions tests/thermo/test_screening.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,11 +433,28 @@ def test_cli_parse_args_routes_screen():
assert cli.parse_args(["screen", "molecules.csv", "--resume"]).resume is True


def test_cli_run_screen_returns_failure_count(monkeypatch):
def test_rank_by_gibbs_sorts_and_filters():
from ThermoScreening.thermo.screening import rank_by_gibbs

results = [
{"name": "a", "status": "ok", "G_total_hartree": -1.0},
{"name": "b", "status": "ok", "G_total_hartree": -3.0},
{"name": "c", "status": "error", "error": "boom"},
{"name": "d", "status": "ok", "G_total_hartree": -2.0},
{"name": "e", "status": "ok"}, # ok but no Gibbs value -> dropped
]
ranked = rank_by_gibbs(results)
assert [record["name"] for record in ranked] == ["b", "d", "a"]


def test_cli_run_screen_returns_failure_count(monkeypatch, capsys):
import ThermoScreening.cli.thermo as cli

monkeypatch.setattr(
cli, "screen", lambda *args, **kwargs: [{"status": "ok"}, {"status": "error"}]
cli, "screen", lambda *args, **kwargs: [
{"name": "m1", "status": "ok", "G_total_hartree": -2.0},
{"name": "m2", "status": "error", "error": "boom"},
]
)

args = Namespace(
Expand All @@ -448,6 +465,32 @@ def test_cli_run_screen_returns_failure_count(monkeypatch):
)

assert cli.run_screen(args) == 1 # one molecule failed
out = capsys.readouterr().out
assert "Ranked by Gibbs free energy" in out
assert "1. m1" in out
assert "m2: boom" in out


def test_cli_run_screen_all_ok_returns_zero(monkeypatch, capsys):
import ThermoScreening.cli.thermo as cli

monkeypatch.setattr(
cli, "screen", lambda *args, **kwargs: [
{"name": "m1", "status": "ok", "G_total_hartree": -2.0},
{"name": "m2", "status": "ok", "G_total_hartree": -3.0},
]
)
args = Namespace(
source="x", out="res", charge=0.0, temperature=298.15,
pressure=101325.0, directory="screening", parameter_set="3ob",
solvent=None, dispersion=None, quasi_rrho=False, engine="dftb+",
method="GFN2-xTB", resume=False,
)

assert cli.run_screen(args) == 0 # no failures
out = capsys.readouterr().out
assert "1. m2" in out and "2. m1" in out # most stable (m2) first
assert "Failed" not in out


def test_cli_parse_args_routes_conformers():
Expand Down
Loading