-
Notifications
You must be signed in to change notification settings - Fork 40
Adding cleavage benchmarks #404
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
vue1999
wants to merge
1
commit into
ddmms:main
Choose a base branch
from
vue1999:cleavage_benchmark
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+448
−0
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
238 changes: 238 additions & 0 deletions
238
ml_peg/analysis/surfaces/cleavage_energy/analyse_cleavage_energy.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,238 @@ | ||
| """Analyse cleavage energy benchmark.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import json | ||
| from pathlib import Path | ||
|
|
||
| import numpy as np | ||
| import pytest | ||
|
|
||
| from ml_peg.analysis.utils.decorators import build_table, plot_parity | ||
| from ml_peg.analysis.utils.utils import 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) | ||
| CALC_PATH = CALCS_ROOT / "surfaces" / "cleavage_energy" / "outputs" | ||
| OUT_PATH = APP_ROOT / "data" / "surfaces" / "cleavage_energy" | ||
|
|
||
| METRICS_CONFIG_PATH = Path(__file__).with_name("metrics.yml") | ||
| DEFAULT_THRESHOLDS, DEFAULT_TOOLTIPS, DEFAULT_WEIGHTS = load_metrics_config( | ||
| METRICS_CONFIG_PATH | ||
| ) | ||
|
|
||
| EV_TO_MEV = 1000.0 | ||
|
|
||
|
|
||
| def _load_model_results(model_name: str) -> dict | None: | ||
| """ | ||
| Load the JSON energy results for a model, or None if absent. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| model_name | ||
| Name of the model whose results file to load. | ||
|
|
||
| Returns | ||
| ------- | ||
| dict | None | ||
| Parsed JSON results dictionary, or None if the file does not exist. | ||
| """ | ||
| result_file = CALC_PATH / f"{model_name}.json" | ||
| if not result_file.exists(): | ||
| return None | ||
| with open(result_file, encoding="utf-8") as f: | ||
| return json.load(f) | ||
|
|
||
|
|
||
| def system_names() -> list[str]: | ||
| """ | ||
| Get list of system identifiers from calc outputs. | ||
|
|
||
| Returns | ||
| ------- | ||
| list[str] | ||
| Sorted list of unique_id values from the first model with results. | ||
| """ | ||
| for model_name in MODELS: | ||
| data = _load_model_results(model_name) | ||
| if data is not None: | ||
| return sorted(data.keys()) | ||
| return [] | ||
|
|
||
|
|
||
| def compute_cleavage_energy( | ||
| slab_energy: float, | ||
| bulk_energy: float, | ||
| thickness_ratio: float, | ||
| area_slab: float, | ||
| ) -> float: | ||
| """ | ||
| Compute cleavage energy from slab and bulk energies. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| slab_energy | ||
| Total energy of the slab. | ||
| bulk_energy | ||
| Total energy of the lattice-matched bulk unit cell. | ||
| thickness_ratio | ||
| Number of bulk unit cells in the slab thickness. | ||
| area_slab | ||
| Surface area of the slab in Angstrom^2. | ||
|
|
||
| Returns | ||
| ------- | ||
| float | ||
| Cleavage energy in eV/Angstrom^2. | ||
| """ | ||
| return (slab_energy - thickness_ratio * bulk_energy) / (2 * area_slab) | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| @plot_parity( | ||
| filename=OUT_PATH / "figure_cleavage_energies.json", | ||
| title="Cleavage Energies", | ||
| x_label="Predicted cleavage energy / meV/\u00c5\u00b2", | ||
| y_label="Reference cleavage energy / meV/\u00c5\u00b2", | ||
| hoverdata={ | ||
| "System": system_names(), | ||
| }, | ||
| ) | ||
| def cleavage_energies() -> dict[str, list]: | ||
| """ | ||
| Get cleavage energies for all systems in meV/A^2. | ||
|
|
||
| Also saves per-system errors for the distribution plot. | ||
|
|
||
| Returns | ||
| ------- | ||
| dict[str, list] | ||
| Dictionary of reference and predicted cleavage energies in meV/A^2. | ||
| """ | ||
| results = {"ref": []} | {mlip: [] for mlip in MODELS} | ||
| canonical_ids = None | ||
|
|
||
| for model_name in MODELS: | ||
| data = _load_model_results(model_name) | ||
| if data is None: | ||
| continue | ||
|
|
||
| if canonical_ids is None: | ||
| canonical_ids = sorted(data.keys()) | ||
| for uid in canonical_ids: | ||
| entry = data[uid] | ||
| results["ref"].append(entry["ref_cleavage_energy"] * EV_TO_MEV) | ||
|
|
||
| for uid in canonical_ids: | ||
| entry = data[uid] | ||
| pred_ce = ( | ||
| compute_cleavage_energy( | ||
| entry["slab_energy"], | ||
| entry["bulk_energy"], | ||
| entry["thickness_ratio"], | ||
| entry["area_slab"], | ||
| ) | ||
| * EV_TO_MEV | ||
| ) | ||
| results[model_name].append(pred_ce) | ||
|
|
||
| return results | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def cleavage_mae(cleavage_energies: dict[str, list]) -> dict[str, float]: | ||
| """ | ||
| Get mean absolute error for cleavage energies. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| cleavage_energies | ||
| Dictionary of reference and predicted cleavage energies. | ||
|
|
||
| Returns | ||
| ------- | ||
| dict[str, float] | ||
| MAE for each model. | ||
| """ | ||
| results = {} | ||
| for model_name in MODELS: | ||
| if cleavage_energies[model_name]: | ||
| results[model_name] = mae( | ||
| cleavage_energies["ref"], cleavage_energies[model_name] | ||
| ) | ||
| else: | ||
| results[model_name] = None | ||
| return results | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def cleavage_rmse(cleavage_energies: dict[str, list]) -> dict[str, float]: | ||
| """ | ||
| Get root mean squared error for cleavage energies. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| cleavage_energies | ||
| Dictionary of reference and predicted cleavage energies. | ||
|
|
||
| Returns | ||
| ------- | ||
| dict[str, float] | ||
| RMSE for each model. | ||
| """ | ||
| results = {} | ||
| for model_name in MODELS: | ||
| if cleavage_energies[model_name]: | ||
| ref = np.array(cleavage_energies["ref"]) | ||
| pred = np.array(cleavage_energies[model_name]) | ||
| results[model_name] = float(np.sqrt(np.mean((pred - ref) ** 2))) | ||
| else: | ||
| results[model_name] = None | ||
| return results | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| @build_table( | ||
| filename=OUT_PATH / "cleavage_energy_metrics_table.json", | ||
| metric_tooltips=DEFAULT_TOOLTIPS, | ||
| thresholds=DEFAULT_THRESHOLDS, | ||
| ) | ||
| def metrics( | ||
| cleavage_mae: dict[str, float], | ||
| cleavage_rmse: dict[str, float], | ||
| ) -> dict[str, dict]: | ||
| """ | ||
| Get all cleavage energy metrics. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| cleavage_mae | ||
| Mean absolute errors for all models. | ||
| cleavage_rmse | ||
| Root mean squared errors for all models. | ||
|
|
||
| Returns | ||
| ------- | ||
| dict[str, dict] | ||
| Metric names and values for all models. | ||
| """ | ||
| return { | ||
| "MAE": cleavage_mae, | ||
| "RMSE": cleavage_rmse, | ||
| } | ||
|
|
||
|
|
||
| def test_cleavage_energy(metrics: dict[str, dict]) -> None: | ||
| """ | ||
| Run cleavage energy analysis. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| metrics | ||
| All cleavage energy metrics. | ||
| """ | ||
| return |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| metrics: | ||
| MAE: | ||
| good: 0.0 | ||
| bad: 10.0 | ||
| unit: meV/A^2 | ||
| tooltip: Mean Absolute Error of cleavage energies | ||
| level_of_theory: PBE | ||
| RMSE: | ||
| good: 0.0 | ||
| bad: 10.0 | ||
| unit: meV/A^2 | ||
| tooltip: Root Mean Squared Error of cleavage energies | ||
| level_of_theory: PBE |
68 changes: 68 additions & 0 deletions
68
ml_peg/app/surfaces/cleavage_energy/app_cleavage_energy.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| """Run cleavage energy 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.load import read_plot | ||
| from ml_peg.models.get_models import get_model_names | ||
| from ml_peg.models.models import current_models | ||
|
|
||
| MODELS = get_model_names(current_models) | ||
| BENCHMARK_NAME = "Cleavage Energy" | ||
| DOCS_URL = ( | ||
| "https://ddmms.github.io/ml-peg/user_guide/benchmarks/surfaces.html#cleavage-energy" | ||
| ) | ||
| DATA_PATH = APP_ROOT / "data" / "surfaces" / "cleavage_energy" | ||
|
|
||
|
|
||
| class CleavageEnergyApp(BaseApp): | ||
| """Cleavage energy benchmark app layout and callbacks.""" | ||
|
|
||
| def register_callbacks(self) -> None: | ||
| """No interactive callbacks needed.""" | ||
|
|
||
|
|
||
| def get_app() -> CleavageEnergyApp: | ||
| """ | ||
| Get cleavage energy benchmark app layout and callback registration. | ||
|
|
||
| Returns | ||
| ------- | ||
| CleavageEnergyApp | ||
| Benchmark layout and callback registration. | ||
| """ | ||
| scatter = read_plot( | ||
| DATA_PATH / "figure_cleavage_energies.json", | ||
| id=f"{BENCHMARK_NAME}-figure", | ||
| ) | ||
|
|
||
| return CleavageEnergyApp( | ||
| name=BENCHMARK_NAME, | ||
| description=( | ||
| "Performance in predicting cleavage energies for " | ||
| "36,718 surface configurations." | ||
| ), | ||
| docs_url=DOCS_URL, | ||
| table_path=DATA_PATH / "cleavage_energy_metrics_table.json", | ||
| extra_components=[ | ||
| Div(scatter, style={"marginTop": "20px"}), | ||
| ], | ||
| ) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| full_app = Dash( | ||
| __name__, | ||
| assets_folder=DATA_PATH.parent.parent, | ||
| suppress_callback_exceptions=True, | ||
| ) | ||
|
|
||
| cleavage_energy_app = get_app() | ||
| full_app.layout = cleavage_energy_app.layout | ||
| cleavage_energy_app.register_callbacks() | ||
|
|
||
| full_app.run(port=8056, debug=True) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be good to give a rough indication of time e.g. hours on gpu and minutes on gpu?