From ceac64d99c5f5764640b9629e79aaff2c7669bd7 Mon Sep 17 00:00:00 2001 From: Giaan Kler-Young Date: Tue, 17 Feb 2026 18:14:20 +0000 Subject: [PATCH 1/7] Added files for SBH17 surface barriers benchmark and updated user guide --- .../source/user_guide/benchmarks/surfaces.rst | 38 ++++ .../analysis/surfaces/sbh17/analyse_sbh17.py | 163 ++++++++++++++++++ ml_peg/analysis/surfaces/sbh17/metrics.yml | 7 + ml_peg/app/surfaces/sbh17/app_sbh17.py | 86 +++++++++ ml_peg/calcs/surfaces/sbh17/calc_sbh17.py | 79 +++++++++ 5 files changed, 373 insertions(+) create mode 100644 ml_peg/analysis/surfaces/sbh17/analyse_sbh17.py create mode 100644 ml_peg/analysis/surfaces/sbh17/metrics.yml create mode 100644 ml_peg/app/surfaces/sbh17/app_sbh17.py create mode 100644 ml_peg/calcs/surfaces/sbh17/calc_sbh17.py diff --git a/docs/source/user_guide/benchmarks/surfaces.rst b/docs/source/user_guide/benchmarks/surfaces.rst index 780118c7c..f90f3327f 100644 --- a/docs/source/user_guide/benchmarks/surfaces.rst +++ b/docs/source/user_guide/benchmarks/surfaces.rst @@ -145,3 +145,41 @@ Reference data: * S. P. Ong, W. D. Richards, A. Jain, G. Hautier, M. Kocher, S. Cholia, D. Gunter, V. Chevrier, K. A. Persson, G. Ceder, "Python Materials Genomics (pymatgen): A Robust, Open-Source Python Library for Materials Analysis," Comput. Mater. Sci., 2013, 68, 314–319. https://doi.org/10.1016/j.commatsci.2012.10.028 * Tran et al. relaxed the slabs using spin-polarized PBE calculations performed in VASP, with a cutoff energy of 400 eV. + +SBH17 +================================ + +Summary +------- + +Performance in predicting activation barriers to dissociative chemisorption +from the gas phase, for a set of 16 adsorbate-surface combinations. + +Metrics +------- + +Error in activation energy (barrier) + +For each adsorbate-surface combination, two single points are performed. +One is of the clean surface with the adsorbate in the gas phase far from the surface, +the second is of the transition state structure with the adsorbate at the surface +(minimum barrier geometry to dissociation and chemisorption). + + +Computational cost +------------------ + +Very low: tests are likely to take less than a minute to run on CPU. + +Data availability +----------------- + +Input structures: + +* Tchakoua, T., Gerrits, N., Smeets, E. W. F., & Kroes, G. J. (2022). SBH17: Benchmark database of barrier heights for dissociative chemisorption on transition metal surfaces. Journal of Chemical Theory and Computation, 19(1), 245-270. + +Reference data: + +* Taken from the SI of the publication above (as the main text of the publication discusses mixed levels of theory). Values from the "Medium algorithm" are used in order to be consistent with the structures. + +* PBE without dispersion diff --git a/ml_peg/analysis/surfaces/sbh17/analyse_sbh17.py b/ml_peg/analysis/surfaces/sbh17/analyse_sbh17.py new file mode 100644 index 000000000..8085e77ee --- /dev/null +++ b/ml_peg/analysis/surfaces/sbh17/analyse_sbh17.py @@ -0,0 +1,163 @@ +"""Analyse SBH17 benchmark.""" + +from __future__ import annotations + +from pathlib import Path + +from ase import units +from ase.io import read, write +import pytest + +from ml_peg.analysis.utils.decorators import build_table, plot_parity +from ml_peg.analysis.utils.utils import build_d3_name_map, load_metrics_config, mae +from ml_peg.app import APP_ROOT +from ml_peg.calcs import CALCS_ROOT +from ml_peg.models.get_models import get_model_names +from ml_peg.models.models import current_models + +MODELS = get_model_names(current_models) +D3_MODEL_NAMES = build_d3_name_map(MODELS) +CALC_PATH = CALCS_ROOT / "surfaces" / "sbh17" / "outputs" +OUT_PATH = APP_ROOT / "data" / "surfaces" / "sbh17" + +METRICS_CONFIG_PATH = Path(__file__).with_name("metrics.yml") +DEFAULT_THRESHOLDS, DEFAULT_TOOLTIPS, DEFAULT_WEIGHTS = load_metrics_config( + METRICS_CONFIG_PATH +) + +def get_system_names() -> list[str]: + """ + Get list of X23 system names. + + Returns + ------- + list[str] + List of system names from structure files. + """ + system_names = [] + for model_name in MODELS: + model_dir = CALC_PATH / model_name + if model_dir.exists(): + xyz_files = sorted(model_dir.glob("*.xyz")) + if xyz_files: + for xyz_file in xyz_files: + atoms = read(xyz_file) + system_names.append(atoms.info["system"]) + break + return system_names + +@pytest.fixture +@plot_parity( + filename=OUT_PATH / "figure_surface_barriers.json", + title="SBH17 dissociative chemisorption barriers", + x_label="Predicted barrier / eV", + y_label="Reference barrier / eV", + hoverdata={ + "System": get_system_names(), + }, +) +def surface_barriers() -> dict[str, list]: + """ + Get barriers for all SBH17 systems. + + Returns + ------- + dict[str, list] + Dictionary of reference and predicted surface barriers. + """ + results = {"ref": []} | {mlip: [] for mlip in MODELS} + ref_stored = False + + for model_name in MODELS: + model_dir = CALC_PATH / model_name + + if not model_dir.exists(): + continue + + xyz_files = sorted(model_dir.glob("*.xyz")) + if not xyz_files: + continue + + for xyz_file in xyz_files: + structs = read(xyz_file, index=":") + + gp_energy = structs[0].get_potential_energy() + system = structs[0].info["system"] + ts_energy = structs[1].get_potential_energy() + + barrier = ts_energy - gp_energy + results[model_name].append(barrier) + + # Copy individual structure files to app data directory + structs_dir = OUT_PATH / model_name + structs_dir.mkdir(parents=True, exist_ok=True) + write(structs_dir / f"{system}.xyz", structs) + + # Store reference energies (only once) + if not ref_stored: + results["ref"].append(structs[0].info["ref"]) + + ref_stored = True + + return results + +@pytest.fixture +def sbh17_errors(surface_barriers) -> dict[str, float]: + """ + Get mean absolute error for surface barriers. + + Parameters + ---------- + barriers + Dictionary of reference and predicted surface barriers. + + Returns + ------- + dict[str, float] + Dictionary of predicted barrier errors for all models. + """ + results = {} + for model_name in MODELS: + if surface_barriers[model_name]: + results[model_name] = mae( + surface_barriers["ref"], surface_barriers[model_name] + ) + else: + results[model_name] = None + return results + +@pytest.fixture +@build_table( + filename=OUT_PATH / "sbh17_metrics_table.json", + metric_tooltips=DEFAULT_TOOLTIPS, + thresholds=DEFAULT_THRESHOLDS, + # mlip_name_map=D3_MODEL_NAMES, +) +def metrics(sbh17_errors: dict[str, float]) -> dict[str, dict]: + """ + Get all SBH17 metrics. + + Parameters + ---------- + sbh17_errors + Mean absolute errors for all systems. + + Returns + ------- + dict[str, dict] + Metric names and values for all models. + """ + return { + "MAE": sbh17_errors, + } + +def test_sbh17(metrics: dict[str, dict]) -> None: + """ + Run SBH17 test. + + Parameters + ---------- + metrics + All SBH17 metrics. + """ + return diff --git a/ml_peg/analysis/surfaces/sbh17/metrics.yml b/ml_peg/analysis/surfaces/sbh17/metrics.yml new file mode 100644 index 000000000..1e9b1cee5 --- /dev/null +++ b/ml_peg/analysis/surfaces/sbh17/metrics.yml @@ -0,0 +1,7 @@ + metrics: + MAE: + good: 0.02 + bad: 0.5 + unit: eV + tooltip: "Mean Absolute Error" + level_of_theory: PBE diff --git a/ml_peg/app/surfaces/sbh17/app_sbh17.py b/ml_peg/app/surfaces/sbh17/app_sbh17.py new file mode 100644 index 000000000..e1d29eaa3 --- /dev/null +++ b/ml_peg/app/surfaces/sbh17/app_sbh17.py @@ -0,0 +1,86 @@ +"""Run SBH17 app.""" + +from __future__ import annotations + +from dash import Dash +from dash.html import Div + +from ml_peg.app import APP_ROOT +from ml_peg.app.base_app import BaseApp +from ml_peg.app.utils.build_callbacks import ( + plot_from_table_column, + struct_from_scatter, +) +from ml_peg.app.utils.load import read_plot +from ml_peg.models.get_models import get_model_names +from ml_peg.models.models import current_models + +# Get all models +MODELS = get_model_names(current_models) +BENCHMARK_NAME = "SBH17 chemisorption barriers" +DOCS_URL = ( + "https://ddmms.github.io/ml-peg/user_guide/benchmarks/surfaces.html#sbh17" +) +DATA_PATH = APP_ROOT / "data" / "surfaces" / "sbh17" + +class SBH17App(BaseApp): + """SBH17 benchmark app layout and callbacks.""" + + def register_callbacks(self) -> None: + """Register callbacks to app.""" + scatter = read_plot( + DATA_PATH / "figure_surface_barriers.json", + id=f"{BENCHMARK_NAME}-figure", + ) + + # Assets dir will be parent directory - individual files for each system + structs_dir = DATA_PATH / MODELS[0] + structs = [ + f"assets/surfaces/sbh17/{MODELS[0]}/{struct_file.stem}.xyz" + for struct_file in sorted(structs_dir.glob("*.xyz")) + ] + + plot_from_table_column( + table_id=self.table_id, + plot_id=f"{BENCHMARK_NAME}-figure-placeholder", + column_to_plot={"MAE": scatter}, + ) + + struct_from_scatter( + scatter_id=f"{BENCHMARK_NAME}-figure", + struct_id=f"{BENCHMARK_NAME}-struct-placeholder", + structs=structs, + mode="struct", + ) + +def get_app() -> SBH17App: + """ + Get SBH17 benchmark app layout and callback registration. + + Returns + ------- + SBH17App + Benchmark layout and callback registration. + """ + return SBH17App( + name=BENCHMARK_NAME, + description="Barriers to dissociative chemisorption for 16 combinations of adsorbates and transition metal surfaces.", + docs_url=DOCS_URL, + table_path=DATA_PATH / "sbh17_metrics_table.json", + extra_components=[ + Div(id=f"{BENCHMARK_NAME}-figure-placeholder"), + Div(id=f"{BENCHMARK_NAME}-struct-placeholder"), + ], + ) + +if __name__ == "__main__": + # Create Dash app + full_app = Dash(__name__, assets_folder=DATA_PATH.parent.parent) + + # Construct layout and register callbacks + sbh17_app = get_app() + full_app.layout = sbh17_app.layout + sbh17_app.register_callbacks() + + # Run app + full_app.run(port=8055, debug=True) diff --git a/ml_peg/calcs/surfaces/sbh17/calc_sbh17.py b/ml_peg/calcs/surfaces/sbh17/calc_sbh17.py new file mode 100644 index 000000000..81fe26e9f --- /dev/null +++ b/ml_peg/calcs/surfaces/sbh17/calc_sbh17.py @@ -0,0 +1,79 @@ +"""Run calculations for SBH17 tests.""" + +from __future__ import annotations + +from copy import copy +from pathlib import Path +from typing import Any + +from ase import units +from ase.io import read, write +import numpy as np +import pytest + +from ml_peg.calcs.utils.utils import download_s3_data +from ml_peg.models.get_models import load_models +from ml_peg.models.models import current_models + +MODELS = load_models(current_models) + +DATA_PATH = Path(__file__).parent / "data" +OUT_PATH = Path(__file__).parent / "outputs" + +@pytest.mark.parametrize("mlip", MODELS.items()) +def test_surface_barrier(mlip: tuple[str, Any]) -> None: + """ + Run SBH17 dissociative chemisorption barrier test. + + Note the data directory currently excludes one data point + from the paper, H2Cu111, because the PBE value + is not available. + + Parameters + ---------- + mlip + Name of model use and model to get calculator. + """ + model_name, model = mlip + calc = model.get_calculator() + + # Do not want D3 as references here are dispersionless PBE + # calc = model.add_d3_calculator(calc) + + # Download SBH17 dataset + # sbh17_dir = ( + # download_s3_data( + # key="inputs/surfaces/sbh17/sbh17.zip", + # filename="sbh17.zip", + # ) + # / "sbh17" + # ) + sbh17_dir=Path("/home/gk504/localCodeFolder/proj-other/ml-peg-contrib/sbh17") + + with open(sbh17_dir / "list") as f: + systems = f.read().splitlines() + + for system in systems: + gp_path = sbh17_dir / system / "POSCAR-gp" + ts_path = sbh17_dir / system / "POSCAR-ts" + ref_path = sbh17_dir / system / "barrier_pbe" + + gp = read(gp_path, index=0, format="vasp") + gp.calc = calc + gp.get_potential_energy() + + ts = read(ts_path, index=0, format="vasp") + ts.calc = copy(calc) + ts.get_potential_energy() + + ref = np.loadtxt(ref_path) + + gp.info["ref"] = ref + gp.info["system"] = system + ts.info["ref"] = ref + ts.info["system"] = system + + # Write output structures + write_dir = OUT_PATH / model_name + write_dir.mkdir(parents=True, exist_ok=True) + write(write_dir / f"{system}.xyz", [gp, ts]) From 58fccb0c3697c92daae08309baca1f9bd5cd4389 Mon Sep 17 00:00:00 2001 From: Giaan Kler-Young <148775996+gkleryoung@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:28:36 +0000 Subject: [PATCH 2/7] Update ml_peg/analysis/surfaces/sbh17/analyse_sbh17.py Co-authored-by: Elliott Kasoar <45317199+ElliottKasoar@users.noreply.github.com> --- ml_peg/analysis/surfaces/sbh17/analyse_sbh17.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ml_peg/analysis/surfaces/sbh17/analyse_sbh17.py b/ml_peg/analysis/surfaces/sbh17/analyse_sbh17.py index 8085e77ee..7a6627dca 100644 --- a/ml_peg/analysis/surfaces/sbh17/analyse_sbh17.py +++ b/ml_peg/analysis/surfaces/sbh17/analyse_sbh17.py @@ -27,7 +27,7 @@ def get_system_names() -> list[str]: """ - Get list of X23 system names. + Get list of SBH17 system names. Returns ------- From 36511edd43032fb66ff8b86b0619a4064c1d0cb1 Mon Sep 17 00:00:00 2001 From: Giaan Kler-Young <148775996+gkleryoung@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:29:02 +0000 Subject: [PATCH 3/7] Update ml_peg/calcs/surfaces/sbh17/calc_sbh17.py Co-authored-by: Elliott Kasoar <45317199+ElliottKasoar@users.noreply.github.com> --- ml_peg/calcs/surfaces/sbh17/calc_sbh17.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/ml_peg/calcs/surfaces/sbh17/calc_sbh17.py b/ml_peg/calcs/surfaces/sbh17/calc_sbh17.py index 81fe26e9f..46d94767a 100644 --- a/ml_peg/calcs/surfaces/sbh17/calc_sbh17.py +++ b/ml_peg/calcs/surfaces/sbh17/calc_sbh17.py @@ -35,20 +35,17 @@ def test_surface_barrier(mlip: tuple[str, Any]) -> None: Name of model use and model to get calculator. """ model_name, model = mlip - calc = model.get_calculator() - # Do not want D3 as references here are dispersionless PBE - # calc = model.add_d3_calculator(calc) + calc = model.get_calculator() # Download SBH17 dataset - # sbh17_dir = ( - # download_s3_data( - # key="inputs/surfaces/sbh17/sbh17.zip", - # filename="sbh17.zip", - # ) - # / "sbh17" - # ) - sbh17_dir=Path("/home/gk504/localCodeFolder/proj-other/ml-peg-contrib/sbh17") + sbh17_dir = ( + download_s3_data( + key="inputs/surfaces/SBH17/SBH17.zip", + filename="sbh17.zip", + ) + / "sbh17" + ) with open(sbh17_dir / "list") as f: systems = f.read().splitlines() From 3f166d84fdd64b3287717945f6b48af595b84368 Mon Sep 17 00:00:00 2001 From: Giaan Kler-Young <148775996+gkleryoung@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:29:52 +0000 Subject: [PATCH 4/7] Update docs/source/user_guide/benchmarks/surfaces.rst Co-authored-by: Elliott Kasoar <45317199+ElliottKasoar@users.noreply.github.com> --- docs/source/user_guide/benchmarks/surfaces.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/user_guide/benchmarks/surfaces.rst b/docs/source/user_guide/benchmarks/surfaces.rst index f90f3327f..330e63da5 100644 --- a/docs/source/user_guide/benchmarks/surfaces.rst +++ b/docs/source/user_guide/benchmarks/surfaces.rst @@ -146,8 +146,9 @@ Reference data: * Tran et al. relaxed the slabs using spin-polarized PBE calculations performed in VASP, with a cutoff energy of 400 eV. + SBH17 -================================ +===== Summary ------- From eea893962bb884facfe86f84e4ed01fb48fe21fb Mon Sep 17 00:00:00 2001 From: Giaan Kler-Young Date: Thu, 19 Feb 2026 14:09:13 +0000 Subject: [PATCH 5/7] Capitalised SBH17 everywhere, added tqdm --- .../analyse_sbh17.py => SBH17/analyse_SBH17.py} | 16 ++++++++-------- .../surfaces/{sbh17 => SBH17}/metrics.yml | 0 .../{sbh17/app_sbh17.py => SBH17/app_SBH17.py} | 14 +++++++------- .../calc_sbh17.py => SBH17/calc_SBH17.py} | 17 +++++++++-------- 4 files changed, 24 insertions(+), 23 deletions(-) rename ml_peg/analysis/surfaces/{sbh17/analyse_sbh17.py => SBH17/analyse_SBH17.py} (91%) rename ml_peg/analysis/surfaces/{sbh17 => SBH17}/metrics.yml (100%) rename ml_peg/app/surfaces/{sbh17/app_sbh17.py => SBH17/app_SBH17.py} (88%) rename ml_peg/calcs/surfaces/{sbh17/calc_sbh17.py => SBH17/calc_SBH17.py} (82%) diff --git a/ml_peg/analysis/surfaces/sbh17/analyse_sbh17.py b/ml_peg/analysis/surfaces/SBH17/analyse_SBH17.py similarity index 91% rename from ml_peg/analysis/surfaces/sbh17/analyse_sbh17.py rename to ml_peg/analysis/surfaces/SBH17/analyse_SBH17.py index 7a6627dca..96e7dc430 100644 --- a/ml_peg/analysis/surfaces/sbh17/analyse_sbh17.py +++ b/ml_peg/analysis/surfaces/SBH17/analyse_SBH17.py @@ -17,8 +17,8 @@ MODELS = get_model_names(current_models) D3_MODEL_NAMES = build_d3_name_map(MODELS) -CALC_PATH = CALCS_ROOT / "surfaces" / "sbh17" / "outputs" -OUT_PATH = APP_ROOT / "data" / "surfaces" / "sbh17" +CALC_PATH = CALCS_ROOT / "surfaces" / "SBH17" / "outputs" +OUT_PATH = APP_ROOT / "data" / "surfaces" / "SBH17" METRICS_CONFIG_PATH = Path(__file__).with_name("metrics.yml") DEFAULT_THRESHOLDS, DEFAULT_TOOLTIPS, DEFAULT_WEIGHTS = load_metrics_config( @@ -102,7 +102,7 @@ def surface_barriers() -> dict[str, list]: return results @pytest.fixture -def sbh17_errors(surface_barriers) -> dict[str, float]: +def SBH17_errors(surface_barriers) -> dict[str, float]: """ Get mean absolute error for surface barriers. @@ -128,18 +128,18 @@ def sbh17_errors(surface_barriers) -> dict[str, float]: @pytest.fixture @build_table( - filename=OUT_PATH / "sbh17_metrics_table.json", + filename=OUT_PATH / "SBH17_metrics_table.json", metric_tooltips=DEFAULT_TOOLTIPS, thresholds=DEFAULT_THRESHOLDS, # mlip_name_map=D3_MODEL_NAMES, ) -def metrics(sbh17_errors: dict[str, float]) -> dict[str, dict]: +def metrics(SBH17_errors: dict[str, float]) -> dict[str, dict]: """ Get all SBH17 metrics. Parameters ---------- - sbh17_errors + SBH17_errors Mean absolute errors for all systems. Returns @@ -148,10 +148,10 @@ def metrics(sbh17_errors: dict[str, float]) -> dict[str, dict]: Metric names and values for all models. """ return { - "MAE": sbh17_errors, + "MAE": SBH17_errors, } -def test_sbh17(metrics: dict[str, dict]) -> None: +def test_SBH17(metrics: dict[str, dict]) -> None: """ Run SBH17 test. diff --git a/ml_peg/analysis/surfaces/sbh17/metrics.yml b/ml_peg/analysis/surfaces/SBH17/metrics.yml similarity index 100% rename from ml_peg/analysis/surfaces/sbh17/metrics.yml rename to ml_peg/analysis/surfaces/SBH17/metrics.yml diff --git a/ml_peg/app/surfaces/sbh17/app_sbh17.py b/ml_peg/app/surfaces/SBH17/app_SBH17.py similarity index 88% rename from ml_peg/app/surfaces/sbh17/app_sbh17.py rename to ml_peg/app/surfaces/SBH17/app_SBH17.py index e1d29eaa3..ccbffd298 100644 --- a/ml_peg/app/surfaces/sbh17/app_sbh17.py +++ b/ml_peg/app/surfaces/SBH17/app_SBH17.py @@ -19,9 +19,9 @@ MODELS = get_model_names(current_models) BENCHMARK_NAME = "SBH17 chemisorption barriers" DOCS_URL = ( - "https://ddmms.github.io/ml-peg/user_guide/benchmarks/surfaces.html#sbh17" + "https://ddmms.github.io/ml-peg/user_guide/benchmarks/surfaces.html#SBH17" ) -DATA_PATH = APP_ROOT / "data" / "surfaces" / "sbh17" +DATA_PATH = APP_ROOT / "data" / "surfaces" / "SBH17" class SBH17App(BaseApp): """SBH17 benchmark app layout and callbacks.""" @@ -36,7 +36,7 @@ def register_callbacks(self) -> None: # Assets dir will be parent directory - individual files for each system structs_dir = DATA_PATH / MODELS[0] structs = [ - f"assets/surfaces/sbh17/{MODELS[0]}/{struct_file.stem}.xyz" + f"assets/surfaces/SBH17/{MODELS[0]}/{struct_file.stem}.xyz" for struct_file in sorted(structs_dir.glob("*.xyz")) ] @@ -66,7 +66,7 @@ def get_app() -> SBH17App: name=BENCHMARK_NAME, description="Barriers to dissociative chemisorption for 16 combinations of adsorbates and transition metal surfaces.", docs_url=DOCS_URL, - table_path=DATA_PATH / "sbh17_metrics_table.json", + table_path=DATA_PATH / "SBH17_metrics_table.json", extra_components=[ Div(id=f"{BENCHMARK_NAME}-figure-placeholder"), Div(id=f"{BENCHMARK_NAME}-struct-placeholder"), @@ -78,9 +78,9 @@ def get_app() -> SBH17App: full_app = Dash(__name__, assets_folder=DATA_PATH.parent.parent) # Construct layout and register callbacks - sbh17_app = get_app() - full_app.layout = sbh17_app.layout - sbh17_app.register_callbacks() + SBH17_app = get_app() + full_app.layout = SBH17_app.layout + SBH17_app.register_callbacks() # Run app full_app.run(port=8055, debug=True) diff --git a/ml_peg/calcs/surfaces/sbh17/calc_sbh17.py b/ml_peg/calcs/surfaces/SBH17/calc_SBH17.py similarity index 82% rename from ml_peg/calcs/surfaces/sbh17/calc_sbh17.py rename to ml_peg/calcs/surfaces/SBH17/calc_SBH17.py index 46d94767a..f1ef4ed0c 100644 --- a/ml_peg/calcs/surfaces/sbh17/calc_sbh17.py +++ b/ml_peg/calcs/surfaces/SBH17/calc_SBH17.py @@ -10,6 +10,7 @@ from ase.io import read, write import numpy as np import pytest +from tqdm import tqdm from ml_peg.calcs.utils.utils import download_s3_data from ml_peg.models.get_models import load_models @@ -39,21 +40,21 @@ def test_surface_barrier(mlip: tuple[str, Any]) -> None: calc = model.get_calculator() # Download SBH17 dataset - sbh17_dir = ( + SBH17_dir = ( download_s3_data( key="inputs/surfaces/SBH17/SBH17.zip", - filename="sbh17.zip", + filename="SBH17.zip", ) - / "sbh17" + / "SBH17" ) - with open(sbh17_dir / "list") as f: + with open(SBH17_dir / "list") as f: systems = f.read().splitlines() - for system in systems: - gp_path = sbh17_dir / system / "POSCAR-gp" - ts_path = sbh17_dir / system / "POSCAR-ts" - ref_path = sbh17_dir / system / "barrier_pbe" + for system in tqdm(systems, desc="Evaluating models on SBH17 structures"): + gp_path = SBH17_dir / system / "POSCAR-gp" + ts_path = SBH17_dir / system / "POSCAR-ts" + ref_path = SBH17_dir / system / "barrier_pbe" gp = read(gp_path, index=0, format="vasp") gp.calc = calc From 2f0d0ae88f4192aded27d7ebbda315c9c8669db7 Mon Sep 17 00:00:00 2001 From: Giaan Kler-Young Date: Thu, 19 Feb 2026 15:07:00 +0000 Subject: [PATCH 6/7] Fixes to adhere with coding style --- .../analysis/surfaces/SBH17/analyse_SBH17.py | 18 +++++++++++------- ml_peg/app/surfaces/SBH17/app_SBH17.py | 10 ++++++---- ml_peg/calcs/surfaces/SBH17/calc_SBH17.py | 12 ++++++------ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/ml_peg/analysis/surfaces/SBH17/analyse_SBH17.py b/ml_peg/analysis/surfaces/SBH17/analyse_SBH17.py index 96e7dc430..6e00245b4 100644 --- a/ml_peg/analysis/surfaces/SBH17/analyse_SBH17.py +++ b/ml_peg/analysis/surfaces/SBH17/analyse_SBH17.py @@ -4,7 +4,6 @@ from pathlib import Path -from ase import units from ase.io import read, write import pytest @@ -25,6 +24,7 @@ METRICS_CONFIG_PATH ) + def get_system_names() -> list[str]: """ Get list of SBH17 system names. @@ -46,6 +46,7 @@ def get_system_names() -> list[str]: break return system_names + @pytest.fixture @plot_parity( filename=OUT_PATH / "figure_surface_barriers.json", @@ -101,14 +102,15 @@ def surface_barriers() -> dict[str, list]: return results + @pytest.fixture -def SBH17_errors(surface_barriers) -> dict[str, float]: +def sbh17_errors(surface_barriers) -> dict[str, float]: """ Get mean absolute error for surface barriers. Parameters ---------- - barriers + surface_barriers Dictionary of reference and predicted surface barriers. Returns @@ -126,6 +128,7 @@ def SBH17_errors(surface_barriers) -> dict[str, float]: results[model_name] = None return results + @pytest.fixture @build_table( filename=OUT_PATH / "SBH17_metrics_table.json", @@ -133,13 +136,13 @@ def SBH17_errors(surface_barriers) -> dict[str, float]: thresholds=DEFAULT_THRESHOLDS, # mlip_name_map=D3_MODEL_NAMES, ) -def metrics(SBH17_errors: dict[str, float]) -> dict[str, dict]: +def metrics(sbh17_errors: dict[str, float]) -> dict[str, dict]: """ Get all SBH17 metrics. Parameters ---------- - SBH17_errors + sbh17_errors Mean absolute errors for all systems. Returns @@ -148,10 +151,11 @@ def metrics(SBH17_errors: dict[str, float]) -> dict[str, dict]: Metric names and values for all models. """ return { - "MAE": SBH17_errors, + "MAE": sbh17_errors, } -def test_SBH17(metrics: dict[str, dict]) -> None: + +def test_sbh17(metrics: dict[str, dict]) -> None: """ Run SBH17 test. diff --git a/ml_peg/app/surfaces/SBH17/app_SBH17.py b/ml_peg/app/surfaces/SBH17/app_SBH17.py index ccbffd298..6a5c2212f 100644 --- a/ml_peg/app/surfaces/SBH17/app_SBH17.py +++ b/ml_peg/app/surfaces/SBH17/app_SBH17.py @@ -18,11 +18,10 @@ # Get all models MODELS = get_model_names(current_models) BENCHMARK_NAME = "SBH17 chemisorption barriers" -DOCS_URL = ( - "https://ddmms.github.io/ml-peg/user_guide/benchmarks/surfaces.html#SBH17" -) +DOCS_URL = "https://ddmms.github.io/ml-peg/user_guide/benchmarks/surfaces.html#SBH17" DATA_PATH = APP_ROOT / "data" / "surfaces" / "SBH17" + class SBH17App(BaseApp): """SBH17 benchmark app layout and callbacks.""" @@ -53,6 +52,7 @@ def register_callbacks(self) -> None: mode="struct", ) + def get_app() -> SBH17App: """ Get SBH17 benchmark app layout and callback registration. @@ -64,7 +64,8 @@ def get_app() -> SBH17App: """ return SBH17App( name=BENCHMARK_NAME, - description="Barriers to dissociative chemisorption for 16 combinations of adsorbates and transition metal surfaces.", + description="Barriers to dissociative chemisorption for 16 \ + combinations of adsorbates and transition metal surfaces.", docs_url=DOCS_URL, table_path=DATA_PATH / "SBH17_metrics_table.json", extra_components=[ @@ -73,6 +74,7 @@ def get_app() -> SBH17App: ], ) + if __name__ == "__main__": # Create Dash app full_app = Dash(__name__, assets_folder=DATA_PATH.parent.parent) diff --git a/ml_peg/calcs/surfaces/SBH17/calc_SBH17.py b/ml_peg/calcs/surfaces/SBH17/calc_SBH17.py index f1ef4ed0c..5cd2206b2 100644 --- a/ml_peg/calcs/surfaces/SBH17/calc_SBH17.py +++ b/ml_peg/calcs/surfaces/SBH17/calc_SBH17.py @@ -6,7 +6,6 @@ from pathlib import Path from typing import Any -from ase import units from ase.io import read, write import numpy as np import pytest @@ -21,6 +20,7 @@ DATA_PATH = Path(__file__).parent / "data" OUT_PATH = Path(__file__).parent / "outputs" + @pytest.mark.parametrize("mlip", MODELS.items()) def test_surface_barrier(mlip: tuple[str, Any]) -> None: """ @@ -40,7 +40,7 @@ def test_surface_barrier(mlip: tuple[str, Any]) -> None: calc = model.get_calculator() # Download SBH17 dataset - SBH17_dir = ( + sbh17_dir = ( download_s3_data( key="inputs/surfaces/SBH17/SBH17.zip", filename="SBH17.zip", @@ -48,13 +48,13 @@ def test_surface_barrier(mlip: tuple[str, Any]) -> None: / "SBH17" ) - with open(SBH17_dir / "list") as f: + with open(sbh17_dir / "list") as f: systems = f.read().splitlines() for system in tqdm(systems, desc="Evaluating models on SBH17 structures"): - gp_path = SBH17_dir / system / "POSCAR-gp" - ts_path = SBH17_dir / system / "POSCAR-ts" - ref_path = SBH17_dir / system / "barrier_pbe" + gp_path = sbh17_dir / system / "POSCAR-gp" + ts_path = sbh17_dir / system / "POSCAR-ts" + ref_path = sbh17_dir / system / "barrier_pbe" gp = read(gp_path, index=0, format="vasp") gp.calc = calc From bb8d6f0e2df934c73101a9bb1a4f785d7788a53c Mon Sep 17 00:00:00 2001 From: Giaan Kler-Young <148775996+gkleryoung@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:20:21 +0000 Subject: [PATCH 7/7] Update ml_peg/app/surfaces/SBH17/app_SBH17.py Co-authored-by: Elliott Kasoar <45317199+ElliottKasoar@users.noreply.github.com> --- ml_peg/app/surfaces/SBH17/app_SBH17.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ml_peg/app/surfaces/SBH17/app_SBH17.py b/ml_peg/app/surfaces/SBH17/app_SBH17.py index 6a5c2212f..5d13659a1 100644 --- a/ml_peg/app/surfaces/SBH17/app_SBH17.py +++ b/ml_peg/app/surfaces/SBH17/app_SBH17.py @@ -18,7 +18,7 @@ # Get all models MODELS = get_model_names(current_models) BENCHMARK_NAME = "SBH17 chemisorption barriers" -DOCS_URL = "https://ddmms.github.io/ml-peg/user_guide/benchmarks/surfaces.html#SBH17" +DOCS_URL = "https://ddmms.github.io/ml-peg/user_guide/benchmarks/surfaces.html#sbh17" DATA_PATH = APP_ROOT / "data" / "surfaces" / "SBH17"