From 55af3512f529ba3030d1c68ac959838610b01522 Mon Sep 17 00:00:00 2001 From: janosh Date: Wed, 22 Jul 2026 07:52:00 +0200 Subject: [PATCH 1/7] Add Matbench diatomic evaluation metrics Extend the existing diatomics benchmark with reference-free and PBE-relative metrics while preserving legacy scoring behavior. --- .../user_guide/benchmarks/physicality.rst | 35 +- .../diatomics/analyse_diatomics.py | 201 ++++----- .../physicality/diatomics/data/README.md | 10 + .../diatomics/data/diatomics-dft.json.gz | Bin 0 -> 203913 bytes .../physicality/diatomics/metrics/__init__.py | 398 ++++++++++++++++++ .../physicality/diatomics/metrics/energy.py | 390 +++++++++++++++++ .../physicality/diatomics/metrics/force.py | 66 +++ .../physicality/diatomics/metrics/schema.py | 252 +++++++++++ tests/metrics/diatomics/test_analysis.py | 129 ++++++ tests/metrics/diatomics/test_energy_force.py | 238 +++++++++++ tests/metrics/diatomics/test_metrics.py | 263 ++++++++++++ tests/metrics/diatomics/test_schema.py | 213 ++++++++++ 12 files changed, 2097 insertions(+), 98 deletions(-) create mode 100644 ml_peg/analysis/physicality/diatomics/data/README.md create mode 100644 ml_peg/analysis/physicality/diatomics/data/diatomics-dft.json.gz create mode 100644 ml_peg/analysis/physicality/diatomics/metrics/__init__.py create mode 100644 ml_peg/analysis/physicality/diatomics/metrics/energy.py create mode 100644 ml_peg/analysis/physicality/diatomics/metrics/force.py create mode 100644 ml_peg/analysis/physicality/diatomics/metrics/schema.py create mode 100644 tests/metrics/diatomics/test_analysis.py create mode 100644 tests/metrics/diatomics/test_energy_force.py create mode 100644 tests/metrics/diatomics/test_metrics.py create mode 100644 tests/metrics/diatomics/test_schema.py diff --git a/docs/source/user_guide/benchmarks/physicality.rst b/docs/source/user_guide/benchmarks/physicality.rst index 8bbca2320..5db0f1fed 100644 --- a/docs/source/user_guide/benchmarks/physicality.rst +++ b/docs/source/user_guide/benchmarks/physicality.rst @@ -126,6 +126,38 @@ Metrics positive correlation, so a value of +1, indicating that as atoms get closer together, the energy increases. +Matbench Discovery metrics +-------------------------- + +A separate result reports the 12 Matbench Discovery diatomic metrics for +homonuclear curves. These do not affect the existing five-metric ml-peg score for +homo- and heteronuclear pairs. + +The six reference-free metrics are tortuosity, force flips, energy-difference flips, +energy jump, force total variation, and force jump. The six PBE-relative metrics are +energy and force MAE, repulsive-wall distance MAE, bond-length error, well-depth +error, and vibrational-frequency error. Element-specific distance windows prevent +the repulsive wall from dominating general metrics, while the dedicated wall metric +uses the wider PBE-supported range. + +The ml-peg CSV adapter maps projected forces to an x-aligned pair: +``-force_parallel`` on atom 0 and ``+force_parallel`` on atom 1. Scoring covers +homonuclear elements H-U except Po, At, Rn, Fr, and Ra. Eight lanthanide PBE curves +with known discontinuities retain reference-free metrics but are excluded from +PBE-relative metrics. Curves with non-finite values in the wider wall window are +skipped. + +These metrics are opt-in and excluded from the weighted table. To evaluate model +CSVs and write JSON: + +.. code-block:: python + + from ml_peg.analysis.physicality.diatomics.analyse_diatomics import ( + write_mbd_diatomic_metrics, + ) + + write_mbd_diatomic_metrics("mbd_diatomics_metrics.json") + Computational cost ------------------ @@ -134,7 +166,8 @@ High: Expected to take hours to run on GPU, or around one day for slower MLIPs. Data availability ----------------- -None required; diatomics are generated in ASE. +Predicted diatomics are generated in ASE. The optional PBE-relative metric family +uses the bundled Matbench Discovery DFT reference curves. Oxidation States diff --git a/ml_peg/analysis/physicality/diatomics/analyse_diatomics.py b/ml_peg/analysis/physicality/diatomics/analyse_diatomics.py index 649de320e..c9beb0aff 100644 --- a/ml_peg/analysis/physicality/diatomics/analyse_diatomics.py +++ b/ml_peg/analysis/physicality/diatomics/analyse_diatomics.py @@ -10,6 +10,14 @@ import pytest from scipy.signal import find_peaks +from ml_peg.analysis.physicality.diatomics.metrics import ( + DEFAULT_DFT_REFERENCE_PATH, + DIATOMIC_METRIC_NAMES, + aggregate_finite_means, + calc_diatomic_metrics, + load_dft_reference_curves, + load_ml_peg_curves, +) from ml_peg.analysis.utils.decorators import build_table, periodic_curve_gallery from ml_peg.analysis.utils.utils import load_metrics_config from ml_peg.app import APP_ROOT @@ -235,6 +243,81 @@ def _load_pair_data() -> dict[str, pd.DataFrame]: return pair_data +def _json_safe_mbd_metrics( + metrics_by_element: dict[str, dict[str, float]], +) -> dict[str, dict[str, float | None]]: + """Convert non-finite MBD metrics to strict-JSON null values.""" + return { + element_symbol: { + metric_name: (float(metric_value) if np.isfinite(metric_value) else None) + for metric_name, metric_value in element_metrics.items() + } + for element_symbol, element_metrics in metrics_by_element.items() + } + + +def evaluate_mbd_diatomic_metrics( + pair_data: dict[str, pd.DataFrame] | None = None, + *, + reference_path: str | Path | None = None, + interpolate: bool | int = 200, +) -> dict[str, object]: + """Evaluate 12 homonuclear MBD metrics outside the legacy weighted score.""" + resolved_reference_path = Path(reference_path or DEFAULT_DFT_REFERENCE_PATH) + reference_curves = load_dft_reference_curves( + functional="PBE", + ref_path=resolved_reference_path, + ) + model_data = pair_data if pair_data is not None else _load_pair_data() + model_results: dict[str, dict[str, object]] = {} + for model_name, model_dataframe in model_data.items(): + predicted_curves = load_ml_peg_curves( + model_dataframe, include_heteronuclear=False + ) + metrics_by_element = calc_diatomic_metrics( + reference_curves, + predicted_curves, + interpolate=interpolate, + ) + model_results[model_name] = { + "means": aggregate_finite_means(metrics_by_element), + "elements": _json_safe_mbd_metrics(metrics_by_element), + } + + return { + "schema_version": 1, + "curve_scope": "homonuclear", + "weighted_in_legacy_score": False, + "reference": { + "functional": "PBE", + "file": resolved_reference_path.name, + }, + "interpolate": interpolate, + "metric_names": list(DIATOMIC_METRIC_NAMES), + "models": model_results, + } + + +def write_mbd_diatomic_metrics( + output_path: str | Path, + pair_data: dict[str, pd.DataFrame] | None = None, + *, + reference_path: str | Path | None = None, + interpolate: bool | int = 200, +) -> dict[str, object]: + """Evaluate MBD metrics and write JSON.""" + result = evaluate_mbd_diatomic_metrics( + pair_data, + reference_path=reference_path, + interpolate=interpolate, + ) + resolved_output_path = Path(output_path) + resolved_output_path.parent.mkdir(parents=True, exist_ok=True) + with open(resolved_output_path, "w", encoding="utf-8") as file: + json.dump(result, file, indent=2, allow_nan=False) + return result + + @periodic_curve_gallery( curve_dir=CURVE_PATH, periodic_dir=None, @@ -249,49 +332,20 @@ def _load_pair_data() -> dict[str, pd.DataFrame]: y_range=(-20.0, 20.0), ) def persist_diatomics_pair_data() -> dict[str, pd.DataFrame]: - """ - Persist curve payloads and return the per-model dataframes. - - Returns - ------- - dict[str, pd.DataFrame] - Mapping of model name to per-pair curve data. - """ + """Persist curve payloads and return per-model dataframes.""" return _load_pair_data() @pytest.fixture def diatomics_pair_data_fixture() -> dict[str, pd.DataFrame]: - """ - Load curve data and persist gallery assets for pytest use. - - Returns - ------- - dict[str, pd.DataFrame] - Mapping of model name to per-pair curve data. - """ + """Load curve data and persist gallery assets for pytest.""" return persist_diatomics_pair_data() def collect_metrics( pair_data: dict[str, pd.DataFrame] | None = None, ) -> pd.DataFrame: - """ - Gather metrics for all models. - - Metrics are averaged across all diatomic pairs (both homonuclear and heteronuclear). - - Parameters - ---------- - pair_data - Optional mapping of model names to curve dataframes. When ``None``, - the data is loaded via ``persist_diatomics_pair_data``. - - Returns - ------- - pd.DataFrame - Aggregated metrics table (all pairs). - """ + """Aggregate metrics across all homo- and heteronuclear pairs by model.""" metrics_rows: list[dict[str, float | str]] = [] OUT_PATH.mkdir(parents=True, exist_ok=True) @@ -312,42 +366,10 @@ def collect_metrics( def diatomics_collection( diatomics_pair_data_fixture: dict[str, pd.DataFrame], ) -> pd.DataFrame: - """ - Collect diatomics metrics across all models. - - Parameters - ---------- - diatomics_pair_data_fixture - Mapping of model names to curve dataframes generated by the fixture. - - Returns - ------- - pd.DataFrame - Aggregated metrics dataframe. - """ + """Collect per-model diatomic metrics.""" return collect_metrics(diatomics_pair_data_fixture) -@pytest.fixture -def diatomics_metrics_dataframe( - diatomics_collection: pd.DataFrame, -) -> pd.DataFrame: - """ - Provide the aggregated diatomics metrics dataframe. - - Parameters - ---------- - diatomics_collection - Metrics dataframe produced by ``collect_metrics``. - - Returns - ------- - pd.DataFrame - Aggregated diatomics metrics indexed by model. - """ - return diatomics_collection - - @pytest.fixture @build_table( filename=OUT_PATH / "diatomics_metrics_table.json", @@ -356,43 +378,28 @@ def diatomics_metrics_dataframe( weights=None, ) def metrics( - diatomics_metrics_dataframe: pd.DataFrame, + diatomics_collection: pd.DataFrame, ) -> dict[str, dict]: - """ - Compute diatomics metrics for all models. - - Parameters - ---------- - diatomics_metrics_dataframe - Aggregated per-model metrics produced by ``collect_metrics``. - - Returns - ------- - dict[str, dict] - Mapping of metric names to per-model results. - """ - metrics_df = diatomics_metrics_dataframe - metrics_dict: dict[str, dict[str, float | None]] = {} - for column in metrics_df.columns: - if column == "Model": - continue - values = [ - value if pd.notna(value) else None for value in metrics_df[column].tolist() - ] - metrics_dict[column] = dict(zip(metrics_df["Model"], values, strict=False)) - return metrics_dict + """Return metric-name mappings by model.""" + return { + column: dict( + zip( + diatomics_collection["Model"], + [ + value if pd.notna(value) else None + for value in diatomics_collection[column] + ], + strict=False, + ) + ) + for column in diatomics_collection + if column != "Model" + } @pytest.mark.framework("mace-multihead") def test_diatomics(metrics: dict[str, dict]) -> None: - """ - Run diatomics analysis. - - Parameters - ---------- - metrics - Benchmark metrics generated by fixtures. - """ + """Write diatomic benchmark metadata after fixture evaluation.""" mock_data = load_model_data("mock") # Write out info.json with open(OUT_PATH / "info.json", "w") as f: diff --git a/ml_peg/analysis/physicality/diatomics/data/README.md b/ml_peg/analysis/physicality/diatomics/data/README.md new file mode 100644 index 000000000..6a7ae1438 --- /dev/null +++ b/ml_peg/analysis/physicality/diatomics/data/README.md @@ -0,0 +1,10 @@ +# Diatomics DFT reference + +`diatomics-dft.json.gz` was copied without modification from +[`matbench_discovery/site/src/lib/diatomics-dft.json.gz`](https://github.com/janosh/matbench-discovery/blob/2c7f9fc42d018711dc2f5df573d225ea6d2d17b2/site/src/lib/diatomics-dft.json.gz) +at Matbench Discovery commit `2c7f9fc42d018711dc2f5df573d225ea6d2d17b2`. + +SHA-256: `1fe6334a82e98208ea74169a3beaf98cd5188bdc7ac40e518697fd36c7196e3d` + +The file contains homonuclear DFT energy and force curves. The imported Matbench +Discovery metrics use its PBE data. Do not modify the compressed file. diff --git a/ml_peg/analysis/physicality/diatomics/data/diatomics-dft.json.gz b/ml_peg/analysis/physicality/diatomics/data/diatomics-dft.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..58dd61ccbb3e0f8ff75c998dff5bad26287defec GIT binary patch literal 203913 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 z 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%0DVHQN&Fk6fz##?JPF5fDa> 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 literal 0 HcmV?d00001 diff --git a/ml_peg/analysis/physicality/diatomics/metrics/__init__.py b/ml_peg/analysis/physicality/diatomics/metrics/__init__.py new file mode 100644 index 000000000..74aa2a060 --- /dev/null +++ b/ml_peg/analysis/physicality/diatomics/metrics/__init__.py @@ -0,0 +1,398 @@ +"""Diatomic potential-energy metrics adapted from Matbench Discovery. + +The smoothness approach follows Stenczel et al., https://arxiv.org/abs/2401.00096. +""" + +from __future__ import annotations + +from collections.abc import Callable +import logging +from typing import Any + +from ase.data import atomic_numbers, covalent_radii, vdw_alvarez +import numpy as np + +from ml_peg.analysis.physicality.diatomics.metrics.energy import ( + calc_energy_diff_flips, + calc_energy_jump, + calc_pbe_bond_length_error, + calc_pbe_energy_mae, + calc_pbe_vib_freq_error, + calc_pbe_wall_dist_mae, + calc_pbe_well_depth_error, + calc_tortuosity, +) +from ml_peg.analysis.physicality.diatomics.metrics.force import ( + calc_force_flips, + calc_force_jump, + calc_force_mae, + calc_force_total_variation, +) +from ml_peg.analysis.physicality.diatomics.metrics.schema import ( + DEFAULT_DFT_REFERENCE_PATH, + DiatomicCurve, + DiatomicCurves, + curves_from_ml_peg_dataframe, + homo_key, + load_dft_reference_curves, + load_mbd_json, + load_ml_peg_curves, +) + +logger = logging.getLogger(__name__) + +TORTUOSITY = "tortuosity" +FORCE_FLIPS = "force_flips" +ENERGY_JUMP = "energy_jump" +ENERGY_DIFF_FLIPS = "energy_diff_flips" +FORCE_TOTAL_VARIATION = "force_total_variation" +FORCE_JUMP = "force_jump" +PBE_WALL_DIST_MAE = "pbe_wall_dist_mae" +PBE_ENERGY_MAE = "pbe_energy_mae" +PBE_BOND_LENGTH_ERROR = "pbe_bond_length_error" +PBE_WELL_DEPTH_ERROR = "pbe_well_depth_error" +PBE_FORCE_MAE = "pbe_force_mae" +PBE_VIB_FREQ_ERROR = "pbe_vib_freq_error" + +DIATOMIC_METRIC_NAMES: tuple[str, ...] = ( + TORTUOSITY, + FORCE_FLIPS, + ENERGY_JUMP, + ENERGY_DIFF_FLIPS, + FORCE_TOTAL_VARIATION, + FORCE_JUMP, + PBE_WALL_DIST_MAE, + PBE_ENERGY_MAE, + PBE_BOND_LENGTH_ERROR, + PBE_WELL_DEPTH_ERROR, + PBE_FORCE_MAE, + PBE_VIB_FREQ_ERROR, +) +DIATOMIC_METRIC_KEYS = frozenset(DIATOMIC_METRIC_NAMES) + +# Skip H-U elements absent from Materials Project training data. +NON_MP_ELEMENTS = frozenset({"Po", "At", "Rn", "Fr", "Ra"}) +DIATOMIC_WALL_R_MIN_FACTOR = 0.8 +MAX_SCORED_ATOMIC_NUMBER = 92 + + +def find_low_quality_dft_refs( + ref_curves: DiatomicCurves, + *, + min_energy_jump: float = 1.5, + min_energy_flips: int = 3, +) -> set[str]: + """Find non-finite or discontinuous DFT references unsuitable for scoring.""" + low_quality: set[str] = set() + for element_symbol, curve in ref_curves.homo_nuclear.items(): + separations = curve.distances + energies = curve.energies + if separations.size == 0: + continue + radius_min, radius_max = eval_window(element_symbol, float(np.max(separations))) + window_mask = (separations >= radius_min) & (separations <= radius_max) + if window_mask.sum() < 5: + continue # too few in-window points to assess smoothness + if not np.isfinite(energies[window_mask]).all(): + # Non-finite references cannot be scored. + low_quality.add(element_symbol) + continue + if ( + calc_energy_jump(separations[window_mask], energies[window_mask]) + >= min_energy_jump + and calc_energy_diff_flips(separations[window_mask], energies[window_mask]) + >= min_energy_flips + ): + low_quality.add(element_symbol) + return low_quality + + +def eval_window( + elem_symbol: str, + seps_max: float, + *, + r_min_factor: float = 0.9, +) -> tuple[float, float]: + """Return the covalent-to-van-der-Waals evaluation window in Å.""" + atomic_number = atomic_numbers[elem_symbol.split("-", maxsplit=1)[0]] + covalent_radius = ( + covalent_radii[atomic_number] if atomic_number < len(covalent_radii) else np.nan + ) + radius_min = r_min_factor * covalent_radius if np.isfinite(covalent_radius) else 0.0 + vdw_radii = vdw_alvarez.vdw_radii + vdw_radius = vdw_radii[atomic_number] if atomic_number < len(vdw_radii) else np.nan + radius_max = ( + min(3.1 * vdw_radius, seps_max) if np.isfinite(vdw_radius) else seps_max + ) + return radius_min, radius_max + + +def calc_diatomic_metrics( + ref_curves: DiatomicCurves | None, + pred_curves: DiatomicCurves, + metrics: dict[str, dict[str, Any]] | None = None, + *, + interpolate: bool | int = False, +) -> dict[str, dict[str, float]]: + """Calculate requested metrics for supported homonuclear curves by element. + + Low-quality references receive self-consistency metrics but no ``pbe_*`` metrics. + """ + requested_metric_keys = ( + set(metrics) if metrics is not None else set(DIATOMIC_METRIC_KEYS) + ) + unknown_metrics = requested_metric_keys - DIATOMIC_METRIC_KEYS + if unknown_metrics: + raise ValueError( + f"unknown_metrics={unknown_metrics}. " + f"Valid metrics={sorted(DIATOMIC_METRIC_KEYS)}" + ) + metric_kwargs = {key: kwargs.copy() for key, kwargs in (metrics or {}).items()} + for metric_key in (PBE_ENERGY_MAE, PBE_FORCE_MAE): + if metric_key in requested_metric_keys: + metric_kwargs.setdefault(metric_key, {}).setdefault( + "interpolate", interpolate + ) + + low_quality_refs = find_low_quality_dft_refs(ref_curves) if ref_curves else set() + results: dict[str, dict[str, float]] = {} + seen_elements: set[str] = set() + for element_symbol, pred_data in pred_curves.homo_nuclear.items(): + normalized_element = homo_key(element_symbol) + if normalized_element in seen_elements: + raise ValueError( + f"Duplicate homonuclear curve for element {normalized_element!r}" + ) + seen_elements.add(normalized_element) + if ( + normalized_element in NON_MP_ELEMENTS + or atomic_numbers[normalized_element] > MAX_SCORED_ATOMIC_NUMBER + ): + continue # score all models on the same MP-supported element set + # General metrics use the MLIP Arena window; wall metrics extend to + # 0.8 times the covalent radius. + predicted_distances = pred_data.distances + separations_max = float(predicted_distances.max()) + radius_min, radius_max = eval_window(element_symbol, separations_max) + predicted_mask = (predicted_distances >= radius_min) & ( + predicted_distances <= radius_max + ) + if predicted_mask.sum() < 5: # too few points in window for stable metrics + logger.info( + "Skipping %s diatomic metrics: <5 points in eval window", + element_symbol, + ) + continue + predicted_separations = predicted_distances[predicted_mask] + predicted_energies_raw = pred_data.energies + predicted_energies = predicted_energies_raw[predicted_mask] + predicted_forces_raw = pred_data.forces + if not predicted_forces_raw.size: + raise ValueError(f"{element_symbol} diatomic curve is missing forces") + if len(predicted_forces_raw) != len(predicted_distances): + raise ValueError( + f"{element_symbol} diatomic force and distance counts differ: " + f"{len(predicted_forces_raw)} != {len(predicted_distances)}" + ) + predicted_forces = predicted_forces_raw[predicted_mask] + + wall_radius_min = eval_window( + element_symbol, + separations_max, + r_min_factor=DIATOMIC_WALL_R_MIN_FACTOR, + )[0] + # Include a generated DFT endpoint that differs from 0.8*r_cov by one ulp. + wall_radius_min -= 1e-12 + predicted_wall_mask = (predicted_distances >= wall_radius_min) & ( + predicted_distances <= radius_max + ) + if not ( + np.isfinite(predicted_energies_raw[predicted_wall_mask]).all() + and np.isfinite(predicted_forces_raw[predicted_wall_mask]).all() + ): + logger.info( + "Skipping %s diatomic metrics: non-finite wall values", element_symbol + ) + continue + + # Skip model instabilities in the scored window. + if not ( + np.isfinite(predicted_energies).all() + and np.isfinite(predicted_forces).all() + ): + logger.info( + "Skipping %s diatomic metrics: non-finite curve values", + element_symbol, + ) + continue + + energy_args = (predicted_separations, predicted_energies) + force_args = (predicted_separations, predicted_forces) + # Calls for metrics that need only the predicted curve. + metric_calls: list[tuple[str, Callable[..., float], tuple[Any, ...]]] = [ + (TORTUOSITY, calc_tortuosity, energy_args), + (ENERGY_DIFF_FLIPS, calc_energy_diff_flips, energy_args), + (ENERGY_JUMP, calc_energy_jump, energy_args), + (FORCE_FLIPS, calc_force_flips, force_args), + (FORCE_TOTAL_VARIATION, calc_force_total_variation, force_args), + (FORCE_JUMP, calc_force_jump, force_args), + ] + + # Add relative metrics only for references that pass the quality gate. + reference_data = ( + ref_curves.homo_nuclear.get(normalized_element) + if ref_curves and normalized_element not in low_quality_refs + else None + ) + if reference_data is not None: + reference_distances = reference_data.distances + reference_mask = (reference_distances >= radius_min) & ( + reference_distances <= radius_max + ) + reference_separations = reference_distances[reference_mask] + reference_energies_raw = reference_data.energies + reference_energies = reference_energies_raw[reference_mask] + if len(reference_separations) >= 2: + pair_args = ( + reference_separations, + reference_energies, + predicted_separations, + predicted_energies, + ) + metric_calls[:0] = [ + (PBE_ENERGY_MAE, calc_pbe_energy_mae, pair_args), + (PBE_BOND_LENGTH_ERROR, calc_pbe_bond_length_error, pair_args), + (PBE_WELL_DEPTH_ERROR, calc_pbe_well_depth_error, pair_args), + ( + PBE_VIB_FREQ_ERROR, + calc_pbe_vib_freq_error, + (element_symbol, *pair_args), + ), + ] + reference_wall_mask = (reference_distances >= wall_radius_min) & ( + reference_distances <= radius_max + ) + if predicted_wall_mask.sum() >= 2 and reference_wall_mask.sum() >= 2: + wall_args = ( + reference_distances[reference_wall_mask], + reference_energies_raw[reference_wall_mask], + predicted_distances[predicted_wall_mask], + predicted_energies_raw[predicted_wall_mask], + ) + metric_calls.insert( + 0, + (PBE_WALL_DIST_MAE, calc_pbe_wall_dist_mae, wall_args), + ) + reference_forces = reference_data.forces + if ( + reference_forces.size + and len(reference_forces) == len(reference_distances) + and len(reference_separations) >= 2 + ): + reference_forces = reference_forces[reference_mask] + force_interpolate = metric_kwargs.get(PBE_FORCE_MAE, {}).get( + "interpolate", False + ) + same_grid = np.array_equal(reference_separations, predicted_separations) + has_overlap = max( + reference_separations.min(), + predicted_separations.min(), + ) < min( + reference_separations.max(), + predicted_separations.max(), + ) + if same_grid or (force_interpolate and has_overlap): + force_pair_args = ( + reference_separations, + reference_forces, + predicted_separations, + predicted_forces, + ) + metric_calls.append( + (PBE_FORCE_MAE, calc_force_mae, force_pair_args) + ) + + results[normalized_element] = { + metric_key: metric_function( + *metric_args, + **metric_kwargs.get(metric_key, {}), + ) + for metric_key, metric_function, metric_args in metric_calls + if metric_key in requested_metric_keys + } + + return results + + +def aggregate_finite_means( + metrics_by_element: dict[str, dict[str, float]], +) -> dict[str, float]: + """Average finite values for every metric present, to four significant digits.""" + metric_means: dict[str, float] = {} + metric_names = dict.fromkeys( + metric_name + for element_metrics in metrics_by_element.values() + for metric_name in element_metrics + ) + for metric_name in metric_names: + finite_values = [ + metric_value + for element_metrics in metrics_by_element.values() + if (metric_value := element_metrics.get(metric_name)) is not None + and np.isfinite(metric_value) + ] + if finite_values: + value_scale = max(abs(metric_value) for metric_value in finite_values) + if value_scale == 0: + metric_mean = 0.0 + else: + metric_mean = value_scale * ( + sum(metric_value / value_scale for metric_value in finite_values) + / len(finite_values) + ) + if np.isfinite(metric_mean): + metric_means[metric_name] = float(f"{metric_mean:.4}") + return metric_means + + +__all__ = [ + "DEFAULT_DFT_REFERENCE_PATH", + "DIATOMIC_METRIC_KEYS", + "DIATOMIC_METRIC_NAMES", + "DIATOMIC_WALL_R_MIN_FACTOR", + "ENERGY_DIFF_FLIPS", + "ENERGY_JUMP", + "FORCE_FLIPS", + "FORCE_JUMP", + "FORCE_TOTAL_VARIATION", + "NON_MP_ELEMENTS", + "PBE_BOND_LENGTH_ERROR", + "PBE_ENERGY_MAE", + "PBE_FORCE_MAE", + "PBE_VIB_FREQ_ERROR", + "PBE_WALL_DIST_MAE", + "PBE_WELL_DEPTH_ERROR", + "TORTUOSITY", + "DiatomicCurve", + "DiatomicCurves", + "aggregate_finite_means", + "calc_diatomic_metrics", + "calc_energy_diff_flips", + "calc_energy_jump", + "calc_force_flips", + "calc_force_jump", + "calc_force_mae", + "calc_force_total_variation", + "calc_pbe_bond_length_error", + "calc_pbe_energy_mae", + "calc_pbe_vib_freq_error", + "calc_pbe_wall_dist_mae", + "calc_pbe_well_depth_error", + "calc_tortuosity", + "curves_from_ml_peg_dataframe", + "eval_window", + "find_low_quality_dft_refs", + "load_dft_reference_curves", + "load_mbd_json", + "load_ml_peg_curves", +] diff --git a/ml_peg/analysis/physicality/diatomics/metrics/energy.py b/ml_peg/analysis/physicality/diatomics/metrics/energy.py new file mode 100644 index 000000000..e14d85121 --- /dev/null +++ b/ml_peg/analysis/physicality/diatomics/metrics/energy.py @@ -0,0 +1,390 @@ +"""Energy-based metrics for diatomic curves.""" + +from __future__ import annotations + +from typing import Literal + +from ase.data import atomic_masses, atomic_numbers +import numpy as np +from numpy.typing import ArrayLike + +PBE_WALL_ENERGY_THRESHOLDS_EV: tuple[float, ...] = (1, 5, 10, 20, 50, 100) + + +def _validate_diatomic_curve( + separations: ArrayLike, + values: ArrayLike, + *, + normalize_energy: bool = False, + value_kind: Literal["energy", "force"] = "energy", +) -> tuple[np.ndarray, np.ndarray]: + """Validate, sort, and optionally far-field-normalize a sampled curve.""" + separation_array = np.asarray(separations) + value_array = np.asarray(values) + + if separation_array.ndim != 1: + raise ValueError( + f"separations must have shape (n,), got {separation_array.shape}" + ) + if value_kind == "energy" and value_array.ndim != 1: + raise ValueError(f"energy values must have shape (n,), got {value_array.shape}") + if value_kind == "force" and ( + value_array.ndim != 3 or value_array.shape[1:] != (2, 3) + ): + raise ValueError( + f"force values must have shape (n, 2, 3), got {value_array.shape}" + ) + + if len(separation_array) != len(value_array): + raise ValueError( + f"len(separation_array)={len(separation_array)} != " + f"len(value_array)={len(value_array)}" + ) + if len(separation_array) < 2: + raise ValueError( + "Input must have at least 2 points, " + f"got len(separation_array)={len(separation_array)}" + ) + n_separation_nan = int(np.isnan(separation_array).sum()) + n_value_nan = int(np.isnan(value_array).sum()) + if n_separation_nan or n_value_nan: + raise ValueError( + "Input contains NaN values: " + f"n_separation_nan={n_separation_nan}, n_value_nan={n_value_nan}" + ) + n_separation_inf = int(np.isinf(separation_array).sum()) + n_value_inf = int(np.isinf(value_array).sum()) + if n_separation_inf or n_value_inf: + raise ValueError( + "Input contains infinite values: " + f"n_separation_inf={n_separation_inf}, n_value_inf={n_value_inf}" + ) + n_unique = len(np.unique(separation_array)) + if n_unique != len(separation_array): + raise ValueError( + f"separations contains {len(separation_array) - n_unique} duplicates" + ) + + sort_indices = np.argsort(separation_array) + separation_array = separation_array[sort_indices] + value_array = value_array[sort_indices] + + # Normalize energy curves to zero at the largest separation. + if normalize_energy and value_array.ndim == 1: + # The ascending sort places that sample last. + value_array = value_array - value_array[-1] + + return separation_array, value_array + + +def _interpolation_point_count(interpolate: bool | int) -> int: + """Return the requested interpolation size, validating a two-point minimum.""" + n_points = 100 if interpolate is True else int(interpolate) + if n_points < 2: + raise ValueError("interpolate must request at least 2 points") + return n_points + + +def _common_grid_curve_pair( + separations_ref: ArrayLike, + values_ref: ArrayLike, + separations_pred: ArrayLike, + values_pred: ArrayLike, + *, + interpolate: bool | int, + value_kind: Literal["energy", "force"] = "energy", +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """Validate two curves and optionally interpolate their common interval.""" + separations_ref, values_ref = _validate_diatomic_curve( + separations_ref, values_ref, value_kind=value_kind + ) + separations_pred, values_pred = _validate_diatomic_curve( + separations_pred, values_pred, value_kind=value_kind + ) + if not interpolate: + if not np.array_equal(separations_ref, separations_pred): + raise ValueError( + "Reference and predicted distances must be same when " + f"interpolate={interpolate}\n" + f"separations_ref={separations_ref}, " + f"separations_pred={separations_pred}" + ) + return separations_ref, values_ref, values_pred + + data_min = max(separations_ref.min(), separations_pred.min()) + data_max = min(separations_ref.max(), separations_pred.max()) + if data_min >= data_max: + curve_label = "force curves" if value_kind == "force" else "curves" + raise ValueError( + f"Cannot interpolate {curve_label} with no overlap: " + f"data_min={data_min}, data_max={data_max}" + ) + common_grid = np.linspace( + data_min, data_max, _interpolation_point_count(interpolate) + ) + + def interpolate_values(separations: np.ndarray, values: np.ndarray) -> np.ndarray: + """Interpolate all flattened value components onto ``common_grid``.""" + flattened_values = values.reshape(len(values), -1) + interpolated = np.column_stack( + [ + np.interp( + common_grid, + separations, + flattened_values[:, component_index], + ) + for component_index in range(flattened_values.shape[1]) + ] + ) + return interpolated.reshape(len(common_grid), *values.shape[1:]) + + return ( + common_grid, + interpolate_values(separations_ref, values_ref), + interpolate_values(separations_pred, values_pred), + ) + + +def _binding_energy(energies: np.ndarray) -> float: + """Return well depth relative to the largest sampled separation.""" + return float(energies[-1] - np.min(energies)) + + +def _validated_energy_pair( + seps_ref: ArrayLike, + energy_ref: ArrayLike, + seps_pred: ArrayLike, + energy_pred: ArrayLike, +) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: + """Validate two independently sampled energy curves.""" + separations_ref, energies_ref = _validate_diatomic_curve(seps_ref, energy_ref) + separations_pred, energies_pred = _validate_diatomic_curve(seps_pred, energy_pred) + return separations_ref, energies_ref, separations_pred, energies_pred + + +def _quadratic_well_fit( + separations: ArrayLike, + energies: ArrayLike, + n_fit_points: int = 5, +) -> tuple[float, float]: + """Estimate equilibrium separation and curvature by local quadratic fit.""" + separations, energies = _validate_diatomic_curve(separations, energies) + minimum_index = int(np.argmin(energies)) + if len(separations) < 3: + return float(separations[minimum_index]), np.nan + + start_index = min( + max(0, minimum_index - n_fit_points // 2), + max(0, len(separations) - n_fit_points), + ) + fit_separations = separations[start_index : start_index + n_fit_points] + fit_energies = energies[start_index : start_index + n_fit_points] + if len(fit_separations) < 3: + return float(separations[minimum_index]), np.nan + + quadratic_coefficient, linear_coefficient, _constant_coefficient = np.polyfit( + fit_separations, fit_energies, 2 + ) + curvature = 2 * quadratic_coefficient + if quadratic_coefficient <= 0: + return float(separations[minimum_index]), np.nan + + equilibrium_distance = -linear_coefficient / (2 * quadratic_coefficient) + if fit_separations.min() <= equilibrium_distance <= fit_separations.max(): + return float(equilibrium_distance), float(curvature) + return float(separations[minimum_index]), float(curvature) + + +def _repulsive_radius_at_threshold( + separations: ArrayLike, + energies: ArrayLike, + threshold_ev: float, +) -> float: + """Return the repulsive radius at an energy threshold, or NaN if unreached.""" + separations, energies = _validate_diatomic_curve(separations, energies) + minimum_index = int(np.argmin(energies)) + if minimum_index == 0: + return np.nan + + radii_inward = separations[minimum_index::-1] + energy_above_minimum = energies[minimum_index::-1] - energies[minimum_index] + monotonic_energy = np.maximum.accumulate(energy_above_minimum) + unique_energy, unique_indices = np.unique(monotonic_energy, return_index=True) + if len(unique_energy) < 2 or threshold_ev > unique_energy[-1]: + return np.nan + return float(np.interp(threshold_ev, unique_energy, radii_inward[unique_indices])) + + +def calc_pbe_wall_dist_mae( + seps_ref: ArrayLike, + energy_ref: ArrayLike, + seps_pred: ArrayLike, + energy_pred: ArrayLike, + *, + thresholds_ev: tuple[float, ...] = PBE_WALL_ENERGY_THRESHOLDS_EV, +) -> float: + """Calculate mean PBE wall-radius error over reachable energy thresholds. + + A missing predicted crossing receives the full reference-radius error. + """ + errors: list[float] = [] + for threshold_ev in thresholds_ev: + radius_ref = _repulsive_radius_at_threshold(seps_ref, energy_ref, threshold_ev) + if not np.isfinite(radius_ref): + continue + radius_pred = _repulsive_radius_at_threshold( + seps_pred, energy_pred, threshold_ev + ) + errors.append( + abs(radius_pred - radius_ref) if np.isfinite(radius_pred) else radius_ref + ) + return float(np.mean(errors)) if errors else np.nan + + +def calc_pbe_energy_mae( + seps_ref: ArrayLike, + energy_ref: ArrayLike, + seps_pred: ArrayLike, + energy_pred: ArrayLike, + *, + interpolate: bool | int = 200, +) -> float: + """Calculate PBE energy MAE after optional interpolation and far-field alignment.""" + _, energy_ref, energy_pred = _common_grid_curve_pair( + seps_ref, + energy_ref, + seps_pred, + energy_pred, + interpolate=interpolate, + ) + energy_ref = energy_ref - energy_ref[-1] + energy_pred = energy_pred - energy_pred[-1] + return float(np.mean(np.abs(energy_pred - energy_ref))) + + +def calc_pbe_bond_length_error( + seps_ref: ArrayLike, + energy_ref: ArrayLike, + seps_pred: ArrayLike, + energy_pred: ArrayLike, + *, + min_ref_binding_ev: float = 0.05, +) -> float: + """Calculate absolute PBE equilibrium-distance error, or NaN if unbound.""" + separations_ref, energy_ref, separations_pred, energy_pred = _validated_energy_pair( + seps_ref, energy_ref, seps_pred, energy_pred + ) + if _binding_energy(energy_ref) < min_ref_binding_ev: + return np.nan + reference_distance = _quadratic_well_fit(separations_ref, energy_ref)[0] + predicted_distance = _quadratic_well_fit(separations_pred, energy_pred)[0] + return float(abs(predicted_distance - reference_distance)) + + +def calc_pbe_well_depth_error( + seps_ref: ArrayLike, + energy_ref: ArrayLike, + seps_pred: ArrayLike, + energy_pred: ArrayLike, + *, + min_ref_binding_ev: float = 0.05, +) -> float: + """Calculate absolute PBE well-depth error, or NaN if unbound.""" + _, energy_ref, _, energy_pred = _validated_energy_pair( + seps_ref, energy_ref, seps_pred, energy_pred + ) + reference_depth = _binding_energy(energy_ref) + if reference_depth < min_ref_binding_ev: + return np.nan + return float(abs(_binding_energy(energy_pred) - reference_depth)) + + +def _vibrational_wavenumber_cm( + element_symbol: str, + curvature_ev_per_a2: float, +) -> float: + """Convert a homonuclear force constant to harmonic wavenumber in cm⁻¹.""" + if not np.isfinite(curvature_ev_per_a2) or curvature_ev_per_a2 <= 0: + return np.nan + atomic_symbol = element_symbol.split("-", maxsplit=1)[0] + reduced_mass_kg = ( + atomic_masses[atomic_numbers[atomic_symbol]] * 1.66053906660e-27 / 2 + ) + force_constant_n_per_m = curvature_ev_per_a2 * 16.02176634 + angular_frequency_per_second = np.sqrt(force_constant_n_per_m / reduced_mass_kg) + return float(angular_frequency_per_second / (2 * np.pi * 2.99792458e10)) + + +def calc_pbe_vib_freq_error( + elem_symbol: str, + seps_ref: ArrayLike, + energy_ref: ArrayLike, + seps_pred: ArrayLike, + energy_pred: ArrayLike, + *, + min_ref_binding_ev: float = 0.05, +) -> float: + """Calculate absolute PBE vibrational-wavenumber error, or NaN if unbound.""" + separations_ref, energy_ref, separations_pred, energy_pred = _validated_energy_pair( + seps_ref, energy_ref, seps_pred, energy_pred + ) + if _binding_energy(energy_ref) < min_ref_binding_ev: + return np.nan + reference_curvature = _quadratic_well_fit(separations_ref, energy_ref)[1] + predicted_curvature = _quadratic_well_fit(separations_pred, energy_pred)[1] + reference_wavenumber = _vibrational_wavenumber_cm(elem_symbol, reference_curvature) + predicted_wavenumber = _vibrational_wavenumber_cm(elem_symbol, predicted_curvature) + return float(abs(predicted_wavenumber - reference_wavenumber)) + + +def calc_tortuosity(seps: ArrayLike, energies: ArrayLike) -> float: + """Calculate projected arc-chord energy tortuosity, or NaN if constant.""" + _, energies = _validate_diatomic_curve(seps, energies) + + total_energy_variation = np.sum(np.abs(np.diff(energies))) + minimum_energy = np.min(energies) + direct_energy_difference = abs(energies[0] - minimum_energy) + abs( + energies[-1] - minimum_energy + ) + + if direct_energy_difference == 0: + return np.nan + return float(total_energy_variation / direct_energy_difference) + + +def _threshold_diff_signs( + values: np.ndarray, + threshold: float = 1e-3, +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """Return nonzero thresholded differences, their signs, and flip mask.""" + differences = np.diff(values) + differences[np.abs(differences) < threshold] = 0 + signs = np.sign(differences) + nonzero_mask = signs != 0 + differences, signs = differences[nonzero_mask], signs[nonzero_mask] + flips = np.diff(signs) != 0 + return differences, signs, flips + + +def _jump_magnitude(values: np.ndarray, threshold: float = 1e-3) -> float: + """Sum adjacent step magnitudes at sign-flip points.""" + differences, _, flips = _threshold_diff_signs(values, threshold) + return float( + np.abs(differences[:-1][flips]).sum() + np.abs(differences[1:][flips]).sum() + ) + + +def calc_energy_diff_flips( + seps: ArrayLike, + energies: ArrayLike, +) -> float: + """Calculate the number of thresholded energy-difference sign flips.""" + _, energies = _validate_diatomic_curve(seps, energies) + _, _, flips = _threshold_diff_signs(energies) + return float(np.sum(flips)) + + +def calc_energy_jump(seps: ArrayLike, energies: ArrayLike) -> float: + """Calculate total energy-step magnitude around sign-flip points.""" + _, energies = _validate_diatomic_curve(seps, energies) + return _jump_magnitude(energies) diff --git a/ml_peg/analysis/physicality/diatomics/metrics/force.py b/ml_peg/analysis/physicality/diatomics/metrics/force.py new file mode 100644 index 000000000..b64dd1a95 --- /dev/null +++ b/ml_peg/analysis/physicality/diatomics/metrics/force.py @@ -0,0 +1,66 @@ +"""Force-based metrics for diatomic curves.""" + +from __future__ import annotations + +import numpy as np +from numpy.typing import ArrayLike + +from ml_peg.analysis.physicality.diatomics.metrics.energy import ( + _common_grid_curve_pair, + _jump_magnitude, + _validate_diatomic_curve, +) + + +def calc_force_mae( + seps_ref: ArrayLike, + f_ref: ArrayLike, + seps_pred: ArrayLike, + f_pred: ArrayLike, + *, + interpolate: bool | int = False, +) -> float: + """Calculate force MAE, optionally interpolating over the shared range.""" + _, f_ref, f_pred = _common_grid_curve_pair( + seps_ref, + f_ref, + seps_pred, + f_pred, + interpolate=interpolate, + value_kind="force", + ) + return float(np.mean(np.abs(f_ref - f_pred))) + + +def _radial_forces(seps: ArrayLike, forces: np.ndarray) -> np.ndarray: + """Validate a force curve and return first-atom radial forces.""" + _, forces = _validate_diatomic_curve(seps, forces, value_kind="force") + return forces[:, 0, 0] # x-component of force on first atom + + +def calc_force_flips( + seps: ArrayLike, + forces: np.ndarray, + threshold: float = 1e-2, # 10meV/A threshold as in reference code +) -> float: + """Count thresholded direction changes in the first atom's radial force.""" + radial_forces = _radial_forces(seps, forces).copy() + radial_forces[np.abs(radial_forces) < threshold] = 0 + force_signs = np.sign(radial_forces[radial_forces != 0]) + return float(np.sum(np.diff(force_signs) != 0)) + + +def calc_force_total_variation( + seps: ArrayLike, + forces: np.ndarray, +) -> float: + """Calculate total variation in the first atom's radial force.""" + return float(np.sum(np.abs(np.diff(_radial_forces(seps, forces))))) + + +def calc_force_jump( + seps: ArrayLike, + forces: np.ndarray, +) -> float: + """Calculate total radial-force step magnitude around sign-flip points.""" + return _jump_magnitude(_radial_forces(seps, forces), threshold=0) diff --git a/ml_peg/analysis/physicality/diatomics/metrics/schema.py b/ml_peg/analysis/physicality/diatomics/metrics/schema.py new file mode 100644 index 000000000..bc97383d2 --- /dev/null +++ b/ml_peg/analysis/physicality/diatomics/metrics/schema.py @@ -0,0 +1,252 @@ +"""Typed diatomic curve schema and local data adapters.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +import gzip +import json +import os +from typing import Any + +import numpy as np +from numpy.typing import ArrayLike +import pandas as pd + +StrPath = str | os.PathLike[str] +DEFAULT_DFT_REFERENCE_PATH = ( + f"{os.path.dirname(os.path.dirname(__file__))}/data/diatomics-dft.json.gz" +) + + +def homo_key(formula: str) -> str: + """Collapse a homonuclear pair label such as ``H-H`` to its element key.""" + element_1, separator, element_2 = formula.partition("-") + return element_1 if separator and element_1 == element_2 else formula + + +class DiatomicCurve: + """Store one validated diatomic energy and Cartesian-force curve.""" + + distances: np.ndarray + energies: np.ndarray + forces: np.ndarray + + def __init__( + self, + distances: ArrayLike, + energies: ArrayLike, + forces: ArrayLike, + ) -> None: + """Convert curve data to arrays and validate shapes and sample counts.""" + self.distances = np.asarray(distances) + self.energies = np.asarray(energies) + self.forces = np.asarray(forces) + + for name, values in ( + ("distances", self.distances), + ("energies", self.energies), + ): + if values.ndim != 1: + raise ValueError(f"{name} must have shape (n,), got {values.shape}") + + n_distances = len(self.distances) + if len(self.energies) != n_distances: + raise ValueError( + f"distance and energy counts differ: {n_distances} != " + f"{len(self.energies)}" + ) + + # Handle forces stored as (1, n_distances*n_atoms, 3) + # instead of (n_distances, n_atoms, 3) + if self.forces.shape == (1, 2 * n_distances, 3): + self.forces = self.forces.reshape(n_distances, 2, 3) + if len(self.forces) != n_distances: + raise ValueError( + f"distance and force counts differ: {n_distances} != {len(self.forces)}" + ) + expected_force_shape = (n_distances, 2, 3) + if self.forces.shape != expected_force_shape: + raise ValueError( + "forces must have shape " + f"{expected_force_shape}, got {self.forces.shape}" + ) + + +@dataclass +class DiatomicCurves: + """Store homo- and heteronuclear curves with their shared or union grid.""" + + distances: np.ndarray + homo_nuclear: dict[str, DiatomicCurve] + hetero_nuclear: dict[str, DiatomicCurve] = field(default_factory=dict) + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> DiatomicCurves: + """Parse MBD JSON curves, requiring per-curve grids to be ordered subsets.""" + distances = np.asarray(data["distances"]) + grid_position_by_distance = { + float(distance): index for index, distance in enumerate(distances) + } + + def make_curves(section: str) -> dict[str, DiatomicCurve]: + """Convert one MBD JSON section to typed curves.""" + raw_curves = data.get(section, data.get(section.replace("-", "_"), {})) + key_function = homo_key if section.startswith("homo") else str + + def curve_distances( + formula: str, + curve: dict[str, Any], + ) -> np.ndarray: + """Return an ordered per-curve subset of the top-level grid.""" + curve_distance_array = np.asarray(curve.get("distances", distances)) + # off-grid points map to -1; valid subsets have strictly + # increasing grid positions + grid_positions = np.array( + [ + grid_position_by_distance.get(float(distance), -1) + for distance in curve_distance_array + ] + ) + if (grid_positions < 0).any() or (np.diff(grid_positions) <= 0).any(): + raise ValueError( + f"{formula} curve distances must be an ordered subset " + "of top-level distances" + ) + return curve_distance_array + + return { + key_function(formula): DiatomicCurve( + distances=curve_distances(formula, curve), + energies=curve["energies"], + forces=curve.get("forces", []), + ) + for formula, curve in raw_curves.items() + if len(curve["energies"]) > 0 + } + + return cls( + distances=distances, + homo_nuclear=make_curves("homo-nuclear"), + hetero_nuclear=make_curves("hetero-nuclear"), + ) + + +def _load_json(path: StrPath) -> dict[str, Any]: + """Load a JSON or gzipped JSON object.""" + string_path = os.fspath(path) + open_function = gzip.open if string_path.endswith(".gz") else open + with open_function(string_path, mode="rt", encoding="utf-8") as file: + return json.load(file) + + +def load_mbd_json(path: StrPath) -> DiatomicCurves: + """Load MBD-format predicted curves from JSON or gzipped JSON.""" + return DiatomicCurves.from_dict(_load_json(path)) + + +def load_dft_reference_curves( + functional: str = "PBE", + ref_path: StrPath | None = None, +) -> DiatomicCurves: + """Load bundled or custom DFT reference curves for one functional.""" + reference_path = ref_path or DEFAULT_DFT_REFERENCE_PATH + references = _load_json(reference_path)[functional] + return DiatomicCurves( + distances=np.array([]), + homo_nuclear={ + homo_key(formula): DiatomicCurve( + distances=curve["distances"], + energies=curve["energies"], + forces=curve.get("forces", []), + ) + for formula, curve in references.items() + }, + ) + + +def _parse_pair_label(pair_label: str) -> tuple[str, str]: + """Parse an ``Element-Element`` pair label.""" + elements = pair_label.split("-") + if len(elements) != 2 or not all(elements): + raise ValueError( + f"pair labels must have form 'Element-Element', got {pair_label!r}" + ) + return elements[0], elements[1] + + +def curves_from_ml_peg_dataframe( + dataframe: pd.DataFrame, + *, + include_heteronuclear: bool = True, +) -> DiatomicCurves: + """Convert an ml-peg dataframe to x-aligned two-atom force curves.""" + required_columns = {"pair", "distance", "energy", "force_parallel"} + missing_columns = required_columns - set(dataframe.columns) + if missing_columns: + raise ValueError(f"Missing ml-peg diatomics columns: {sorted(missing_columns)}") + + homo_nuclear: dict[str, DiatomicCurve] = {} + hetero_nuclear: dict[str, DiatomicCurve] = {} + for pair_label, pair_dataframe in dataframe.groupby( + "pair", sort=False, dropna=False + ): + string_pair_label = str(pair_label) + element_1, element_2 = _parse_pair_label(string_pair_label) + if element_1 != element_2 and not include_heteronuclear: + continue + sorted_dataframe = pair_dataframe.sort_values("distance") + duplicate_rows = sorted_dataframe[ + sorted_dataframe.duplicated("distance", keep=False) + ] + for distance, duplicate_samples in duplicate_rows.groupby("distance"): + unique_values = duplicate_samples[ + ["energy", "force_parallel"] + ].drop_duplicates() + if len(unique_values) > 1: + raise ValueError( + f"{string_pair_label} has conflicting samples at " + f"distance={distance}" + ) + if not duplicate_rows.empty: + duplicate_distances = duplicate_rows["distance"].unique().tolist() + raise ValueError( + f"{string_pair_label} has duplicate distance values: " + f"{duplicate_distances!r}" + ) + distances = sorted_dataframe["distance"].to_numpy(dtype=float) + energies = sorted_dataframe["energy"].to_numpy(dtype=float) + projected_forces = sorted_dataframe["force_parallel"].to_numpy(dtype=float) + forces = np.zeros((len(distances), 2, 3), dtype=float) + forces[:, 0, 0] = -projected_forces + forces[:, 1, 0] = projected_forces + curve = DiatomicCurve(distances=distances, energies=energies, forces=forces) + if element_1 == element_2: + homo_nuclear[element_1] = curve + else: + hetero_nuclear[string_pair_label] = curve + + included_curves = [*homo_nuclear.values(), *hetero_nuclear.values()] + all_distances = ( + np.concatenate([curve.distances for curve in included_curves]) + if included_curves + else np.array([], dtype=float) + ) + return DiatomicCurves( + distances=np.sort(np.unique(all_distances)), + homo_nuclear=homo_nuclear, + hetero_nuclear=hetero_nuclear, + ) + + +def load_ml_peg_curves( + source: pd.DataFrame | StrPath, + *, + include_heteronuclear: bool = True, +) -> DiatomicCurves: + """Load current ml-peg diatomic curves from a dataframe or CSV.""" + dataframe = ( + source if isinstance(source, pd.DataFrame) else pd.read_csv(os.fspath(source)) + ) + return curves_from_ml_peg_dataframe( + dataframe, include_heteronuclear=include_heteronuclear + ) diff --git a/tests/metrics/diatomics/test_analysis.py b/tests/metrics/diatomics/test_analysis.py new file mode 100644 index 000000000..36f0005f6 --- /dev/null +++ b/tests/metrics/diatomics/test_analysis.py @@ -0,0 +1,129 @@ +"""Integration tests for ml-peg diatomic analysis entry points.""" + +from __future__ import annotations + +import gzip +import json +from pathlib import Path + +import numpy as np +import pandas as pd +import pytest + +from ml_peg.analysis.physicality.diatomics.analyse_diatomics import ( + DEFAULT_THRESHOLDS, + aggregate_model_metrics, + collect_metrics, + evaluate_mbd_diatomic_metrics, + write_mbd_diatomic_metrics, +) +from ml_peg.analysis.physicality.diatomics.metrics import DIATOMIC_METRIC_KEYS + + +def _integration_dataframe() -> pd.DataFrame: + """Build smooth homo- and heteronuclear current-format curves.""" + distances = np.linspace(0.3, 3.0, 20) + energies = 100 * (distances - 1.0) ** 2 - 2 + force_parallel = -200 * (distances - 1.0) + dataframe = pd.DataFrame( + { + "element_1": "H", + "distance": distances, + "energy": energies, + "force_parallel": force_parallel, + } + ) + return pd.concat( + [ + dataframe.assign(pair=pair, element_2=element_2) + for pair, element_2 in (("H-H", "H"), ("H-He", "He")) + ], + ignore_index=True, + ) + + +def _write_reference(path: Path) -> None: + """Write a matching gzipped PBE curve for integration tests.""" + dataframe = _integration_dataframe().query("pair == 'H-H'") + forces = np.zeros((len(dataframe), 2, 3)) + forces[:, 0, 0] = -dataframe["force_parallel"] + forces[:, 1, 0] = dataframe["force_parallel"] + curve = { + "distances": dataframe["distance"].tolist(), + "energies": dataframe["energy"].tolist(), + "forces": forces.tolist(), + } + with gzip.open(path, mode="wt", encoding="utf-8") as file: + json.dump({"PBE": {"H-H": curve}}, file) + + +def test_separate_mbd_evaluation_is_json_safe_and_homonuclear( + tmp_path: Path, +) -> None: + """Separate result reports 12 homonuclear metrics without legacy weighting.""" + reference_path = tmp_path / "reference.json.gz" + _write_reference(reference_path) + pair_data = {"test-model": _integration_dataframe()} + result = evaluate_mbd_diatomic_metrics( + pair_data, + reference_path=reference_path, + interpolate=200, + ) + + assert result["weighted_in_legacy_score"] is False + assert set(result["metric_names"]) == DIATOMIC_METRIC_KEYS + model_results = result["models"] + assert isinstance(model_results, dict) + assert set(model_results["test-model"]["elements"]) == {"H"} + assert set(model_results["test-model"]["means"]) == DIATOMIC_METRIC_KEYS + json.dumps(result, allow_nan=False) + + output_path = tmp_path / "mbd-metrics.json" + written = write_mbd_diatomic_metrics( + output_path, + pair_data, + reference_path=reference_path, + ) + assert json.loads(output_path.read_text(encoding="utf-8")) == written + + +def test_mbd_evaluation_uses_pair_labels_for_homonuclear_filtering( + tmp_path: Path, +) -> None: + """Ignore inconsistent element columns and classify pairs from their labels.""" + reference_path = tmp_path / "reference.json.gz" + _write_reference(reference_path) + dataframe = _integration_dataframe() + dataframe.loc[dataframe["pair"] == "H-H", "element_2"] = "He" + + result = evaluate_mbd_diatomic_metrics( + {"test-model": dataframe}, reference_path=reference_path + ) + + assert set(result["models"]["test-model"]["elements"]) == {"H"} + + +def test_legacy_metric_regression_remains_unchanged() -> None: + """Legacy five-metric homo-plus-hetero aggregation retains pinned values.""" + pair_dataframe = pd.DataFrame( + { + "pair": ["H-H"] * 5 + ["H-He"] * 5, + "distance": [1, 2, 3, 4, 5] * 2, + "energy": [4, 1, 0, 1, 4] * 2, + "force_parallel": [-2, -1, 0, 1, 2] * 2, + } + ) + expected = { + "Force flips": 1.0, + "Energy minima": 1.0, + "Energy inflections": 0.0, + "ρ(E, repulsion)": -1.0, + "ρ(E, attraction)": 1.0, + } + + assert list(DEFAULT_THRESHOLDS) == list(expected) + assert aggregate_model_metrics(pair_dataframe) == pytest.approx(expected) + collected = collect_metrics({"test-model": pair_dataframe}) + collected_record = collected.to_dict(orient="records")[0] + assert collected_record.pop("Model") == "test-model" + assert collected_record == pytest.approx(expected) diff --git a/tests/metrics/diatomics/test_energy_force.py b/tests/metrics/diatomics/test_energy_force.py new file mode 100644 index 000000000..4904eb993 --- /dev/null +++ b/tests/metrics/diatomics/test_energy_force.py @@ -0,0 +1,238 @@ +"""Tests for imported diatomic energy and force formulas.""" + +from __future__ import annotations + +from collections.abc import Callable +import re + +import numpy as np +import pytest + +from ml_peg.analysis.physicality.diatomics.metrics.energy import ( + calc_energy_diff_flips, + calc_energy_jump, + calc_pbe_bond_length_error, + calc_pbe_energy_mae, + calc_pbe_vib_freq_error, + calc_pbe_wall_dist_mae, + calc_pbe_well_depth_error, + calc_tortuosity, +) +from ml_peg.analysis.physicality.diatomics.metrics.force import ( + calc_force_flips, + calc_force_jump, + calc_force_mae, + calc_force_total_variation, +) + +_LENGTH_ERROR = re.escape("len(separation_array)=2 != len(value_array)=3") +_ENERGY_SHAPE_ERROR = re.escape("energy values must have shape (n,)") +_FORCE_SHAPE_ERROR = re.escape("force values must have shape (n, 2, 3)") + + +def _radial_forces(radial_values: np.ndarray) -> np.ndarray: + """Return equal-and-opposite two-atom forces from radial values.""" + forces = np.zeros((len(radial_values), 2, 3)) + forces[:, 0, 0] = radial_values + forces[:, 1, 0] = -radial_values + return forces + + +@pytest.mark.parametrize( + ("energies", "expected_flips", "expected_jump"), + [ + (np.array([1.0, 2.0, 3.0, 4.0, 5.0]), 0, 0.0), + (np.array([1.0, 3.0, 2.0, 4.0, 5.0]), 2, 6.0), + (np.array([0.0, 2.0, 1.0, 3.0, 0.5]), 3, 10.5), + ], +) +def test_energy_flip_and_jump_formulas( + energies: np.ndarray, + expected_flips: int, + expected_jump: float, +) -> None: + """Energy flips and jumps match hand-computed values.""" + separations = np.arange(1, len(energies) + 1, dtype=float) + assert calc_energy_diff_flips(separations, energies) == expected_flips + assert calc_energy_jump(separations, energies) == pytest.approx(expected_jump) + + +@pytest.mark.parametrize( + ("energies", "expected"), + [ + (np.arange(1, 6, dtype=float), 1.0), + (np.arange(1, 6, dtype=float) ** 2, 1.0), + (np.ones(5), np.nan), + ], +) +def test_tortuosity_formulas(energies: np.ndarray, expected: float) -> None: + """Tortuosity is one for monotone curves and NaN for flat curves.""" + result = calc_tortuosity(np.arange(1, 6), energies) + assert result == pytest.approx(expected, nan_ok=True) + + +def test_source_keyword_argument_names_are_supported() -> None: + """Public metric functions retain Matbench Discovery keyword names.""" + separations = np.arange(1, 6, dtype=float) + energies = np.arange(1, 6, dtype=float) + forces = np.zeros((5, 2, 3)) + assert calc_tortuosity(seps=separations, energies=energies) == pytest.approx(1) + assert calc_force_total_variation(seps=separations, forces=forces) == 0 + energy_kwargs = { + "seps_ref": separations, + "energy_ref": energies, + "seps_pred": separations, + "energy_pred": energies, + } + force_kwargs = { + "seps_ref": separations, + "f_ref": forces, + "seps_pred": separations, + "f_pred": forces, + } + assert calc_pbe_energy_mae(**energy_kwargs) == 0 + assert calc_force_mae(**force_kwargs) == 0 + + +def test_pbe_reference_energy_formulas() -> None: + """PBE-relative metrics match analytic parabolic-well expectations.""" + reference_equilibrium = 1.5 + predicted_equilibrium = 1.6 + predicted_curvature_factor = 1.2 + reference_max = 3.0 + predicted_max = 3.05 + separations_ref = np.linspace(0.5, reference_max, 51) + separations_pred = np.linspace(0.55, predicted_max, 51) + energy_ref = (separations_ref - reference_equilibrium) ** 2 - 2 + energy_pred = ( + predicted_curvature_factor * (separations_pred - predicted_equilibrium) ** 2 + - 1.7 + ) + curve_args = (separations_ref, energy_ref, separations_pred, energy_pred) + expected_depth_error = abs( + predicted_curvature_factor * (predicted_max - predicted_equilibrium) ** 2 + - (reference_max - reference_equilibrium) ** 2 + ) + results = [ + (calc_pbe_wall_dist_mae(*curve_args, thresholds_ev=(1,)), 0.187, 0.01), + (calc_pbe_energy_mae(*curve_args, interpolate=200), 0.118, 0.01), + ( + calc_pbe_bond_length_error(*curve_args), + predicted_equilibrium - reference_equilibrium, + None, + ), + (calc_pbe_well_depth_error(*curve_args), expected_depth_error, 0.01), + (calc_pbe_vib_freq_error("H", *curve_args), 99.1, 1), + ] + for actual, expected, absolute_tolerance in results: + assert actual == pytest.approx(expected, abs=absolute_tolerance) + + +def test_force_metric_formulas() -> None: + """Force metrics match hand-computed radial-force values.""" + separations = np.arange(1, 6, dtype=float) + forces = _radial_forces(np.array([1.0, 2.0, -1.0, 3.0, -2.0])) + assert calc_force_flips(separations, forces) == 3 + assert calc_force_total_variation(separations, forces) == pytest.approx(13) + assert calc_force_jump(separations, forces) == pytest.approx(20) + assert calc_force_mae(separations, forces, separations, forces) == 0 + + +def test_energy_and_force_interpolation_use_shared_range() -> None: + """Interpolated MAEs compare linear curves only on their overlap.""" + separations_ref = np.array([2.0, 3.0, 4.0]) + separations_pred = np.array([2.1, 3.1, 4.1]) + curve_args = (separations_ref, separations_ref, separations_pred, separations_pred) + assert calc_pbe_energy_mae( + *curve_args, + interpolate=20, + ) == pytest.approx(0) + assert calc_force_mae( + separations_ref, + _radial_forces(separations_ref), + separations_pred, + _radial_forces(separations_pred), + interpolate=20, + ) == pytest.approx(0) + + +@pytest.mark.parametrize( + ("metric_function", "separations", "values", "error_match"), + [ + ( + calc_energy_jump, + np.array([1.0, 1.0, 2.0]), + np.arange(3.0), + "contains 1 duplicates", + ), + ( + calc_energy_jump, + np.array([1.0, np.nan, 3.0]), + np.arange(3.0), + "Input contains NaN", + ), + (calc_energy_jump, np.arange(2.0), np.arange(3.0), _LENGTH_ERROR), + (calc_energy_jump, np.arange(3.0), np.zeros((3, 1)), _ENERGY_SHAPE_ERROR), + ( + calc_force_total_variation, + np.arange(3.0), + np.zeros((3, 1, 3)), + _FORCE_SHAPE_ERROR, + ), + ], +) +def test_curve_validation_errors( + metric_function: Callable[[np.ndarray, np.ndarray], float], + separations: np.ndarray, + values: np.ndarray, + error_match: str, +) -> None: + """Curve formulas reject duplicate, non-finite, and malformed inputs.""" + with pytest.raises(ValueError, match=error_match): + metric_function(separations, values) + + +@pytest.mark.parametrize( + "separations_pred", + [np.array([4.0, 5.0, 6.0]), np.array([3.0, 4.0, 5.0])], + ids=["disjoint", "single-shared-point"], +) +def test_force_interpolation_rejects_unusable_overlap( + separations_pred: np.ndarray, +) -> None: + """Force interpolation rejects disjoint and point-only overlap.""" + separations_ref = np.array([1.0, 2.0, 3.0]) + forces = np.zeros((3, 2, 3)) + with pytest.raises(ValueError, match="no overlap"): + calc_force_mae( + separations_ref, forces, separations_pred, forces, interpolate=True + ) + + +@pytest.mark.parametrize( + ("metric_function", "reference_values", "predicted_values"), + [ + ( + calc_pbe_energy_mae, + np.array([0.0, 1.0, 2.0]), + np.array([2.0, 1.0, 0.0]), + ), + (calc_force_mae, np.zeros((3, 2, 3)), np.ones((3, 2, 3))), + ], +) +def test_interpolation_requires_at_least_two_points( + metric_function: Callable[..., float], + reference_values: np.ndarray, + predicted_values: np.ndarray, +) -> None: + """Reject one-point interpolation, which erases far-field energy errors.""" + separations_ref = np.array([1.0, 2.0, 3.0]) + separations_pred = np.array([1.1, 2.1, 3.1]) + with pytest.raises(ValueError, match="at least 2 points"): + metric_function( + separations_ref, + reference_values, + separations_pred, + predicted_values, + interpolate=1, + ) diff --git a/tests/metrics/diatomics/test_metrics.py b/tests/metrics/diatomics/test_metrics.py new file mode 100644 index 000000000..d35918c8b --- /dev/null +++ b/tests/metrics/diatomics/test_metrics.py @@ -0,0 +1,263 @@ +"""Tests for diatomic metric orchestration and aggregation.""" + +from __future__ import annotations + +import hashlib + +import numpy as np +import pytest + +from ml_peg.analysis.physicality.diatomics import metrics +from ml_peg.analysis.physicality.diatomics.metrics import ( + DEFAULT_DFT_REFERENCE_PATH, + DIATOMIC_METRIC_KEYS, + ENERGY_JUMP, + PBE_ENERGY_MAE, + PBE_FORCE_MAE, + TORTUOSITY, + DiatomicCurve, + DiatomicCurves, + aggregate_finite_means, + calc_diatomic_metrics, + eval_window, + find_low_quality_dft_refs, + load_dft_reference_curves, +) + + +def _forces_from_energy( + distances: np.ndarray, + energies: np.ndarray, +) -> np.ndarray: + """Construct equal-and-opposite radial forces.""" + forces = np.zeros((len(distances), 2, 3)) + forces[:, 0, 0] = -np.gradient(energies, distances) + forces[:, 1, 0] = -forces[:, 0, 0] + return forces + + +def _make_curves( + curves_by_element: dict[str, np.ndarray], + distances: np.ndarray, +) -> DiatomicCurves: + """Wrap element energy arrays as homonuclear curves.""" + return DiatomicCurves( + distances=distances, + homo_nuclear={ + element_symbol: DiatomicCurve( + distances, energies, _forces_from_energy(distances, energies) + ) + for element_symbol, energies in curves_by_element.items() + }, + ) + + +@pytest.mark.parametrize("prediction_key", ["H", "H-H"]) +def test_all_metrics_and_reference_key_normalization(prediction_key: str) -> None: + """Matching element and pair keys both produce all 12 finite metrics.""" + distances = np.linspace(0.2, 3.0, 50) + energies = 100 * (distances - 1.0) ** 2 - 2 + references = _make_curves({"H": energies}, distances) + predictions = _make_curves({"H": energies}, distances) + if prediction_key == "H-H": + predictions.homo_nuclear[prediction_key] = predictions.homo_nuclear.pop("H") + result = calc_diatomic_metrics(references, predictions, interpolate=200) + assert set(result["H"]) == DIATOMIC_METRIC_KEYS + assert result["H"][PBE_ENERGY_MAE] == pytest.approx(0) + for metric_value in result["H"].values(): + assert np.isfinite(metric_value) + + +def test_duplicate_normalized_element_keys_are_rejected() -> None: + """Reject separate element and pair keys for the same homonuclear curve.""" + distances = np.linspace(0.2, 3.0, 50) + energies = (distances - 1.0) ** 2 + curves = _make_curves({"H": energies}, distances) + curves.homo_nuclear["H-H"] = curves.homo_nuclear["H"] + + with pytest.raises(ValueError, match="Duplicate homonuclear curve"): + calc_diatomic_metrics(None, curves) + + +def test_homonuclear_only_and_non_mp_filtering() -> None: + """Imported metrics ignore heteronuclear curves and unsupported MP elements.""" + distances = np.linspace(0.3, 3.0, 20) + energies = (distances - 1.0) ** 2 + homo_curves = _make_curves( + {"H": energies, "Po": energies, "Og": energies}, distances + ) + homo_curves.hetero_nuclear["H-He"] = DiatomicCurve( + distances, energies, _forces_from_energy(distances, energies) + ) + + result = calc_diatomic_metrics(None, homo_curves) + + assert set(result) == {"H"} + assert TORTUOSITY in result["H"] + + +def test_nonfinite_repulsive_wall_sample_skips_element() -> None: + """Non-finite values in the wider wall window exclude the whole curve.""" + distances = np.linspace(0.5, 3.0, 101) + energies = (distances - 1.5) ** 2 + curves = _make_curves({"C": energies}, distances) + wall_only_index = int(np.flatnonzero((distances >= 0.608) & (distances < 0.684))[0]) + curves.homo_nuclear["C"].energies[wall_only_index] = np.nan + + assert calc_diatomic_metrics(None, curves) == {} + + +def test_low_quality_reference_gate_and_nonfinite_predictions() -> None: + """Jumpy refs lose PBE metrics while non-finite predictions are skipped.""" + distances = np.linspace(0.3, 6.0, 40) + smooth = (distances - 1.5) ** 2 + jumpy = smooth + 5 * (-1) ** np.arange(len(distances)) + nonfinite = smooth.copy() + nonfinite[10] = np.nan + reference_curves = _make_curves( + {"H": smooth, "Ho": jumpy, "Er": np.full(len(distances), np.nan)}, + distances, + ) + predicted_curves = _make_curves( + {"H": smooth, "Ho": smooth, "Er": smooth, "He": nonfinite}, + distances, + ) + + assert find_low_quality_dft_refs(reference_curves) == {"Ho", "Er"} + result = calc_diatomic_metrics(reference_curves, predicted_curves) + assert set(result) == {"H", "Ho", "Er"} + assert PBE_ENERGY_MAE in result["H"] + for gated_element in ("Ho", "Er"): + assert PBE_ENERGY_MAE not in result[gated_element] + assert TORTUOSITY in result[gated_element] + + +def test_missing_forces_raise_clear_error() -> None: + """A scored prediction missing force samples is rejected.""" + distances = np.linspace(0.3, 3.0, 10) + energies = (distances - 1.0) ** 2 + predicted_curves = _make_curves({"H": energies}, distances) + predicted_curves.homo_nuclear["H"].forces = np.array([]) + + with pytest.raises(ValueError, match="H diatomic curve is missing forces"): + calc_diatomic_metrics(None, predicted_curves) + + +def test_full_pipeline_interpolation() -> None: + """Full relative metrics require matching grids unless interpolation is enabled.""" + reference_distances = np.linspace(0.3, 3.0, 20) + predicted_distances = reference_distances * 1.001 + reference_energies = (reference_distances - 1.0) ** 2 + predicted_energies = (predicted_distances - 1.0) ** 2 + reference_curves = _make_curves({"H": reference_energies}, reference_distances) + predicted_curves = _make_curves({"H": predicted_energies}, predicted_distances) + curve_pair = (reference_curves, predicted_curves) + + with pytest.raises(ValueError, match="distances must be same"): + calc_diatomic_metrics(*curve_pair, interpolate=False) + result = calc_diatomic_metrics(*curve_pair, interpolate=200) + assert set(result["H"]) == DIATOMIC_METRIC_KEYS + + +def test_force_interpolation_omits_point_only_overlap() -> None: + """Omit force MAE when reference and prediction ranges only touch.""" + reference_distances = np.linspace(0.4, 1.0, 5) + predicted_distances = np.linspace(1.0, 3.0, 5) + reference_curves = _make_curves({"H": reference_distances**2}, reference_distances) + predicted_curves = _make_curves({"H": predicted_distances**2}, predicted_distances) + result = calc_diatomic_metrics( + reference_curves, + predicted_curves, + metrics={PBE_FORCE_MAE: {"interpolate": True}}, + ) + assert result["H"] == {} + + +def test_eval_window_and_repulsive_exclusion() -> None: + """Element windows use physical radii and exclude deep-overlap spikes.""" + radius_min, radius_max = eval_window("H-H", 2.5) + atomic_number = metrics.atomic_numbers["H"] + assert radius_min == pytest.approx(0.9 * metrics.covalent_radii[atomic_number]) + assert radius_max == pytest.approx(2.5) + + distances = np.linspace(0.1, 6.0, 60) + energies = np.exp(-distances) + energies[distances < 0.2] = 1e6 + result = calc_diatomic_metrics( + None, + _make_curves({"H": energies}, distances), + ) + assert result["H"][ENERGY_JUMP] == pytest.approx(0) + + source_keyword_window = eval_window( + elem_symbol="H-H", seps_max=2.5, r_min_factor=0.8 + ) + assert source_keyword_window[0] == pytest.approx( + 0.8 * metrics.covalent_radii[atomic_number] + ) + + +def test_bundled_pbe_quality_gate_regression() -> None: + """Bundled PBE data retains the eight known jumpy lanthanide references.""" + reference_curves = load_dft_reference_curves() + assert find_low_quality_dft_refs(reference_curves) == { + "Pr", + "Pm", + "Sm", + "Tb", + "Dy", + "Ho", + "Er", + "Tm", + } + self_metrics = calc_diatomic_metrics( + reference_curves, reference_curves, interpolate=200 + ) + assert len(self_metrics) == 87 + mean_metrics = aggregate_finite_means(self_metrics) + assert mean_metrics == { + "tortuosity": 1.043, + "force_flips": 1.632, + "energy_jump": 6.28, + "energy_diff_flips": 2.575, + "force_total_variation": 193.5, + "force_jump": 7.164, + "pbe_wall_dist_mae": 0.0, + "pbe_energy_mae": 0.0, + "pbe_bond_length_error": 0.0, + "pbe_well_depth_error": 0.0, + "pbe_force_mae": 0.0, + "pbe_vib_freq_error": 0.0, + } + + +def test_bundled_pbe_reference_hash() -> None: + """Pin the bundled DFT reference file.""" + with open(DEFAULT_DFT_REFERENCE_PATH, "rb") as file: + digest = hashlib.sha256(file.read()).hexdigest() + + assert digest == "1fe6334a82e98208ea74169a3beaf98cd5188bdc7ac40e518697fd36c7196e3d" + + +def test_finite_mean_aggregation() -> None: + """Aggregation unions metric keys and ignores missing or non-finite values.""" + metrics_by_element = { + "H": {TORTUOSITY: 1.0, ENERGY_JUMP: 2.0}, + "He": {TORTUOSITY: np.nan, ENERGY_JUMP: 4.0}, + "Li": {TORTUOSITY: np.inf}, + } + assert aggregate_finite_means(metrics_by_element) == { + TORTUOSITY: 1.0, + ENERGY_JUMP: 3.0, + } + assert aggregate_finite_means( + {"H": {TORTUOSITY: 1e308}, "He": {TORTUOSITY: 1e308}} + ) == {TORTUOSITY: 1e308} + + +def test_unknown_metric_rejected() -> None: + """The orchestrator rejects names outside the stable 12-key set.""" + distances = np.linspace(0.3, 3.0, 10) + curves = _make_curves({"H": distances**2}, distances) + with pytest.raises(ValueError, match="unknown_metrics"): + calc_diatomic_metrics(None, curves, metrics={"not_a_metric": {}}) diff --git a/tests/metrics/diatomics/test_schema.py b/tests/metrics/diatomics/test_schema.py new file mode 100644 index 000000000..b7aba4eee --- /dev/null +++ b/tests/metrics/diatomics/test_schema.py @@ -0,0 +1,213 @@ +"""Tests for diatomic schemas and local data adapters.""" + +from __future__ import annotations + +import gzip +import json +from pathlib import Path + +import numpy as np +import pandas as pd +import pytest + +from ml_peg.analysis.physicality.diatomics.metrics import ( + DiatomicCurve, + DiatomicCurves, + load_dft_reference_curves, + load_mbd_json, + load_ml_peg_curves, +) + + +def _curve_payload() -> dict[str, object]: + """Return a minimal two-point MBD curve payload.""" + return { + "energies": [0.2, 0.0], + "forces": [ + [[0.1, 0, 0], [-0.1, 0, 0]], + [[0.0, 0, 0], [0.0, 0, 0]], + ], + } + + +def _ml_peg_dataframe( + *rows: tuple[object, float, float, float], +) -> pd.DataFrame: + """Build an ml-peg dataframe from pair, distance, energy, and force rows.""" + return pd.DataFrame(rows, columns=("pair", "distance", "energy", "force_parallel")) + + +def test_diatomic_classes_parse_and_reshape() -> None: + """Typed classes convert arrays and reshape legacy flattened forces.""" + distances = [1.0, 2.0] + energies = [0.1, 0.2] + flattened_forces = np.arange(12).reshape(1, 4, 3) + + curve = DiatomicCurve(distances, energies, flattened_forces) + assert curve.forces.shape == (2, 2, 3) + assert { + type(curve.distances), + type(curve.energies), + type(curve.forces), + } == {np.ndarray} + + payload = { + "distances": distances, + "homo-nuclear": {"H-H": _curve_payload()}, + "hetero_nuclear": {"H-He": _curve_payload()}, + } + curves = DiatomicCurves.from_dict(payload) + assert list(curves.homo_nuclear) == ["H"] + assert list(curves.hetero_nuclear) == ["H-He"] + np.testing.assert_array_equal(curves.homo_nuclear["H"].distances, distances) + + +@pytest.mark.parametrize( + ("override", "error_match"), + [ + ({"energies": [0.0]}, "distance and energy counts differ"), + ({"forces": np.zeros((1, 2, 3))}, "distance and force counts differ"), + ({"forces": np.zeros((2, 1, 3))}, "forces must have shape"), + ({"distances": [[1.0], [2.0]]}, "distances must have shape"), + ({"energies": [[0.0], [1.0]]}, "energies must have shape"), + ], +) +def test_diatomic_curve_rejects_invalid_shapes_and_counts( + override: dict[str, object], + error_match: str, +) -> None: + """DiatomicCurve rejects malformed arrays and sample-count mismatches.""" + arguments: dict[str, object] = { + "distances": [1.0, 2.0], + "energies": [0.0, 1.0], + "forces": np.zeros((2, 2, 3)), + } + with pytest.raises(ValueError, match=error_match): + DiatomicCurve(**(arguments | override)) + + +@pytest.mark.parametrize( + "bad_distances", + [[0.5, 1.5], [1.0, 1.0], [2.0, 1.0]], + ids=["off-grid", "duplicate", "reordered"], +) +def test_mbd_schema_rejects_invalid_curve_grids( + bad_distances: list[float], +) -> None: + """MBD curves must use ordered subsets of the top-level grid.""" + curve_payload = _curve_payload() | {"distances": bad_distances} + payload = { + "distances": [1.0, 2.0], + "homo-nuclear": {"H-H": curve_payload}, + } + with pytest.raises(ValueError, match="must be an ordered subset"): + DiatomicCurves.from_dict(payload) + + +def test_json_and_gzip_loaders(tmp_path: Path) -> None: + """MBD JSON and gzipped DFT references load into the typed schema.""" + prediction_path = tmp_path / "predictions.json" + prediction_path.write_text( + json.dumps( + { + "distances": [0.7, 1.0], + "homo-nuclear": {"H-H": _curve_payload()}, + } + ), + encoding="utf-8", + ) + prediction_curves = load_mbd_json(prediction_path) + assert list(prediction_curves.homo_nuclear) == ["H"] + + reference_path = tmp_path / "reference.json.gz" + with gzip.open(reference_path, mode="wt", encoding="utf-8") as file: + json.dump({"PBE": {"H-H": _curve_payload() | {"distances": [0.7, 1.0]}}}, file) + reference_curves = load_dft_reference_curves(ref_path=reference_path) + np.testing.assert_array_equal( + reference_curves.homo_nuclear["H"].energies, + [0.2, 0.0], + ) + + +def test_ml_peg_dataframe_and_csv_force_adapter(tmp_path: Path) -> None: + """CSV adapter reconstructs x-axis forces with the expected atom signs.""" + dataframe = _ml_peg_dataframe( + ("H-H", 2, 0, 2.5), + ("H-H", 1, 1, -1.5), + ("H-He", 1, 2, 4), + ("H-He", 2, 1, -3), + ) + csv_path = tmp_path / "diatomics.csv" + dataframe.to_csv(csv_path, index=False) + + for source in (dataframe, csv_path): + curves = load_ml_peg_curves(source) + assert list(curves.homo_nuclear) == ["H"] + assert list(curves.hetero_nuclear) == ["H-He"] + h_curve = curves.homo_nuclear["H"] + np.testing.assert_array_equal(h_curve.distances, [1.0, 2.0]) + np.testing.assert_array_equal(h_curve.forces[:, 0, 0], [1.5, -2.5]) + np.testing.assert_array_equal(h_curve.forces[:, 1, 0], [-1.5, 2.5]) + np.testing.assert_array_equal(h_curve.forces[:, :, 1:], 0) + + homonuclear_only = load_ml_peg_curves(dataframe, include_heteronuclear=False) + assert list(homonuclear_only.homo_nuclear) == ["H"] + assert homonuclear_only.hetero_nuclear == {} + + +@pytest.mark.parametrize( + ("dataframe", "error_match"), + [ + (pd.DataFrame({"pair": ["H-H"]}), "Missing ml-peg diatomics columns"), + (_ml_peg_dataframe(("H2", 1, 0, 0)), "pair labels must have form"), + ], +) +def test_ml_peg_adapter_rejects_schema_errors( + dataframe: pd.DataFrame, + error_match: str, +) -> None: + """ml-peg adapter reports missing columns and malformed pair labels.""" + with pytest.raises(ValueError, match=error_match): + load_ml_peg_curves(dataframe) + + +@pytest.mark.parametrize( + "pair_values", + [[None, "H-H"], [None, None]], + ids=["partly-null", "entirely-null"], +) +def test_ml_peg_adapter_rejects_null_pair_labels( + pair_values: list[str | None], +) -> None: + """Pass null pair labels through grouping so schema validation rejects them.""" + dataframe = pd.DataFrame( + { + "pair": pair_values, + "distance": range(1, len(pair_values) + 1), + "energy": 0.0, + "force_parallel": 0.0, + } + ) + + with pytest.raises(ValueError, match="pair labels must have form"): + load_ml_peg_curves(dataframe) + + +@pytest.mark.parametrize( + ("energies", "error_match"), + [ + ([0.0, 0.0], "duplicate distance values"), + ([0.0, 1.0], "conflicting samples"), + ], + ids=["identical", "conflicting"], +) +def test_ml_peg_adapter_rejects_duplicate_distances( + energies: list[float], error_match: str +) -> None: + """Reject duplicate distances instead of retaining an arbitrary sample.""" + dataframe = _ml_peg_dataframe( + ("H-H", 1, energies[0], 0), + ("H-H", 1, energies[1], 0), + ) + with pytest.raises(ValueError, match=error_match): + load_ml_peg_curves(dataframe) From d28c951acf72be1a90fcdf3b2dc6bd8cd4a87f4d Mon Sep 17 00:00:00 2001 From: janosh Date: Thu, 23 Jul 2026 07:48:08 +0200 Subject: [PATCH 2/7] Tag diatomic tests with framework Allow pytest framework filtering to select the imported Matbench Discovery suite. --- tests/metrics/diatomics/test_analysis.py | 2 ++ tests/metrics/diatomics/test_energy_force.py | 2 ++ tests/metrics/diatomics/test_metrics.py | 2 ++ tests/metrics/diatomics/test_schema.py | 2 ++ 4 files changed, 8 insertions(+) diff --git a/tests/metrics/diatomics/test_analysis.py b/tests/metrics/diatomics/test_analysis.py index 36f0005f6..03f7ff0b4 100644 --- a/tests/metrics/diatomics/test_analysis.py +++ b/tests/metrics/diatomics/test_analysis.py @@ -19,6 +19,8 @@ ) from ml_peg.analysis.physicality.diatomics.metrics import DIATOMIC_METRIC_KEYS +pytestmark = pytest.mark.framework("matbench-discovery") + def _integration_dataframe() -> pd.DataFrame: """Build smooth homo- and heteronuclear current-format curves.""" diff --git a/tests/metrics/diatomics/test_energy_force.py b/tests/metrics/diatomics/test_energy_force.py index 4904eb993..2fa277e02 100644 --- a/tests/metrics/diatomics/test_energy_force.py +++ b/tests/metrics/diatomics/test_energy_force.py @@ -25,6 +25,8 @@ calc_force_total_variation, ) +pytestmark = pytest.mark.framework("matbench-discovery") + _LENGTH_ERROR = re.escape("len(separation_array)=2 != len(value_array)=3") _ENERGY_SHAPE_ERROR = re.escape("energy values must have shape (n,)") _FORCE_SHAPE_ERROR = re.escape("force values must have shape (n, 2, 3)") diff --git a/tests/metrics/diatomics/test_metrics.py b/tests/metrics/diatomics/test_metrics.py index d35918c8b..940817ce9 100644 --- a/tests/metrics/diatomics/test_metrics.py +++ b/tests/metrics/diatomics/test_metrics.py @@ -24,6 +24,8 @@ load_dft_reference_curves, ) +pytestmark = pytest.mark.framework("matbench-discovery") + def _forces_from_energy( distances: np.ndarray, diff --git a/tests/metrics/diatomics/test_schema.py b/tests/metrics/diatomics/test_schema.py index b7aba4eee..36deb5be8 100644 --- a/tests/metrics/diatomics/test_schema.py +++ b/tests/metrics/diatomics/test_schema.py @@ -18,6 +18,8 @@ load_ml_peg_curves, ) +pytestmark = pytest.mark.framework("matbench-discovery") + def _curve_payload() -> dict[str, object]: """Return a minimal two-point MBD curve payload.""" From d315a14dbe09b29ca5e2b4144ae203e2daca4cf6 Mon Sep 17 00:00:00 2001 From: janosh Date: Thu, 23 Jul 2026 09:34:49 +0200 Subject: [PATCH 3/7] Simplify diatomic metric validation Remove redundant finite checks, condense schema validation, and fold overlapping adapter tests while preserving coverage. --- .../user_guide/benchmarks/physicality.rst | 31 +++++++------------ .../physicality/diatomics/metrics/__init__.py | 11 ------- .../physicality/diatomics/metrics/schema.py | 9 +++--- tests/metrics/diatomics/test_schema.py | 30 +++++------------- 4 files changed, 23 insertions(+), 58 deletions(-) diff --git a/docs/source/user_guide/benchmarks/physicality.rst b/docs/source/user_guide/benchmarks/physicality.rst index 5db0f1fed..f16ff7fb4 100644 --- a/docs/source/user_guide/benchmarks/physicality.rst +++ b/docs/source/user_guide/benchmarks/physicality.rst @@ -129,26 +129,17 @@ Metrics Matbench Discovery metrics -------------------------- -A separate result reports the 12 Matbench Discovery diatomic metrics for -homonuclear curves. These do not affect the existing five-metric ml-peg score for -homo- and heteronuclear pairs. - -The six reference-free metrics are tortuosity, force flips, energy-difference flips, -energy jump, force total variation, and force jump. The six PBE-relative metrics are -energy and force MAE, repulsive-wall distance MAE, bond-length error, well-depth -error, and vibrational-frequency error. Element-specific distance windows prevent -the repulsive wall from dominating general metrics, while the dedicated wall metric -uses the wider PBE-supported range. - -The ml-peg CSV adapter maps projected forces to an x-aligned pair: -``-force_parallel`` on atom 0 and ``+force_parallel`` on atom 1. Scoring covers -homonuclear elements H-U except Po, At, Rn, Fr, and Ra. Eight lanthanide PBE curves -with known discontinuities retain reference-free metrics but are excluded from -PBE-relative metrics. Curves with non-finite values in the wider wall window are -skipped. - -These metrics are opt-in and excluded from the weighted table. To evaluate model -CSVs and write JSON: +A separate, opt-in result reports 12 homonuclear metrics without changing the +five-metric homo- and heteronuclear score. The reference-free metrics are tortuosity, +force flips, energy-difference flips, energy jump, force total variation, and force +jump. PBE-relative metrics cover energy and force MAE, repulsive-wall distance MAE, +bond-length error, well-depth error, and vibrational-frequency error. + +Element-specific windows keep the repulsive wall from dominating general metrics. +Scoring covers H-U except Po, At, Rn, Fr, and Ra; known-discontinuous PBE curves keep +reference-free metrics but not PBE-relative ones, and non-finite wall-window curves +are skipped. Projected forces map to ``-force_parallel`` on atom 0 and +``+force_parallel`` on atom 1. To write strict JSON: .. code-block:: python diff --git a/ml_peg/analysis/physicality/diatomics/metrics/__init__.py b/ml_peg/analysis/physicality/diatomics/metrics/__init__.py index 74aa2a060..149d9f23f 100644 --- a/ml_peg/analysis/physicality/diatomics/metrics/__init__.py +++ b/ml_peg/analysis/physicality/diatomics/metrics/__init__.py @@ -215,17 +215,6 @@ def calc_diatomic_metrics( ) continue - # Skip model instabilities in the scored window. - if not ( - np.isfinite(predicted_energies).all() - and np.isfinite(predicted_forces).all() - ): - logger.info( - "Skipping %s diatomic metrics: non-finite curve values", - element_symbol, - ) - continue - energy_args = (predicted_separations, predicted_energies) force_args = (predicted_separations, predicted_forces) # Calls for metrics that need only the predicted curve. diff --git a/ml_peg/analysis/physicality/diatomics/metrics/schema.py b/ml_peg/analysis/physicality/diatomics/metrics/schema.py index bc97383d2..59f753f7d 100644 --- a/ml_peg/analysis/physicality/diatomics/metrics/schema.py +++ b/ml_peg/analysis/physicality/diatomics/metrics/schema.py @@ -50,19 +50,18 @@ def __init__( raise ValueError(f"{name} must have shape (n,), got {values.shape}") n_distances = len(self.distances) - if len(self.energies) != n_distances: + if (n_energies := len(self.energies)) != n_distances: raise ValueError( - f"distance and energy counts differ: {n_distances} != " - f"{len(self.energies)}" + f"distance and energy counts differ: {n_distances} != {n_energies}" ) # Handle forces stored as (1, n_distances*n_atoms, 3) # instead of (n_distances, n_atoms, 3) if self.forces.shape == (1, 2 * n_distances, 3): self.forces = self.forces.reshape(n_distances, 2, 3) - if len(self.forces) != n_distances: + if (n_forces := len(self.forces)) != n_distances: raise ValueError( - f"distance and force counts differ: {n_distances} != {len(self.forces)}" + f"distance and force counts differ: {n_distances} != {n_forces}" ) expected_force_shape = (n_distances, 2, 3) if self.forces.shape != expected_force_shape: diff --git a/tests/metrics/diatomics/test_schema.py b/tests/metrics/diatomics/test_schema.py index 36deb5be8..8591c98f8 100644 --- a/tests/metrics/diatomics/test_schema.py +++ b/tests/metrics/diatomics/test_schema.py @@ -162,6 +162,14 @@ def test_ml_peg_dataframe_and_csv_force_adapter(tmp_path: Path) -> None: [ (pd.DataFrame({"pair": ["H-H"]}), "Missing ml-peg diatomics columns"), (_ml_peg_dataframe(("H2", 1, 0, 0)), "pair labels must have form"), + ( + _ml_peg_dataframe((None, 1, 0, 0), ("H-H", 2, 0, 0)), + "pair labels must have form", + ), + ( + _ml_peg_dataframe((None, 1, 0, 0), (None, 2, 0, 0)), + "pair labels must have form", + ), ], ) def test_ml_peg_adapter_rejects_schema_errors( @@ -173,28 +181,6 @@ def test_ml_peg_adapter_rejects_schema_errors( load_ml_peg_curves(dataframe) -@pytest.mark.parametrize( - "pair_values", - [[None, "H-H"], [None, None]], - ids=["partly-null", "entirely-null"], -) -def test_ml_peg_adapter_rejects_null_pair_labels( - pair_values: list[str | None], -) -> None: - """Pass null pair labels through grouping so schema validation rejects them.""" - dataframe = pd.DataFrame( - { - "pair": pair_values, - "distance": range(1, len(pair_values) + 1), - "energy": 0.0, - "force_parallel": 0.0, - } - ) - - with pytest.raises(ValueError, match="pair labels must have form"): - load_ml_peg_curves(dataframe) - - @pytest.mark.parametrize( ("energies", "error_match"), [ From 94fccd065f474a0228721343e702b2bd8f4404f6 Mon Sep 17 00:00:00 2001 From: janosh Date: Thu, 23 Jul 2026 15:14:06 +0200 Subject: [PATCH 4/7] Record diatomic result provenance Add schema and Matbench Discovery version metadata to serialized diatomic metric results. --- docs/source/user_guide/benchmarks/physicality.rst | 3 ++- .../physicality/diatomics/analyse_diatomics.py | 10 +++++++++- tests/metrics/diatomics/test_analysis.py | 5 +++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/source/user_guide/benchmarks/physicality.rst b/docs/source/user_guide/benchmarks/physicality.rst index f16ff7fb4..c2e42c20f 100644 --- a/docs/source/user_guide/benchmarks/physicality.rst +++ b/docs/source/user_guide/benchmarks/physicality.rst @@ -139,7 +139,8 @@ Element-specific windows keep the repulsive wall from dominating general metrics Scoring covers H-U except Po, At, Rn, Fr, and Ra; known-discontinuous PBE curves keep reference-free metrics but not PBE-relative ones, and non-finite wall-window curves are skipped. Projected forces map to ``-force_parallel`` on atom 0 and -``+force_parallel`` on atom 1. To write strict JSON: +``+force_parallel`` on atom 1. JSON results include schema and source-framework +versions. To write strict JSON: .. code-block:: python diff --git a/ml_peg/analysis/physicality/diatomics/analyse_diatomics.py b/ml_peg/analysis/physicality/diatomics/analyse_diatomics.py index c9beb0aff..decf5f6f1 100644 --- a/ml_peg/analysis/physicality/diatomics/analyse_diatomics.py +++ b/ml_peg/analysis/physicality/diatomics/analyse_diatomics.py @@ -30,6 +30,10 @@ OUT_PATH = APP_ROOT / "data" / "physicality" / "diatomics" CURVE_PATH = OUT_PATH / "curves" +RESULT_SCHEMA_VERSION = 1 +SOURCE_FRAMEWORK_ID = "matbench-discovery" +SOURCE_FRAMEWORK_VERSION = "1.3.1" + METRICS_CONFIG_PATH = Path(__file__).with_name("metrics.yml") DEFAULT_THRESHOLDS, DEFAULT_TOOLTIPS, _ = load_metrics_config(METRICS_CONFIG_PATH) @@ -285,7 +289,11 @@ def evaluate_mbd_diatomic_metrics( } return { - "schema_version": 1, + "schema_version": RESULT_SCHEMA_VERSION, + "source": { + "framework": SOURCE_FRAMEWORK_ID, + "version": SOURCE_FRAMEWORK_VERSION, + }, "curve_scope": "homonuclear", "weighted_in_legacy_score": False, "reference": { diff --git a/tests/metrics/diatomics/test_analysis.py b/tests/metrics/diatomics/test_analysis.py index 03f7ff0b4..5467ba804 100644 --- a/tests/metrics/diatomics/test_analysis.py +++ b/tests/metrics/diatomics/test_analysis.py @@ -72,6 +72,11 @@ def test_separate_mbd_evaluation_is_json_safe_and_homonuclear( interpolate=200, ) + assert result["schema_version"] == 1 + assert result["source"] == { + "framework": "matbench-discovery", + "version": "1.3.1", + } assert result["weighted_in_legacy_score"] is False assert set(result["metric_names"]) == DIATOMIC_METRIC_KEYS model_results = result["models"] From 68ccccde6513de5931d702e6347368f72b92da98 Mon Sep 17 00:00:00 2001 From: janosh Date: Thu, 23 Jul 2026 15:44:48 +0200 Subject: [PATCH 5/7] Remove legacy diatomic schema shims Require the canonical force tensor shape and hyphenated curve section names defined by the new schema. --- .../analysis/physicality/diatomics/metrics/schema.py | 6 +----- tests/metrics/diatomics/test_schema.py | 10 +++++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/ml_peg/analysis/physicality/diatomics/metrics/schema.py b/ml_peg/analysis/physicality/diatomics/metrics/schema.py index 59f753f7d..ce32edfb2 100644 --- a/ml_peg/analysis/physicality/diatomics/metrics/schema.py +++ b/ml_peg/analysis/physicality/diatomics/metrics/schema.py @@ -55,10 +55,6 @@ def __init__( f"distance and energy counts differ: {n_distances} != {n_energies}" ) - # Handle forces stored as (1, n_distances*n_atoms, 3) - # instead of (n_distances, n_atoms, 3) - if self.forces.shape == (1, 2 * n_distances, 3): - self.forces = self.forces.reshape(n_distances, 2, 3) if (n_forces := len(self.forces)) != n_distances: raise ValueError( f"distance and force counts differ: {n_distances} != {n_forces}" @@ -89,7 +85,7 @@ def from_dict(cls, data: dict[str, Any]) -> DiatomicCurves: def make_curves(section: str) -> dict[str, DiatomicCurve]: """Convert one MBD JSON section to typed curves.""" - raw_curves = data.get(section, data.get(section.replace("-", "_"), {})) + raw_curves = data.get(section, {}) key_function = homo_key if section.startswith("homo") else str def curve_distances( diff --git a/tests/metrics/diatomics/test_schema.py b/tests/metrics/diatomics/test_schema.py index 8591c98f8..fa5032538 100644 --- a/tests/metrics/diatomics/test_schema.py +++ b/tests/metrics/diatomics/test_schema.py @@ -39,13 +39,13 @@ def _ml_peg_dataframe( return pd.DataFrame(rows, columns=("pair", "distance", "energy", "force_parallel")) -def test_diatomic_classes_parse_and_reshape() -> None: - """Typed classes convert arrays and reshape legacy flattened forces.""" +def test_diatomic_classes_parse_arrays() -> None: + """Typed classes convert valid curve arrays.""" distances = [1.0, 2.0] energies = [0.1, 0.2] - flattened_forces = np.arange(12).reshape(1, 4, 3) + forces = np.arange(12).reshape(2, 2, 3) - curve = DiatomicCurve(distances, energies, flattened_forces) + curve = DiatomicCurve(distances, energies, forces) assert curve.forces.shape == (2, 2, 3) assert { type(curve.distances), @@ -56,7 +56,7 @@ def test_diatomic_classes_parse_and_reshape() -> None: payload = { "distances": distances, "homo-nuclear": {"H-H": _curve_payload()}, - "hetero_nuclear": {"H-He": _curve_payload()}, + "hetero-nuclear": {"H-He": _curve_payload()}, } curves = DiatomicCurves.from_dict(payload) assert list(curves.homo_nuclear) == ["H"] From bb821b44ab461182f82c8652b821163d245e6843 Mon Sep 17 00:00:00 2001 From: janosh Date: Fri, 24 Jul 2026 14:52:46 +0200 Subject: [PATCH 6/7] Support typing metadata on Python 3.10 Use typing-extensions for NotRequired so diatomic analysis imports under every supported Python version. --- ml_peg/app/utils/utils.py | 3 ++- pyproject.toml | 1 + uv.lock | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ml_peg/app/utils/utils.py b/ml_peg/app/utils/utils.py index c526db085..e53b6ef3d 100644 --- a/ml_peg/app/utils/utils.py +++ b/ml_peg/app/utils/utils.py @@ -8,11 +8,12 @@ import json from numbers import Number from pathlib import Path -from typing import Any, NotRequired, TypedDict +from typing import Any, TypedDict import dash.dash_table.Format as TableFormat from matplotlib import colormaps import numpy as np +from typing_extensions import NotRequired import yaml from ml_peg.models import MODELS_ROOT diff --git a/pyproject.toml b/pyproject.toml index ec7590a04..4a82eeb36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dependencies = [ "scikit-learn<2,>=1.7.2", "tqdm<5,>=4.67.3", "typer<1.0.0,>=0.19.1", + "typing-extensions>=4.0", ] [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index 6f679f234..7cf5f756b 100644 --- a/uv.lock +++ b/uv.lock @@ -5667,6 +5667,7 @@ dependencies = [ { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-ml-peg-grace' and extra == 'extra-6-ml-peg-mattersim') or (extra == 'extra-6-ml-peg-grace' and extra == 'extra-6-ml-peg-uma') or (extra == 'extra-6-ml-peg-mace' and extra == 'extra-6-ml-peg-mattersim') or (extra == 'extra-6-ml-peg-mace' and extra == 'extra-6-ml-peg-uma')" }, { name = "tqdm" }, { name = "typer" }, + { name = "typing-extensions" }, ] [package.optional-dependencies] @@ -5758,6 +5759,7 @@ requires-dist = [ { name = "torch-dftd", marker = "extra == 'd3'", specifier = "==0.5.1" }, { name = "tqdm", specifier = ">=4.67.3,<5" }, { name = "typer", specifier = ">=0.19.1,<1.0.0" }, + { name = "typing-extensions", specifier = ">=4.0" }, ] provides-extras = ["asemolec", "chgnet", "d3", "dpa3", "grace", "mace", "mattersim", "orb", "pet-mad", "uma"] From 43726a9f2ad2a6d9fae0b748bfd4e40475be8014 Mon Sep 17 00:00:00 2001 From: janosh Date: Fri, 24 Jul 2026 14:52:46 +0200 Subject: [PATCH 7/7] Complete diatomic API documentation Document parameters and return values required by the repository's numpydoc validation hook. --- .../diatomics/analyse_diatomics.py | 121 +++++- .../physicality/diatomics/metrics/__init__.py | 72 +++- .../physicality/diatomics/metrics/energy.py | 351 +++++++++++++++++- .../physicality/diatomics/metrics/force.py | 88 ++++- .../physicality/diatomics/metrics/schema.py | 174 ++++++++- 5 files changed, 756 insertions(+), 50 deletions(-) diff --git a/ml_peg/analysis/physicality/diatomics/analyse_diatomics.py b/ml_peg/analysis/physicality/diatomics/analyse_diatomics.py index decf5f6f1..85fc0318b 100644 --- a/ml_peg/analysis/physicality/diatomics/analyse_diatomics.py +++ b/ml_peg/analysis/physicality/diatomics/analyse_diatomics.py @@ -250,7 +250,19 @@ def _load_pair_data() -> dict[str, pd.DataFrame]: def _json_safe_mbd_metrics( metrics_by_element: dict[str, dict[str, float]], ) -> dict[str, dict[str, float | None]]: - """Convert non-finite MBD metrics to strict-JSON null values.""" + """ + Convert non-finite MBD metrics to strict-JSON null values. + + Parameters + ---------- + metrics_by_element + Metric values grouped by element. + + Returns + ------- + dict[str, dict[str, float | None]] + JSON-safe metric values grouped by element. + """ return { element_symbol: { metric_name: (float(metric_value) if np.isfinite(metric_value) else None) @@ -266,7 +278,23 @@ def evaluate_mbd_diatomic_metrics( reference_path: str | Path | None = None, interpolate: bool | int = 200, ) -> dict[str, object]: - """Evaluate 12 homonuclear MBD metrics outside the legacy weighted score.""" + """ + Evaluate 12 homonuclear MBD metrics outside the legacy weighted score. + + Parameters + ---------- + pair_data + Optional model-to-dataframe mapping; calculator outputs are loaded by default. + reference_path + Optional DFT reference-curve path. + interpolate + Whether or how many points to use when interpolating curves. + + Returns + ------- + dict[str, object] + Versioned benchmark results and per-model metrics. + """ resolved_reference_path = Path(reference_path or DEFAULT_DFT_REFERENCE_PATH) reference_curves = load_dft_reference_curves( functional="PBE", @@ -313,7 +341,25 @@ def write_mbd_diatomic_metrics( reference_path: str | Path | None = None, interpolate: bool | int = 200, ) -> dict[str, object]: - """Evaluate MBD metrics and write JSON.""" + """ + Evaluate MBD metrics and write JSON. + + Parameters + ---------- + output_path + Destination JSON path. + pair_data + Optional model-to-dataframe mapping; calculator outputs are loaded by default. + reference_path + Optional DFT reference-curve path. + interpolate + Whether or how many points to use when interpolating curves. + + Returns + ------- + dict[str, object] + Written benchmark results. + """ result = evaluate_mbd_diatomic_metrics( pair_data, reference_path=reference_path, @@ -340,20 +386,46 @@ def write_mbd_diatomic_metrics( y_range=(-20.0, 20.0), ) def persist_diatomics_pair_data() -> dict[str, pd.DataFrame]: - """Persist curve payloads and return per-model dataframes.""" + """ + Persist curve payloads and return per-model dataframes. + + Returns + ------- + dict[str, pd.DataFrame] + Curve dataframes keyed by model name. + """ return _load_pair_data() @pytest.fixture def diatomics_pair_data_fixture() -> dict[str, pd.DataFrame]: - """Load curve data and persist gallery assets for pytest.""" + """ + Load curve data and persist gallery assets for pytest. + + Returns + ------- + dict[str, pd.DataFrame] + Curve dataframes keyed by model name. + """ return persist_diatomics_pair_data() def collect_metrics( pair_data: dict[str, pd.DataFrame] | None = None, ) -> pd.DataFrame: - """Aggregate metrics across all homo- and heteronuclear pairs by model.""" + """ + Aggregate metrics across all homo- and heteronuclear pairs by model. + + Parameters + ---------- + pair_data + Optional curve dataframes keyed by model name. + + Returns + ------- + pd.DataFrame + One row of aggregated metrics per model. + """ metrics_rows: list[dict[str, float | str]] = [] OUT_PATH.mkdir(parents=True, exist_ok=True) @@ -374,7 +446,19 @@ def collect_metrics( def diatomics_collection( diatomics_pair_data_fixture: dict[str, pd.DataFrame], ) -> pd.DataFrame: - """Collect per-model diatomic metrics.""" + """ + Collect per-model diatomic metrics. + + Parameters + ---------- + diatomics_pair_data_fixture + Curve dataframes keyed by model name. + + Returns + ------- + pd.DataFrame + One row of aggregated metrics per model. + """ return collect_metrics(diatomics_pair_data_fixture) @@ -388,7 +472,19 @@ def diatomics_collection( def metrics( diatomics_collection: pd.DataFrame, ) -> dict[str, dict]: - """Return metric-name mappings by model.""" + """ + Return metric-name mappings by model. + + Parameters + ---------- + diatomics_collection + Aggregated metrics with one row per model. + + Returns + ------- + dict[str, dict] + Model values keyed by metric name. + """ return { column: dict( zip( @@ -407,7 +503,14 @@ def metrics( @pytest.mark.framework("mace-multihead") def test_diatomics(metrics: dict[str, dict]) -> None: - """Write diatomic benchmark metadata after fixture evaluation.""" + """ + Write diatomic benchmark metadata after fixture evaluation. + + Parameters + ---------- + metrics + Evaluated metric mappings supplied by pytest. + """ mock_data = load_model_data("mock") # Write out info.json with open(OUT_PATH / "info.json", "w") as f: diff --git a/ml_peg/analysis/physicality/diatomics/metrics/__init__.py b/ml_peg/analysis/physicality/diatomics/metrics/__init__.py index 149d9f23f..a437ab1a2 100644 --- a/ml_peg/analysis/physicality/diatomics/metrics/__init__.py +++ b/ml_peg/analysis/physicality/diatomics/metrics/__init__.py @@ -1,4 +1,5 @@ -"""Diatomic potential-energy metrics adapted from Matbench Discovery. +""" +Diatomic potential-energy metrics adapted from Matbench Discovery. The smoothness approach follows Stenczel et al., https://arxiv.org/abs/2401.00096. """ @@ -82,7 +83,23 @@ def find_low_quality_dft_refs( min_energy_jump: float = 1.5, min_energy_flips: int = 3, ) -> set[str]: - """Find non-finite or discontinuous DFT references unsuitable for scoring.""" + """ + Find non-finite or discontinuous DFT references unsuitable for scoring. + + Parameters + ---------- + ref_curves + DFT reference curves. + min_energy_jump + Minimum discontinuity magnitude used by the quality gate. + min_energy_flips + Minimum number of energy-difference flips used by the quality gate. + + Returns + ------- + set[str] + Element symbols with unsuitable reference curves. + """ low_quality: set[str] = set() for element_symbol, curve in ref_curves.homo_nuclear.items(): separations = curve.distances @@ -113,7 +130,23 @@ def eval_window( *, r_min_factor: float = 0.9, ) -> tuple[float, float]: - """Return the covalent-to-van-der-Waals evaluation window in Å.""" + """ + Return the covalent-to-van-der-Waals evaluation window in Å. + + Parameters + ---------- + elem_symbol + Element or pair label. + seps_max + Largest available separation. + r_min_factor + Covalent-radius multiplier for the lower bound. + + Returns + ------- + tuple[float, float] + Lower and upper evaluation bounds. + """ atomic_number = atomic_numbers[elem_symbol.split("-", maxsplit=1)[0]] covalent_radius = ( covalent_radii[atomic_number] if atomic_number < len(covalent_radii) else np.nan @@ -134,9 +167,26 @@ def calc_diatomic_metrics( *, interpolate: bool | int = False, ) -> dict[str, dict[str, float]]: - """Calculate requested metrics for supported homonuclear curves by element. + """ + Calculate requested metrics for supported homonuclear curves by element. Low-quality references receive self-consistency metrics but no ``pbe_*`` metrics. + + Parameters + ---------- + ref_curves + Optional DFT reference curves. + pred_curves + Predicted diatomic curves. + metrics + Optional mapping of requested metric names to keyword arguments. + interpolate + Whether or how many points to use when interpolating paired curves. + + Returns + ------- + dict[str, dict[str, float]] + Metric values grouped by element. """ requested_metric_keys = ( set(metrics) if metrics is not None else set(DIATOMIC_METRIC_KEYS) @@ -316,7 +366,19 @@ def calc_diatomic_metrics( def aggregate_finite_means( metrics_by_element: dict[str, dict[str, float]], ) -> dict[str, float]: - """Average finite values for every metric present, to four significant digits.""" + """ + Average finite values for every metric present, to four significant digits. + + Parameters + ---------- + metrics_by_element + Metric values grouped by element. + + Returns + ------- + dict[str, float] + Finite metric means keyed by metric name. + """ metric_means: dict[str, float] = {} metric_names = dict.fromkeys( metric_name diff --git a/ml_peg/analysis/physicality/diatomics/metrics/energy.py b/ml_peg/analysis/physicality/diatomics/metrics/energy.py index e14d85121..fb14bead3 100644 --- a/ml_peg/analysis/physicality/diatomics/metrics/energy.py +++ b/ml_peg/analysis/physicality/diatomics/metrics/energy.py @@ -18,7 +18,25 @@ def _validate_diatomic_curve( normalize_energy: bool = False, value_kind: Literal["energy", "force"] = "energy", ) -> tuple[np.ndarray, np.ndarray]: - """Validate, sort, and optionally far-field-normalize a sampled curve.""" + """ + Validate, sort, and optionally far-field-normalize a sampled curve. + + Parameters + ---------- + separations + Sample separations. + values + Sampled energy or force values. + normalize_energy + Whether to shift the last energy sample to zero. + value_kind + Kind of sampled values, used for shape validation. + + Returns + ------- + tuple[np.ndarray, np.ndarray] + Sorted separations and values. + """ separation_array = np.asarray(separations) value_array = np.asarray(values) @@ -78,7 +96,19 @@ def _validate_diatomic_curve( def _interpolation_point_count(interpolate: bool | int) -> int: - """Return the requested interpolation size, validating a two-point minimum.""" + """ + Return the requested interpolation size, validating a two-point minimum. + + Parameters + ---------- + interpolate + Whether or how many interpolation points to use. + + Returns + ------- + int + Number of interpolation points. + """ n_points = 100 if interpolate is True else int(interpolate) if n_points < 2: raise ValueError("interpolate must request at least 2 points") @@ -94,7 +124,29 @@ def _common_grid_curve_pair( interpolate: bool | int, value_kind: Literal["energy", "force"] = "energy", ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: - """Validate two curves and optionally interpolate their common interval.""" + """ + Validate two curves and optionally interpolate their common interval. + + Parameters + ---------- + separations_ref + Reference-curve separations. + values_ref + Reference-curve values. + separations_pred + Predicted-curve separations. + values_pred + Predicted-curve values. + interpolate + Whether or how many common-grid points to use. + value_kind + Kind of sampled values, used for shape validation. + + Returns + ------- + tuple[np.ndarray, np.ndarray, np.ndarray] + Common separations, reference values, and predicted values. + """ separations_ref, values_ref = _validate_diatomic_curve( separations_ref, values_ref, value_kind=value_kind ) @@ -124,7 +176,21 @@ def _common_grid_curve_pair( ) def interpolate_values(separations: np.ndarray, values: np.ndarray) -> np.ndarray: - """Interpolate all flattened value components onto ``common_grid``.""" + """ + Interpolate all flattened value components onto ``common_grid``. + + Parameters + ---------- + separations + Source separations. + values + Source values. + + Returns + ------- + np.ndarray + Values interpolated onto the common grid. + """ flattened_values = values.reshape(len(values), -1) interpolated = np.column_stack( [ @@ -146,7 +212,19 @@ def interpolate_values(separations: np.ndarray, values: np.ndarray) -> np.ndarra def _binding_energy(energies: np.ndarray) -> float: - """Return well depth relative to the largest sampled separation.""" + """ + Return well depth relative to the largest sampled separation. + + Parameters + ---------- + energies + Sampled energies ordered by separation. + + Returns + ------- + float + Binding energy. + """ return float(energies[-1] - np.min(energies)) @@ -156,7 +234,25 @@ def _validated_energy_pair( seps_pred: ArrayLike, energy_pred: ArrayLike, ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: - """Validate two independently sampled energy curves.""" + """ + Validate two independently sampled energy curves. + + Parameters + ---------- + seps_ref + Reference separations. + energy_ref + Reference energies. + seps_pred + Predicted separations. + energy_pred + Predicted energies. + + Returns + ------- + tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray] + Sorted reference separations and energies followed by predicted values. + """ separations_ref, energies_ref = _validate_diatomic_curve(seps_ref, energy_ref) separations_pred, energies_pred = _validate_diatomic_curve(seps_pred, energy_pred) return separations_ref, energies_ref, separations_pred, energies_pred @@ -167,7 +263,23 @@ def _quadratic_well_fit( energies: ArrayLike, n_fit_points: int = 5, ) -> tuple[float, float]: - """Estimate equilibrium separation and curvature by local quadratic fit.""" + """ + Estimate equilibrium separation and curvature by local quadratic fit. + + Parameters + ---------- + separations + Sample separations. + energies + Sample energies. + n_fit_points + Maximum number of local points to fit. + + Returns + ------- + tuple[float, float] + Equilibrium separation and fitted curvature. + """ separations, energies = _validate_diatomic_curve(separations, energies) minimum_index = int(np.argmin(energies)) if len(separations) < 3: @@ -200,7 +312,23 @@ def _repulsive_radius_at_threshold( energies: ArrayLike, threshold_ev: float, ) -> float: - """Return the repulsive radius at an energy threshold, or NaN if unreached.""" + """ + Return the repulsive radius at an energy threshold, or NaN if unreached. + + Parameters + ---------- + separations + Sample separations. + energies + Sample energies. + threshold_ev + Energy above the curve minimum. + + Returns + ------- + float + Interpolated repulsive radius or NaN. + """ separations, energies = _validate_diatomic_curve(separations, energies) minimum_index = int(np.argmin(energies)) if minimum_index == 0: @@ -223,9 +351,28 @@ def calc_pbe_wall_dist_mae( *, thresholds_ev: tuple[float, ...] = PBE_WALL_ENERGY_THRESHOLDS_EV, ) -> float: - """Calculate mean PBE wall-radius error over reachable energy thresholds. + """ + Calculate mean PBE wall-radius error over reachable energy thresholds. A missing predicted crossing receives the full reference-radius error. + + Parameters + ---------- + seps_ref + Reference separations. + energy_ref + Reference energies. + seps_pred + Predicted separations. + energy_pred + Predicted energies. + thresholds_ev + Energy thresholds above the well minimum. + + Returns + ------- + float + Mean absolute wall-radius error. """ errors: list[float] = [] for threshold_ev in thresholds_ev: @@ -249,7 +396,27 @@ def calc_pbe_energy_mae( *, interpolate: bool | int = 200, ) -> float: - """Calculate PBE energy MAE after optional interpolation and far-field alignment.""" + """ + Calculate PBE energy MAE after optional interpolation and far-field alignment. + + Parameters + ---------- + seps_ref + Reference separations. + energy_ref + Reference energies. + seps_pred + Predicted separations. + energy_pred + Predicted energies. + interpolate + Whether or how many common-grid points to use. + + Returns + ------- + float + Mean absolute energy error. + """ _, energy_ref, energy_pred = _common_grid_curve_pair( seps_ref, energy_ref, @@ -270,7 +437,27 @@ def calc_pbe_bond_length_error( *, min_ref_binding_ev: float = 0.05, ) -> float: - """Calculate absolute PBE equilibrium-distance error, or NaN if unbound.""" + """ + Calculate absolute PBE equilibrium-distance error, or NaN if unbound. + + Parameters + ---------- + seps_ref + Reference separations. + energy_ref + Reference energies. + seps_pred + Predicted separations. + energy_pred + Predicted energies. + min_ref_binding_ev + Minimum reference binding energy required for scoring. + + Returns + ------- + float + Absolute equilibrium-distance error or NaN. + """ separations_ref, energy_ref, separations_pred, energy_pred = _validated_energy_pair( seps_ref, energy_ref, seps_pred, energy_pred ) @@ -289,7 +476,27 @@ def calc_pbe_well_depth_error( *, min_ref_binding_ev: float = 0.05, ) -> float: - """Calculate absolute PBE well-depth error, or NaN if unbound.""" + """ + Calculate absolute PBE well-depth error, or NaN if unbound. + + Parameters + ---------- + seps_ref + Reference separations. + energy_ref + Reference energies. + seps_pred + Predicted separations. + energy_pred + Predicted energies. + min_ref_binding_ev + Minimum reference binding energy required for scoring. + + Returns + ------- + float + Absolute well-depth error or NaN. + """ _, energy_ref, _, energy_pred = _validated_energy_pair( seps_ref, energy_ref, seps_pred, energy_pred ) @@ -303,7 +510,21 @@ def _vibrational_wavenumber_cm( element_symbol: str, curvature_ev_per_a2: float, ) -> float: - """Convert a homonuclear force constant to harmonic wavenumber in cm⁻¹.""" + """ + Convert a homonuclear force constant to harmonic wavenumber in cm⁻¹. + + Parameters + ---------- + element_symbol + Element or homonuclear pair label. + curvature_ev_per_a2 + Energy-well curvature in eV/Ų. + + Returns + ------- + float + Harmonic wavenumber in cm⁻¹ or NaN. + """ if not np.isfinite(curvature_ev_per_a2) or curvature_ev_per_a2 <= 0: return np.nan atomic_symbol = element_symbol.split("-", maxsplit=1)[0] @@ -324,7 +545,29 @@ def calc_pbe_vib_freq_error( *, min_ref_binding_ev: float = 0.05, ) -> float: - """Calculate absolute PBE vibrational-wavenumber error, or NaN if unbound.""" + """ + Calculate absolute PBE vibrational-wavenumber error, or NaN if unbound. + + Parameters + ---------- + elem_symbol + Element or homonuclear pair label. + seps_ref + Reference separations. + energy_ref + Reference energies. + seps_pred + Predicted separations. + energy_pred + Predicted energies. + min_ref_binding_ev + Minimum reference binding energy required for scoring. + + Returns + ------- + float + Absolute vibrational-wavenumber error or NaN. + """ separations_ref, energy_ref, separations_pred, energy_pred = _validated_energy_pair( seps_ref, energy_ref, seps_pred, energy_pred ) @@ -338,7 +581,21 @@ def calc_pbe_vib_freq_error( def calc_tortuosity(seps: ArrayLike, energies: ArrayLike) -> float: - """Calculate projected arc-chord energy tortuosity, or NaN if constant.""" + """ + Calculate projected arc-chord energy tortuosity, or NaN if constant. + + Parameters + ---------- + seps + Sample separations. + energies + Sample energies. + + Returns + ------- + float + Curve tortuosity or NaN. + """ _, energies = _validate_diatomic_curve(seps, energies) total_energy_variation = np.sum(np.abs(np.diff(energies))) @@ -356,7 +613,21 @@ def _threshold_diff_signs( values: np.ndarray, threshold: float = 1e-3, ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: - """Return nonzero thresholded differences, their signs, and flip mask.""" + """ + Return nonzero thresholded differences, their signs, and flip mask. + + Parameters + ---------- + values + Sample values. + threshold + Magnitudes below this value are treated as zero. + + Returns + ------- + tuple[np.ndarray, np.ndarray, np.ndarray] + Nonzero differences, their signs, and adjacent sign-flip mask. + """ differences = np.diff(values) differences[np.abs(differences) < threshold] = 0 signs = np.sign(differences) @@ -367,7 +638,21 @@ def _threshold_diff_signs( def _jump_magnitude(values: np.ndarray, threshold: float = 1e-3) -> float: - """Sum adjacent step magnitudes at sign-flip points.""" + """ + Sum adjacent step magnitudes at sign-flip points. + + Parameters + ---------- + values + Sample values. + threshold + Difference magnitudes below this value are ignored. + + Returns + ------- + float + Total adjacent jump magnitude. + """ differences, _, flips = _threshold_diff_signs(values, threshold) return float( np.abs(differences[:-1][flips]).sum() + np.abs(differences[1:][flips]).sum() @@ -378,13 +663,41 @@ def calc_energy_diff_flips( seps: ArrayLike, energies: ArrayLike, ) -> float: - """Calculate the number of thresholded energy-difference sign flips.""" + """ + Calculate the number of thresholded energy-difference sign flips. + + Parameters + ---------- + seps + Sample separations. + energies + Sample energies. + + Returns + ------- + float + Number of energy-difference sign flips. + """ _, energies = _validate_diatomic_curve(seps, energies) _, _, flips = _threshold_diff_signs(energies) return float(np.sum(flips)) def calc_energy_jump(seps: ArrayLike, energies: ArrayLike) -> float: - """Calculate total energy-step magnitude around sign-flip points.""" + """ + Calculate total energy-step magnitude around sign-flip points. + + Parameters + ---------- + seps + Sample separations. + energies + Sample energies. + + Returns + ------- + float + Total energy-step magnitude. + """ _, energies = _validate_diatomic_curve(seps, energies) return _jump_magnitude(energies) diff --git a/ml_peg/analysis/physicality/diatomics/metrics/force.py b/ml_peg/analysis/physicality/diatomics/metrics/force.py index b64dd1a95..bbc2df10d 100644 --- a/ml_peg/analysis/physicality/diatomics/metrics/force.py +++ b/ml_peg/analysis/physicality/diatomics/metrics/force.py @@ -20,7 +20,27 @@ def calc_force_mae( *, interpolate: bool | int = False, ) -> float: - """Calculate force MAE, optionally interpolating over the shared range.""" + """ + Calculate force MAE, optionally interpolating over the shared range. + + Parameters + ---------- + seps_ref + Reference separations. + f_ref + Reference Cartesian forces. + seps_pred + Predicted separations. + f_pred + Predicted Cartesian forces. + interpolate + Whether or how many common-grid points to use. + + Returns + ------- + float + Mean absolute force error. + """ _, f_ref, f_pred = _common_grid_curve_pair( seps_ref, f_ref, @@ -33,7 +53,21 @@ def calc_force_mae( def _radial_forces(seps: ArrayLike, forces: np.ndarray) -> np.ndarray: - """Validate a force curve and return first-atom radial forces.""" + """ + Validate a force curve and return first-atom radial forces. + + Parameters + ---------- + seps + Sample separations. + forces + Cartesian forces for both atoms. + + Returns + ------- + np.ndarray + First-atom radial forces. + """ _, forces = _validate_diatomic_curve(seps, forces, value_kind="force") return forces[:, 0, 0] # x-component of force on first atom @@ -43,7 +77,23 @@ def calc_force_flips( forces: np.ndarray, threshold: float = 1e-2, # 10meV/A threshold as in reference code ) -> float: - """Count thresholded direction changes in the first atom's radial force.""" + """ + Count thresholded direction changes in the first atom's radial force. + + Parameters + ---------- + seps + Sample separations. + forces + Cartesian forces for both atoms. + threshold + Force magnitudes below this value are treated as zero. + + Returns + ------- + float + Number of radial-force direction changes. + """ radial_forces = _radial_forces(seps, forces).copy() radial_forces[np.abs(radial_forces) < threshold] = 0 force_signs = np.sign(radial_forces[radial_forces != 0]) @@ -54,7 +104,21 @@ def calc_force_total_variation( seps: ArrayLike, forces: np.ndarray, ) -> float: - """Calculate total variation in the first atom's radial force.""" + """ + Calculate total variation in the first atom's radial force. + + Parameters + ---------- + seps + Sample separations. + forces + Cartesian forces for both atoms. + + Returns + ------- + float + Total radial-force variation. + """ return float(np.sum(np.abs(np.diff(_radial_forces(seps, forces))))) @@ -62,5 +126,19 @@ def calc_force_jump( seps: ArrayLike, forces: np.ndarray, ) -> float: - """Calculate total radial-force step magnitude around sign-flip points.""" + """ + Calculate total radial-force step magnitude around sign-flip points. + + Parameters + ---------- + seps + Sample separations. + forces + Cartesian forces for both atoms. + + Returns + ------- + float + Total radial-force step magnitude. + """ return _jump_magnitude(_radial_forces(seps, forces), threshold=0) diff --git a/ml_peg/analysis/physicality/diatomics/metrics/schema.py b/ml_peg/analysis/physicality/diatomics/metrics/schema.py index ce32edfb2..c4b74e7a3 100644 --- a/ml_peg/analysis/physicality/diatomics/metrics/schema.py +++ b/ml_peg/analysis/physicality/diatomics/metrics/schema.py @@ -19,13 +19,36 @@ def homo_key(formula: str) -> str: - """Collapse a homonuclear pair label such as ``H-H`` to its element key.""" + """ + Collapse a homonuclear pair label such as ``H-H`` to its element key. + + Parameters + ---------- + formula + Element or pair label. + + Returns + ------- + str + Element key for homonuclear pairs, otherwise the original label. + """ element_1, separator, element_2 = formula.partition("-") return element_1 if separator and element_1 == element_2 else formula class DiatomicCurve: - """Store one validated diatomic energy and Cartesian-force curve.""" + """ + Store one validated diatomic energy and Cartesian-force curve. + + Parameters + ---------- + distances + Sample separations. + energies + Sample energies. + forces + Cartesian forces for both atoms. + """ distances: np.ndarray energies: np.ndarray @@ -37,7 +60,18 @@ def __init__( energies: ArrayLike, forces: ArrayLike, ) -> None: - """Convert curve data to arrays and validate shapes and sample counts.""" + """ + Convert curve data to arrays and validate shapes and sample counts. + + Parameters + ---------- + distances + Sample separations. + energies + Sample energies. + forces + Cartesian forces for both atoms. + """ self.distances = np.asarray(distances) self.energies = np.asarray(energies) self.forces = np.asarray(forces) @@ -77,14 +111,38 @@ class DiatomicCurves: @classmethod def from_dict(cls, data: dict[str, Any]) -> DiatomicCurves: - """Parse MBD JSON curves, requiring per-curve grids to be ordered subsets.""" + """ + Parse MBD JSON curves, requiring per-curve grids to be ordered subsets. + + Parameters + ---------- + data + Decoded MBD curve payload. + + Returns + ------- + DiatomicCurves + Validated homo- and heteronuclear curves. + """ distances = np.asarray(data["distances"]) grid_position_by_distance = { float(distance): index for index, distance in enumerate(distances) } def make_curves(section: str) -> dict[str, DiatomicCurve]: - """Convert one MBD JSON section to typed curves.""" + """ + Convert one MBD JSON section to typed curves. + + Parameters + ---------- + section + MBD JSON section name. + + Returns + ------- + dict[str, DiatomicCurve] + Typed curves keyed by normalized formula. + """ raw_curves = data.get(section, {}) key_function = homo_key if section.startswith("homo") else str @@ -92,7 +150,21 @@ def curve_distances( formula: str, curve: dict[str, Any], ) -> np.ndarray: - """Return an ordered per-curve subset of the top-level grid.""" + """ + Return an ordered per-curve subset of the top-level grid. + + Parameters + ---------- + formula + Curve formula used in validation errors. + curve + Raw curve payload. + + Returns + ------- + np.ndarray + Ordered curve-specific distance grid. + """ curve_distance_array = np.asarray(curve.get("distances", distances)) # off-grid points map to -1; valid subsets have strictly # increasing grid positions @@ -127,7 +199,19 @@ def curve_distances( def _load_json(path: StrPath) -> dict[str, Any]: - """Load a JSON or gzipped JSON object.""" + """ + Load a JSON or gzipped JSON object. + + Parameters + ---------- + path + JSON or gzipped JSON path. + + Returns + ------- + dict[str, Any] + Decoded JSON object. + """ string_path = os.fspath(path) open_function = gzip.open if string_path.endswith(".gz") else open with open_function(string_path, mode="rt", encoding="utf-8") as file: @@ -135,7 +219,19 @@ def _load_json(path: StrPath) -> dict[str, Any]: def load_mbd_json(path: StrPath) -> DiatomicCurves: - """Load MBD-format predicted curves from JSON or gzipped JSON.""" + """ + Load MBD-format predicted curves from JSON or gzipped JSON. + + Parameters + ---------- + path + JSON or gzipped JSON path. + + Returns + ------- + DiatomicCurves + Validated predicted curves. + """ return DiatomicCurves.from_dict(_load_json(path)) @@ -143,7 +239,21 @@ def load_dft_reference_curves( functional: str = "PBE", ref_path: StrPath | None = None, ) -> DiatomicCurves: - """Load bundled or custom DFT reference curves for one functional.""" + """ + Load bundled or custom DFT reference curves for one functional. + + Parameters + ---------- + functional + Density functional key in the reference payload. + ref_path + Optional custom reference path. + + Returns + ------- + DiatomicCurves + DFT reference curves. + """ reference_path = ref_path or DEFAULT_DFT_REFERENCE_PATH references = _load_json(reference_path)[functional] return DiatomicCurves( @@ -160,7 +270,19 @@ def load_dft_reference_curves( def _parse_pair_label(pair_label: str) -> tuple[str, str]: - """Parse an ``Element-Element`` pair label.""" + """ + Parse an ``Element-Element`` pair label. + + Parameters + ---------- + pair_label + Pair label to parse. + + Returns + ------- + tuple[str, str] + First and second element symbols. + """ elements = pair_label.split("-") if len(elements) != 2 or not all(elements): raise ValueError( @@ -174,7 +296,21 @@ def curves_from_ml_peg_dataframe( *, include_heteronuclear: bool = True, ) -> DiatomicCurves: - """Convert an ml-peg dataframe to x-aligned two-atom force curves.""" + """ + Convert an ml-peg dataframe to x-aligned two-atom force curves. + + Parameters + ---------- + dataframe + Diatomic samples with pair, distance, energy, and projected force columns. + include_heteronuclear + Whether to include heteronuclear pairs. + + Returns + ------- + DiatomicCurves + Converted homo- and heteronuclear curves. + """ required_columns = {"pair", "distance", "energy", "force_parallel"} missing_columns = required_columns - set(dataframe.columns) if missing_columns: @@ -238,7 +374,21 @@ def load_ml_peg_curves( *, include_heteronuclear: bool = True, ) -> DiatomicCurves: - """Load current ml-peg diatomic curves from a dataframe or CSV.""" + """ + Load current ml-peg diatomic curves from a dataframe or CSV. + + Parameters + ---------- + source + Diatomic dataframe or CSV path. + include_heteronuclear + Whether to include heteronuclear pairs. + + Returns + ------- + DiatomicCurves + Converted homo- and heteronuclear curves. + """ dataframe = ( source if isinstance(source, pd.DataFrame) else pd.read_csv(os.fspath(source)) )