From c1013a122b4336bd434c6eac4e7881929ad26202 Mon Sep 17 00:00:00 2001 From: Mariia Radova <7radians@gmail.com> Date: Mon, 9 Feb 2026 14:22:23 +0000 Subject: [PATCH 1/6] Add diamond phonons benchmark --- .../user_guide/benchmarks/bulk_crystal.rst | 55 + .../analyse_diamond_phonons.py | 479 ++++ .../bulk_crystal/diamond_phonons/metrics.yml | 16 + .../diamond_phonons/app_diamond_phonons.py | 185 ++ .../diamond_interactive_helpers.py | 266 ++ .../diamond_phonons/calc_diamond_phonons.py | 261 ++ .../diamond_phonons/data/dft_band.npz | Bin 0 -> 18668 bytes .../diamond_phonons/data/diamond.yaml | 2135 +++++++++++++++++ 8 files changed, 3397 insertions(+) create mode 100644 ml_peg/analysis/bulk_crystal/diamond_phonons/analyse_diamond_phonons.py create mode 100644 ml_peg/analysis/bulk_crystal/diamond_phonons/metrics.yml create mode 100644 ml_peg/app/bulk_crystal/diamond_phonons/app_diamond_phonons.py create mode 100644 ml_peg/app/bulk_crystal/diamond_phonons/diamond_interactive_helpers.py create mode 100644 ml_peg/calcs/bulk_crystal/diamond_phonons/calc_diamond_phonons.py create mode 100644 ml_peg/calcs/bulk_crystal/diamond_phonons/data/dft_band.npz create mode 100644 ml_peg/calcs/bulk_crystal/diamond_phonons/data/diamond.yaml diff --git a/docs/source/user_guide/benchmarks/bulk_crystal.rst b/docs/source/user_guide/benchmarks/bulk_crystal.rst index 575bbcc11..fc3ada371 100644 --- a/docs/source/user_guide/benchmarks/bulk_crystal.rst +++ b/docs/source/user_guide/benchmarks/bulk_crystal.rst @@ -115,3 +115,58 @@ Reference data: * Same as input data * PBE + + +Diamond phonons +=============== + +Summary +------- + +Performance in predicting the phonon dispersion of bulk diamond (carbon). + +The benchmark evaluates the accuracy of phonon frequencies along a fixed +high-symmetry path in the Brillouin zone for a single reference crystal +structure of diamond. + + +Metrics +------- + +1. Band MAE + +Mean absolute error (MAE) between predicted and reference phonon frequencies. + +For bulk diamond, the phonon band structure is computed for each model along the same +q-point path as the reference calculation. At each q-point, the six phonon frequencies +are compared to the reference frequencies after sorting the modes to avoid branch +labelling ambiguities. The MAE is evaluated over all q-points and all phonon branches. + + +2. Band RMSE + +Root mean squared error (RMSE) between predicted and reference phonon frequencies. + +The RMSE is computed using the same sorted, mode-unlabelled comparison procedure as in +(1), over all q-points and all phonon branches. + + +Computational cost +------------------ + +Medium: tests typically take a few minutes to run on CPU. + + +Data availability +----------------- + +Input structures: + +* A primitive bulk diamond unit cell containing two carbon atoms, used to generate a + phonopy displacement dataset on a 4×4×4 supercell. + +Reference data: + +* DFT phonon band structure for bulk diamond along a fixed high-symmetry path, provided + as ``dft_band.npz``. +* RSCAN. diff --git a/ml_peg/analysis/bulk_crystal/diamond_phonons/analyse_diamond_phonons.py b/ml_peg/analysis/bulk_crystal/diamond_phonons/analyse_diamond_phonons.py new file mode 100644 index 000000000..847168e41 --- /dev/null +++ b/ml_peg/analysis/bulk_crystal/diamond_phonons/analyse_diamond_phonons.py @@ -0,0 +1,479 @@ +""" +Analyse diamond phonon dispersion benchmark (bands only). + +Notes +----- +This analysis produces two JSON artifacts used by the diamond phonon Dash app: + +- ``diamond_phonons_bands_table.json``: metrics table for the Dash table component. +- ``diamond_phonons_bands_interactive.json``: interactive scatter dataset consumed by + the PhononApp callbacks. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import numpy as np +import pytest +import yaml # type: ignore + +from ml_peg.analysis.utils.decorators import build_table, cell_to_scatter +from ml_peg.app import APP_ROOT +from ml_peg.calcs import CALCS_ROOT +from ml_peg.models.get_models import get_model_names +from ml_peg.models.models import current_models + +MODELS = get_model_names(current_models) + +CATEGORY = "bulk_crystal" +BENCH = "diamond_phonons" + +CALC_PATH = CALCS_ROOT / CATEGORY / BENCH / "outputs" +CALC_DATA = CALCS_ROOT / CATEGORY / BENCH / "data" +OUT_PATH = APP_ROOT / "data" / CATEGORY / BENCH + +THZ_TO_CM1 = 33.35640951981521 +NBANDS = 6 + +SCATTER_FILENAME = OUT_PATH / "diamond_phonons_bands_interactive.json" + +# Internal keys used in the interactive dataset (not shown to user) +METRIC_KEY_MAE = "band_mae" +METRIC_KEY_RMSE = "band_rmse" + +# Visible labels (must match the table column headers exactly) +METRIC_LABEL_MAE = "Band MAE" +METRIC_LABEL_RMSE = "Band RMSE" + +METRICS_YML = Path(__file__).with_name("metrics.yml") +with METRICS_YML.open("r", encoding="utf8") as f: + _cfg = yaml.safe_load(f) or {} + +_metric_specs = _cfg.get("metrics", {}) +if not isinstance(_metric_specs, dict) or not _metric_specs: + raise ValueError(f"{METRICS_YML} must contain a 'metrics:' mapping.") + +THRESHOLDS = { + name: { + "good": spec.get("good"), + "bad": spec.get("bad"), + "unit": spec.get("unit", "cm-1"), + } + for name, spec in _metric_specs.items() +} + + +def _load_reference_npz(path: Path) -> dict[str, Any]: + """ + Load DFT reference bands from an NPZ file. + + Parameters + ---------- + path + Path to ``dft_band.npz``. Must contain a ``frequencies`` array in cm-1 with + shape ``(Nq, NBANDS)`` or ``(1, Nq, NBANDS)``. + + Returns + ------- + dict[str, Any] + Mapping with keys: + + - ``freqs``: array of shape ``(Nq, NBANDS)`` in cm-1 + - ``units``: ``"cm-1"`` + - ``path``: input path + """ + if not path.exists(): + raise FileNotFoundError(f"Missing DFT reference: {path}") + + d = np.load(path, allow_pickle=False) + if "frequencies" not in d.files: + raise KeyError(f"{path}: missing 'frequencies'. Found keys: {list(d.files)}") + + freqs_cm1 = np.asarray(d["frequencies"], dtype=float) + + # Allow legacy (1, Nq, NBANDS) storage + if freqs_cm1.ndim == 3 and freqs_cm1.shape[0] == 1: + freqs_cm1 = freqs_cm1[0] + + if freqs_cm1.ndim != 2 or freqs_cm1.shape[1] != NBANDS: + msg = f"{path}: expected (Nq, {NBANDS}) frequencies, got {freqs_cm1.shape}" + raise ValueError(msg) + + if not np.isfinite(freqs_cm1).all(): + raise ValueError(f"{path}: contains non-finite reference frequencies.") + + return {"freqs": freqs_cm1, "units": "cm-1", "path": path} + + +def _load_band_yaml(path: Path) -> dict[str, Any]: + """ + Load phonopy ``band.yaml`` and return frequencies in THz. + + Parameters + ---------- + path + Path to a phonopy ``band.yaml``. + + Returns + ------- + dict[str, Any] + Mapping with keys ``freqs`` (``(Nq, NBANDS)``), ``units`` (``"THz"``), + and ``path``. + """ + if not path.exists(): + raise FileNotFoundError(f"Missing predicted band.yaml: {path}") + + with path.open("r", encoding="utf8") as f: + y = yaml.safe_load(f) + + phonon = y.get("phonon") + if not isinstance(phonon, list) or not phonon: + raise ValueError( + f"{path} does not look like a phonopy band.yaml (missing 'phonon' list)." + ) + + freqs = np.array( + [[b.get("frequency", np.nan) for b in p.get("band", [])] for p in phonon], + dtype=float, + ) + + if freqs.ndim != 2 or freqs.shape[1] != NBANDS: + raise ValueError(f"{path}: expected (Nq, {NBANDS}) freqs, got {freqs.shape}") + + if not np.isfinite(freqs).all(): + raise ValueError(f"{path}: contains non-finite predicted frequencies.") + + return {"freqs": freqs, "units": "THz", "path": path} + + +def _convert_units(freqs: np.ndarray, src: str, dst: str) -> np.ndarray: + """ + Convert between THz and cm-1. + + Parameters + ---------- + freqs + Frequency array. + src + Source unit (``"THz"`` or ``"cm-1"``). + dst + Destination unit (``"THz"`` or ``"cm-1"``). + + Returns + ------- + numpy.ndarray + Converted frequencies. + """ + if src == dst: + return freqs + if src == "THz" and dst == "cm-1": + return freqs * THZ_TO_CM1 + if src == "cm-1" and dst == "THz": + return freqs / THZ_TO_CM1 + raise ValueError(f"Unsupported unit conversion {src} -> {dst}") + + +def _sorted_flat(freqs: np.ndarray) -> np.ndarray: + """ + Sort each q-point's bands then flatten. + + Parameters + ---------- + freqs + Array of shape ``(Nq, NBANDS)``. + + Returns + ------- + numpy.ndarray + Flattened array of shape ``(Nq * NBANDS,)``. + """ + if freqs.ndim != 2: + raise ValueError(f"Expected (Nq, nb). Got {freqs.shape}") + if freqs.shape[1] != NBANDS: + raise ValueError(f"Expected {NBANDS} bands, got {freqs.shape[1]}") + return np.sort(freqs, axis=1).reshape(-1) + + +def _mae(a: np.ndarray, b: np.ndarray) -> float: + """ + Compute mean absolute error. + + Parameters + ---------- + a + Predicted values. + b + Reference values. + + Returns + ------- + float + Mean absolute error. + """ + return float(np.mean(np.abs(a - b))) + + +def _rmse(a: np.ndarray, b: np.ndarray) -> float: + """ + Compute root mean squared error. + + Parameters + ---------- + a + Predicted values. + b + Reference values. + + Returns + ------- + float + Root mean squared error. + """ + d = a - b + return float(np.sqrt(np.mean(d * d))) + + +@pytest.fixture +def reference() -> dict[str, Any]: + """ + Load the DFT reference and ensure output directory exists. + + Returns + ------- + dict[str, Any] + Reference data mapping returned by :func:`_load_reference_npz`. + """ + OUT_PATH.mkdir(parents=True, exist_ok=True) + return _load_reference_npz(CALC_DATA / "dft_band.npz") + + +def _model_flat(model_name: str, ref_units: str) -> np.ndarray: + """ + Load, unit-convert, and flatten one model's predicted bands. + + Parameters + ---------- + model_name + Model name under ``outputs/``. + ref_units + Reference unit string. + + Returns + ------- + numpy.ndarray + Flattened predicted frequencies. + """ + pred = _load_band_yaml(CALC_PATH / model_name / "band.yaml") + freqs = _convert_units(pred["freqs"], pred["units"], ref_units) + return _sorted_flat(freqs) + + +@pytest.fixture +def flat_bands(reference: dict[str, Any]) -> tuple[np.ndarray, dict[str, np.ndarray]]: + """ + Load and cache flattened reference and predicted bands. + + Parameters + ---------- + reference + Reference mapping from the ``reference`` fixture. + + Returns + ------- + tuple + ``(ref_flat, pred_flats)`` where ``ref_flat`` is a 1D array and + ``pred_flats`` maps model name to a 1D array. + """ + ref_flat = _sorted_flat(reference["freqs"]) + ref_units = reference["units"] + + pred_flats: dict[str, np.ndarray] = {} + for model_name in MODELS: + pred_flat = _model_flat(model_name, ref_units) + if pred_flat.shape != ref_flat.shape: + raise ValueError( + f"{model_name}: prediction and reference flattened shapes differ " + f"{pred_flat.shape} vs {ref_flat.shape}." + ) + pred_flats[model_name] = pred_flat + + return ref_flat, pred_flats + + +@pytest.fixture +def band_errors( + flat_bands: tuple[np.ndarray, dict[str, np.ndarray]], +) -> dict[str, dict[str, float]]: + """ + Compute MAE and RMSE for each model. + + Parameters + ---------- + flat_bands + Output of the ``flat_bands`` fixture. + + Returns + ------- + dict[str, dict[str, float]] + Mapping ``model -> {"mae": ..., "rmse": ...}``. + """ + ref_flat, pred_flats = flat_bands + + out: dict[str, dict[str, float]] = {} + for model_name in MODELS: + pred_flat = pred_flats[model_name] + out[model_name] = { + "mae": _mae(pred_flat, ref_flat), + "rmse": _rmse(pred_flat, ref_flat), + } + return out + + +@pytest.fixture +@build_table( + filename=OUT_PATH / "diamond_phonons_bands_table.json", + thresholds=THRESHOLDS, + metric_tooltips={ + "Band MAE": "Mean absolute error over all q-points and phonon branches", + "Band RMSE": "Root mean squared error over all q-points and phonon branches", + }, +) +def metrics(band_errors: dict[str, dict[str, float]]) -> dict[str, dict[str, float]]: + """ + Build the metrics table mapping for the Dash table. + + Parameters + ---------- + band_errors + Output of the ``band_errors`` fixture. + + Returns + ------- + dict[str, dict[str, float]] + Mapping from metric label to per-model values. + """ + return { + METRIC_LABEL_MAE: {m: band_errors[m]["mae"] for m in MODELS}, + METRIC_LABEL_RMSE: {m: band_errors[m]["rmse"] for m in MODELS}, + } + + +@pytest.fixture +def band_stats( + flat_bands: tuple[np.ndarray, dict[str, np.ndarray]], + band_errors: dict[str, dict[str, float]], +) -> dict[str, dict[str, Any]]: + """ + Build per-model structures consumed by ``cell_to_scatter``. + + Parameters + ---------- + flat_bands + Output of the ``flat_bands`` fixture. + band_errors + Output of the ``band_errors`` fixture. + + Returns + ------- + dict[str, dict[str, Any]] + Per-model mapping containing scatter points and scalar metrics. + """ + ref_flat, pred_flats = flat_bands + + stats: dict[str, dict[str, Any]] = {} + for model_name in MODELS: + pred_flat = pred_flats[model_name] + + points = [ + { + "id": f"diamond-{i}", + "label": "diamond", + "ref": float(ref_val), + "pred": float(pred_val), + } + for i, (pred_val, ref_val) in enumerate( + zip(pred_flat, ref_flat, strict=True) + ) + ] + + stats[model_name] = { + "model": model_name, + "metrics": { + METRIC_KEY_MAE: { + "points": points, + "mae": float(band_errors[model_name]["mae"]), + }, + METRIC_KEY_RMSE: { + "points": points, + "rmse": float(band_errors[model_name]["rmse"]), + }, + }, + } + + return stats + + +@pytest.fixture +@cell_to_scatter( + filename=SCATTER_FILENAME, + x_label="Predicted frequency (cm-1)", + y_label="DFT frequency (cm-1)", +) +def interactive_dataset(band_stats: dict[str, dict[str, Any]]) -> dict[str, Any]: + """ + Build the interactive scatter dataset for the phonon Dash app. + + Parameters + ---------- + band_stats + Output of the ``band_stats`` fixture. + + Returns + ------- + dict[str, Any] + Interactive dataset written to JSON by the decorator. + """ + dataset: dict[str, Any] = { + "metrics": { + METRIC_KEY_MAE: METRIC_LABEL_MAE, + METRIC_KEY_RMSE: METRIC_LABEL_RMSE, + }, + "models": {}, + } + + for model_name, model_data in band_stats.items(): + dataset["models"][model_name] = {"metrics": {}} + + dataset["models"][model_name]["metrics"][METRIC_KEY_MAE] = { + "points": model_data["metrics"][METRIC_KEY_MAE]["points"], + "mae": model_data["metrics"][METRIC_KEY_MAE]["mae"], + } + + dataset["models"][model_name]["metrics"][METRIC_KEY_RMSE] = { + "points": model_data["metrics"][METRIC_KEY_RMSE]["points"], + "rmse": model_data["metrics"][METRIC_KEY_RMSE]["rmse"], + } + + return dataset + + +def test_diamond_phonons_analysis(metrics, interactive_dataset) -> None: + """ + Generate JSON artifacts for the diamond phonons benchmark. + + Parameters + ---------- + metrics + Table metrics fixture output (decorator writes JSON). + interactive_dataset + Scatter dataset fixture output (decorator writes JSON). + + Returns + ------- + None + This test passes if fixtures execute successfully. + """ + _ = metrics + _ = interactive_dataset diff --git a/ml_peg/analysis/bulk_crystal/diamond_phonons/metrics.yml b/ml_peg/analysis/bulk_crystal/diamond_phonons/metrics.yml new file mode 100644 index 000000000..06bb82baf --- /dev/null +++ b/ml_peg/analysis/bulk_crystal/diamond_phonons/metrics.yml @@ -0,0 +1,16 @@ +metrics: + Band MAE: + good: 0 + bad: 50.0 + unit: cm-1 + tooltip: "Mean absolute error of phonon frequencies for diamond over all sampled q-points and phonon branches along the chosen band path." + weight: 1.0 + level_of_theory: RSCAN + + Band RMSE: + good: 0.0 + bad: 50.0 + unit: cm-1 + tooltip: "Root mean squared error of phonon frequencies for diamond over all sampled q-points and phonon branches along the chosen band path." + weight: 1.0 + level_of_theory: RSCAN diff --git a/ml_peg/app/bulk_crystal/diamond_phonons/app_diamond_phonons.py b/ml_peg/app/bulk_crystal/diamond_phonons/app_diamond_phonons.py new file mode 100644 index 000000000..7a534bc58 --- /dev/null +++ b/ml_peg/app/bulk_crystal/diamond_phonons/app_diamond_phonons.py @@ -0,0 +1,185 @@ +"""Run diamond phonon dispersion app (bands-only benchmark).""" + +from __future__ import annotations + +from collections.abc import Mapping +from functools import partial +import json +from pathlib import Path +from typing import Any + +from dash import Dash, dcc, html + +from ml_peg.app import APP_ROOT +from ml_peg.app.base_app import BaseApp +from ml_peg.app.bulk_crystal.diamond_phonons.diamond_interactive_helpers import ( + render_dispersion_component, +) +from ml_peg.app.utils.build_callbacks import ( + model_asset_from_scatter, + scatter_and_assets_from_table, +) +from ml_peg.app.utils.plot_helpers import build_serialized_scatter_content +from ml_peg.calcs import CALCS_ROOT + +DATA_PATH = APP_ROOT / "data" / "bulk_crystal" / "diamond_phonons" +TABLE_PATH = DATA_PATH / "diamond_phonons_bands_table.json" +SCATTER_PATH = DATA_PATH / "diamond_phonons_bands_interactive.json" + +BENCHMARK_NAME = "diamond_phonons" +DOCS_URL = ( + "https://ddmms.github.io/ml-peg/user_guide/benchmarks/bulk_crystal.html" + "#diamond_phonons" +) + +# IMPORTANT: must match how `data_paths` are built in analysis +CALC_BASE = CALCS_ROOT / "bulk_crystal" / "diamond_phonons" +DFT_REF_PATH = Path("data") / "dft_band.npz" + +PLOT_CONTAINER_ID = f"{BENCHMARK_NAME}-plot-container" +DISPERSION_CONTAINER_ID = f"{BENCHMARK_NAME}-dispersion-container" +LAST_CELL_STORE_ID = f"{BENCHMARK_NAME}-last-cell" +SCATTER_METADATA_STORE_ID = f"{BENCHMARK_NAME}-scatter-meta" +SCATTER_GRAPH_ID = f"{BENCHMARK_NAME}-scatter" + + +def model_only_lookup( + click_data: Mapping[str, Any] | None, + metadata: Mapping[str, Any], +) -> dict[str, Any]: + """ + Build a selection context for the dispersion preview. + + For this benchmark we ignore which scatter point was clicked and return a + single dispersion preview per model. The returned ``band_yaml`` must be + resolvable under the app's ``calc_root`` passed to the renderer. + + Parameters + ---------- + click_data + Dash click payload from the scatter plot. Unused for this benchmark. + metadata + Metadata payload produced by the scatter callback helpers. Must contain + a ``model`` key. + + Returns + ------- + dict + Selection context consumed by ``render_dispersion_component``. + """ + _ = click_data + model = str(metadata["model"]) + return { + "model": model, + "selection": { + "id": "diamond", + "label": "Carbon diamond", + "band_yaml": f"outputs/{model}/band.yaml", + }, + } + + +class PhononApp(BaseApp): + """Bands-only phonon benchmark app wiring callbacks and layout.""" + + def register_callbacks(self) -> None: + """Register scatter and dispersion callbacks.""" + with SCATTER_PATH.open(encoding="utf8") as handle: + interactive_data = json.load(handle) + + calc_root = Path(CALC_BASE) + models_data = interactive_data.get("models", {}) + metric_labels = interactive_data.get("metrics", {}) + label_to_key = {label: key for key, label in metric_labels.items()} + + refresh_msg = "Click on a metric to view scatter plots." + + metric_handler = partial( + build_serialized_scatter_content, + models_data=models_data, + label_map=label_to_key, + scatter_id=SCATTER_GRAPH_ID, + instructions=refresh_msg, + ) + + # Bands-only benchmark: no BZ violin panel and no stability panel. + scatter_and_assets_from_table( + table_id=self.table_id, + table_data=self.table.data, + plot_container_id=PLOT_CONTAINER_ID, + scatter_metadata_store_id=SCATTER_METADATA_STORE_ID, + last_cell_store_id=LAST_CELL_STORE_ID, + column_handlers={}, # only metric scatter + default_handler=metric_handler, + ) + + dispersion_renderer = partial( + render_dispersion_component, + calc_root=calc_root, + frequency_scale=1, + frequency_unit="cm⁻¹", + reference_label="RSCAN", + reference_band_npz=DFT_REF_PATH, + ) + + model_asset_from_scatter( + scatter_id=SCATTER_GRAPH_ID, + metadata_store_id=SCATTER_METADATA_STORE_ID, + asset_container_id=DISPERSION_CONTAINER_ID, + data_lookup=model_only_lookup, + asset_renderer=dispersion_renderer, + empty_message="Select a model to preview the phonon dispersion.", + missing_message="No band.yaml found for this model.", + ) + + +def get_app() -> PhononApp: + """ + Construct the diamond phonon PhononApp instance. + + Returns + ------- + PhononApp + Configured application with table + scatter/dispersion panels. + """ + return PhononApp( + name=BENCHMARK_NAME, + description=( + "Accuracy of MLIPs in predicting phonon dispersions for Carbon diamond " + "(RSCAN)." + ), + docs_url=DOCS_URL, + table_path=TABLE_PATH, + extra_components=[ + dcc.Store(id=LAST_CELL_STORE_ID), + dcc.Store(id=SCATTER_METADATA_STORE_ID), + html.Div( + [ + html.Div( + "Click on a metric to view scatter plots.", + id=PLOT_CONTAINER_ID, + style={"flex": "1", "minWidth": 0}, + ), + html.Div( + "Click on a scatter point to view the dispersion plot.", + id=DISPERSION_CONTAINER_ID, + style={"flex": "1", "minWidth": 0}, + ), + ], + style={ + "display": "flex", + "gap": "24px", + "alignItems": "stretch", + "flexWrap": "wrap", + }, + ), + ], + ) + + +if __name__ == "__main__": + full_app = Dash(__name__, assets_folder=DATA_PATH.parent.parent) + phonon_app = get_app() + full_app.layout = phonon_app.layout + phonon_app.register_callbacks() + full_app.run(port=8060, debug=True) diff --git a/ml_peg/app/bulk_crystal/diamond_phonons/diamond_interactive_helpers.py b/ml_peg/app/bulk_crystal/diamond_phonons/diamond_interactive_helpers.py new file mode 100644 index 000000000..d7553c921 --- /dev/null +++ b/ml_peg/app/bulk_crystal/diamond_phonons/diamond_interactive_helpers.py @@ -0,0 +1,266 @@ +""" +Utilities for diamond phonon interactive assets (bands-only). + +This module only supports rendering a dispersion preview from: + +- Predicted phonopy band.yaml (THz) +- Reference dft_band.npz with keys: distance, qpoints, frequencies (cm^-1) +""" + +from __future__ import annotations + +import base64 +from collections.abc import Mapping +from io import BytesIO +from pathlib import Path +from typing import Any + +import matplotlib + +matplotlib.use("Agg") +from dash import html +import matplotlib.pyplot as plt +import numpy as np + +THZ_TO_CM1 = 33.35640951981521 +HS_LABELS = [r"$\Gamma$", "X", "W", "K", r"$\Gamma$", "L", "U", "W", "L", "K", "X"] + + +def render_dispersion_component( + selection_context: Mapping[str, Any], + calc_root: Path, + frequency_scale: float, + frequency_unit: str, + reference_label: str, + reference_band_npz: Path | None = None, +): + """ + Render a Matplotlib dispersion PNG (prediction + reference overlay). + + Parameters + ---------- + selection_context + Mapping containing the current selection. Must contain a ``selection`` entry + with a ``band_yaml`` path. + calc_root + Root directory for calculation outputs. + frequency_scale + Multiplicative scale applied to both predicted and reference frequencies. + frequency_unit + Unit label shown on the y-axis. + reference_label + Label used for the reference curve in the legend. + reference_band_npz + Optional path to the reference ``dft_band.npz`` file, relative to + ``calc_root``. + + Returns + ------- + dash.html.Div | None + A Dash component containing the rendered PNG, or ``None`` if no band file + is selected or rendering fails. + """ + model_display = selection_context.get("model") + selected = selection_context.get("selection") or {} + + band_yaml = selected.get("band_yaml") + label = selected.get("label") or selected.get("id", "") + + if not band_yaml: + return None + + image_src = render_band_yaml_png( + calc_root=calc_root, + band_yaml=str(band_yaml), + reference_band_npz=reference_band_npz, + system_label=str(label), + frequency_scale=float(frequency_scale), + frequency_unit=str(frequency_unit), + reference_label=str(reference_label), + prediction_label=str(model_display) + if model_display is not None + else "Prediction", + ) + + if not image_src: + return None + + return html.Div( + [ + html.H4(label), + html.Img( + src=image_src, + style={"maxWidth": "100%", "border": "1px solid #ccc"}, + ), + ] + ) + + +def render_band_yaml_png( + *, + calc_root: Path, + band_yaml: str, + reference_band_npz: Path | None, + system_label: str, + frequency_scale: float, + frequency_unit: str, + reference_label: str, + prediction_label: str, +) -> str | None: + """ + Render dispersion by parsing a phonopy band.yaml and overlaying a DFT reference. + + Parameters + ---------- + calc_root + Root directory for calculation outputs. + band_yaml + Path to the predicted phonopy ``band.yaml`` relative to ``calc_root``. + reference_band_npz + Optional path to the reference ``dft_band.npz`` relative to ``calc_root``. + system_label + Label identifying the system being plotted. + frequency_scale + Multiplicative scale applied to both predicted and reference frequencies. + frequency_unit + Unit label shown on the y-axis. + reference_label + Legend label for the reference curves. + prediction_label + Legend label for the prediction curves. + + Returns + ------- + str | None + A base64-encoded PNG image (``data:image/png;base64,...``), or ``None`` if + the input data could not be loaded. + """ + import yaml # type: ignore + + def _detect_symmetry_boundaries(q_ref: np.ndarray) -> list[int]: + """ + Detect symmetry-point boundaries along the reference q-point path. + + Parameters + ---------- + q_ref + Reference q-point coordinates of shape ``(Nq, 3)``. + + Returns + ------- + list[int] + Indices of q-points corresponding to symmetry boundaries. + """ + dq = np.diff(q_ref, axis=0) + dq_norm = np.linalg.norm(dq, axis=1) + eps = 1e-12 + dq_unit = dq / (dq_norm[:, None] + eps) + cosang = np.sum(dq_unit[1:] * dq_unit[:-1], axis=1) + cand = np.where(cosang < 0.95)[0] + 1 + + boundaries = [0] + for i in cand: + if i + 1 < len(q_ref) and np.allclose(q_ref[i], q_ref[i + 1], atol=1e-10): + boundaries.append(int(i)) + else: + boundaries.append(int(i)) + + boundaries = sorted(set(boundaries)) + if boundaries[0] != 0: + boundaries = [0] + boundaries + if boundaries[-1] != len(q_ref) - 1: + boundaries.append(len(q_ref) - 1) + return boundaries + + pred_path = Path(calc_root) / band_yaml + if not pred_path.exists(): + return None + + try: + with pred_path.open("r", encoding="utf8") as f: + y = yaml.safe_load(f) + except Exception: + return None + + phonon = y.get("phonon", None) + if not isinstance(phonon, list) or not phonon: + return None + + s_pred = np.asarray([p.get("distance", np.nan) for p in phonon], float) + + freqs_pred_thz = np.asarray( + [[b.get("frequency", np.nan) for b in p.get("band", [])] for p in phonon], + float, + ) + if freqs_pred_thz.ndim != 2 or not np.isfinite(freqs_pred_thz).all(): + return None + + freqs_pred_cm1 = freqs_pred_thz * THZ_TO_CM1 * frequency_scale + + s_ref = None + q_ref = None + freqs_ref_cm1 = None + sym_pos = None + + if reference_band_npz is not None: + ref_path = Path(calc_root) / reference_band_npz + if ref_path.exists(): + obj = np.load(ref_path, allow_pickle=False) + + s_ref = np.asarray(obj["distance"], float) + q_ref = np.asarray(obj["qpoints"], float) + freqs_ref_cm1 = np.asarray(obj["frequencies"], float) + + if freqs_ref_cm1.ndim == 3 and freqs_ref_cm1.shape[0] == 1: + freqs_ref_cm1 = freqs_ref_cm1[0] + + if freqs_ref_cm1.ndim != 2: + s_ref = q_ref = freqs_ref_cm1 = None + else: + freqs_ref_cm1 = freqs_ref_cm1 * frequency_scale + bounds = _detect_symmetry_boundaries(q_ref) + sym_pos = s_ref[bounds] + + use_ref_x = s_ref is not None and len(s_ref) == len(s_pred) + x = s_ref if use_ref_x else s_pred + + fig, ax = plt.subplots(figsize=(8, 5)) + + for band in freqs_pred_cm1.T: + ax.plot(x, band, "--", lw=1.0, color="red") + + if freqs_ref_cm1 is not None: + for band in freqs_ref_cm1.T: + ax.plot(x if use_ref_x else s_ref, band, "-", lw=1.0, color="blue") + + if sym_pos is not None and len(sym_pos) == len(HS_LABELS): + for xpos in sym_pos: + ax.axvline(float(xpos), color="k", lw=0.8) + ax.set_xticks(sym_pos) + ax.set_xticklabels(HS_LABELS) + ax.set_xlim(float(x[0]), float(x[-1])) + + ax.set_xlabel("Wave vector") + ax.set_ylabel(rf"Frequency ({frequency_unit})") + ax.axhline(0.0, color="k", lw=0.8) + ax.grid(True, linestyle=":", linewidth=0.5) + ax.set_ylim(bottom=0.0) + + from matplotlib.lines import Line2D + + handles = [] + if freqs_ref_cm1 is not None: + handles.append( + Line2D([0], [0], color="blue", lw=1.0, ls="-", label=reference_label) + ) + handles.append( + Line2D([0], [0], color="red", lw=1.0, ls="--", label=prediction_label) + ) + ax.legend(handles=handles, frameon=False) + + buffer = BytesIO() + fig.savefig(buffer, format="png", dpi=150, bbox_inches="tight") + plt.close(fig) + + encoded = base64.b64encode(buffer.getvalue()).decode("ascii") + return f"data:image/png;base64,{encoded}" diff --git a/ml_peg/calcs/bulk_crystal/diamond_phonons/calc_diamond_phonons.py b/ml_peg/calcs/bulk_crystal/diamond_phonons/calc_diamond_phonons.py new file mode 100644 index 000000000..cfa473d44 --- /dev/null +++ b/ml_peg/calcs/bulk_crystal/diamond_phonons/calc_diamond_phonons.py @@ -0,0 +1,261 @@ +""" +Calculate diamond phonon bands (phonopy version-friendly). + +This module computes phonon force constants and phonon band dispersions for +diamond using a set of MLIP models. Outputs are written to ``outputs//`` +as: + +- ``FORCE_CONSTANTS`` +- ``band.yaml`` + +The band path is taken from the DFT reference NPZ so that predicted dispersions +are evaluated on the exact same q-path as the reference. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +from ase import Atoms +import numpy as np +from phonopy.structure.atoms import PhonopyAtoms +import pytest + +from ml_peg.models.get_models import load_models +from ml_peg.models.models import current_models + +HERE = Path(__file__).parent +DATA_PATH = HERE / "data" +OUT_PATH = HERE / "outputs" + +DIAMOND_YAML = DATA_PATH / "diamond.yaml" +DFT_BAND_NPZ = DATA_PATH / "dft_band.npz" + +# Load all MLIP models to benchmark. +MODELS = load_models(current_models) + + +def phonopy_atoms_to_ase(ph_atoms: PhonopyAtoms) -> Atoms: + """ + Convert ``PhonopyAtoms`` to ASE ``Atoms``. + + Parameters + ---------- + ph_atoms + Phonopy atoms object. + + Returns + ------- + ase.Atoms + ASE representation with periodic boundary conditions enabled. + """ + return Atoms( + symbols=ph_atoms.symbols, + cell=ph_atoms.cell, + scaled_positions=ph_atoms.scaled_positions, + pbc=True, + ) + + +def load_phonon_yaml(yaml_path: Path) -> Any: + """ + Load a phonopy object from a phonopy YAML file (version-tolerant). + + Parameters + ---------- + yaml_path + Path to a phonopy YAML file (e.g. ``diamond.yaml``). + + Returns + ------- + object + A phonopy object as returned by ``phonopy.load`` or the older YAML reader. + + Raises + ------ + FileNotFoundError + If ``yaml_path`` does not exist. + """ + if not yaml_path.exists(): + raise FileNotFoundError(f"Missing phonopy YAML: {yaml_path}") + + try: + import phonopy # type: ignore + + return phonopy.load(str(yaml_path)) + except Exception: + from phonopy.interface.phonopy_yaml import read_phonopy_yaml # type: ignore + + obj = read_phonopy_yaml(str(yaml_path)) + return getattr(obj, "phonopy", obj) + + +def get_displaced_supercells(phonon: Any) -> list[PhonopyAtoms]: + """ + Return displaced supercells from a phonopy object (version-tolerant). + + Parameters + ---------- + phonon + Phonopy object. + + Returns + ------- + list[phonopy.structure.atoms.PhonopyAtoms] + Displaced supercells. Returns an empty list if not available. + """ + if hasattr(phonon, "get_supercells_with_displacements"): + return phonon.get_supercells_with_displacements() + return getattr(phonon, "supercells_with_displacements", []) or [] + + +def ref_band_path() -> list[list[list[float]]]: + """ + Build a q-point path for ``phonon.run_band_structure`` from the DFT NPZ. + + The DFT reference NPZ is used so that predicted dispersions are computed on + the exact same q-path as the reference. + + The NPZ is expected to contain: + + - ``qpoints``: array with shape ``(Nq, 3)`` + - ``distance``: array with shape ``(Nq,)`` (optional; used to detect segment + boundaries). If missing, the q-path is treated as a single segment. + + Returns + ------- + list[list[list[float]]] + List of q-point segments, each a list of ``[qx, qy, qz]`` points. + + Raises + ------ + FileNotFoundError + If the DFT reference NPZ does not exist. + KeyError + If the NPZ does not contain ``qpoints``. + ValueError + If ``qpoints`` or ``distance`` have invalid shapes. + """ + if not DFT_BAND_NPZ.exists(): + raise FileNotFoundError(f"Missing DFT reference NPZ: {DFT_BAND_NPZ}") + + obj = np.load(DFT_BAND_NPZ, allow_pickle=False) + if "qpoints" not in obj: + raise KeyError(f"{DFT_BAND_NPZ} missing required key 'qpoints'.") + + q = np.asarray(obj["qpoints"], float) + if q.ndim != 2 or q.shape[1] != 3: + raise ValueError(f"Bad qpoints shape in {DFT_BAND_NPZ}: {q.shape}") + + x = np.asarray(obj["distance"], float) if "distance" in obj else None + if x is None: + return [q.tolist()] + + if x.ndim != 1 or x.shape[0] != q.shape[0]: + raise ValueError(f"Bad distance shape in {DFT_BAND_NPZ}: {x.shape}") + + cuts = [0] + [i for i in range(1, len(x)) if x[i] <= x[i - 1] + 1e-12] + [len(x)] + return [q[cuts[i] : cuts[i + 1]].tolist() for i in range(len(cuts) - 1)] + + +def write_force_constants(phonon: Any, fc_path: Path) -> None: + """ + Write force constants to disk in a phonopy-version-tolerant way. + + Parameters + ---------- + phonon + Phonopy object with computed force constants. + fc_path + Output path for ``FORCE_CONSTANTS``. + """ + fc_path.parent.mkdir(parents=True, exist_ok=True) + try: + phonon.write_force_constants(filename=str(fc_path)) + except AttributeError: + from phonopy.file_IO import write_FORCE_CONSTANTS # type: ignore + + write_FORCE_CONSTANTS(phonon.force_constants, filename=str(fc_path)) + + +def write_band_yaml(phonon: Any, bands_path: Path) -> None: + """ + Write phonopy band-structure YAML to disk. + + Parameters + ---------- + phonon + Phonopy object that has already run ``run_band_structure``. + bands_path + Output path for ``band.yaml``. + + Raises + ------ + RuntimeError + If a YAML writer is not available for the current phonopy version. + """ + bands_path.parent.mkdir(parents=True, exist_ok=True) + + if hasattr(phonon, "write_yaml_band_structure"): + phonon.write_yaml_band_structure(filename=str(bands_path)) + return + + bs = getattr(phonon, "band_structure", None) + if bs is None or not hasattr(bs, "write_yaml"): + raise RuntimeError( + "Phonopy band-structure YAML writer not found in this phonopy version." + ) + bs.write_yaml(filename=str(bands_path)) + + +@pytest.mark.parametrize("mlip", MODELS.items()) +def test_diamond_phonons_band(mlip) -> None: + """ + Compute diamond phonon bands for one MLIP and write output files. + + Parameters + ---------- + mlip + Tuple ``(model_name, model)`` from ``MODELS.items()``. + + Raises + ------ + RuntimeError + If the phonopy YAML lacks a displacement dataset, has no displaced + supercells, or cannot write band YAML for the current phonopy version. + """ + model_name, model = mlip + + phonon = load_phonon_yaml(DIAMOND_YAML) + if getattr(phonon, "dataset", None) is None: + raise RuntimeError(f"{DIAMOND_YAML} has no displacement dataset.") + + scells = get_displaced_supercells(phonon) + if not scells: + raise RuntimeError(f"No displaced supercells found in {DIAMOND_YAML}.") + + calc = model.get_calculator() + forces = [] + for scp in scells: + ase_sc = phonopy_atoms_to_ase(scp) + ase_sc.calc = calc + forces.append(ase_sc.get_forces()) + + if hasattr(phonon, "set_forces"): + phonon.set_forces(forces) + else: + phonon.forces = forces + + phonon.produce_force_constants() + phonon.run_band_structure(ref_band_path()) + + write_dir = OUT_PATH / model_name + fc_path = write_dir / "FORCE_CONSTANTS" + bands_path = write_dir / "band.yaml" + + write_force_constants(phonon, fc_path) + write_band_yaml(phonon, bands_path) + + assert fc_path.exists(), f"Missing FORCE_CONSTANTS for {model_name}: {fc_path}" + assert bands_path.exists(), f"Missing band.yaml for {model_name}: {bands_path}" diff --git a/ml_peg/calcs/bulk_crystal/diamond_phonons/data/dft_band.npz b/ml_peg/calcs/bulk_crystal/diamond_phonons/data/dft_band.npz new file mode 100644 index 0000000000000000000000000000000000000000..b016d9a643215dcf34323cce840c062718e6b180 GIT binary patch literal 18668 zcmeHPd3a6N*S_X?7BNc@p<*nG;%q~eni4Tnq!L7lL{LP2m3flfd(XXSP1PD(w5p?4 z4XvSQHD-x~2!d8=4ILEy)?RDveUsbY_k7<||0aF(efGNhoU_l~YrX4Td*{Y~P^r?j zE%f7U`8I9TH2=yLi~Lu~Qri*{K07pQZe-iA@P#ET)zK`w%8%ubW9Y~s{$op6=3AnC zgF_;x%<=8+<=bOwS6@Fb->I|b%$*YyHfi>p;1Fr=A2c&E1niO1gTh0g-LgZ+E`DCm zw(|3e_WGAU6=@Xz{Qun@{;#flZMC63QEkF)ZuV`)#;^8!wg~)0+f_UN=CYlYBX(=M zYFDfD$)<&e_iMXqxAAUrf99xz+OFE)oN66XFaKL@S8clAciLXGovH2g9+?I`=Y+Pa zcH=rThTO|IrR}O6I{TM|BO}geyJ|03w9&6`!*klM+Q|vyPVK&OUfWfBg{|76E?cv; zUA1YvIoht;zutdoY+~1o+FrDsr|tA!s-0)3b)x6G+qd!iU*_xc^tyfW+#g>Dzn8D= zG>+XykFSO=_s+PY?P{G4^w}PIG2*JWt92UmZMmMSMqbl)wN5nt0&Q38MDuoC+toS| z-Y_yh8v9^Z_C=%p;7t|#`>1uhVSqD?@%${;SXIzy^Nj%~`T841m+zk+dcvM#9PN1E z!?s~+J#TRP0)Kv5_owT#`F?7hZ}9W7`1xmbe;SusUxhb}GM#F4>G9QBjm<9n`JWlP z3yl7s-g|s{!5NJ$aRRRygMKUxSDbdktHv;NyptMd9elDg>TbSaTlYtYg7;6j;bmjl z=?c@E&dhYfJY(DT^ONQeJFYQ&Qn};x0xlY#c>Hzj_K{<5c){2k-Kguden&LQUf=Y| zGJ416AAaM@LmJh4()bVX_!XWuMhy2|m$GBOMm264&mJC+!sGnBU2b^9_&Ra(=%XD! z)~KFG_j#Z1qi~OLYRD_q$8_7EQCj3Ir9L;@k^1lB{x0kR-Z|mPH-f5mSulwQ2P#CL`}Qh&v7E^G|Z(Dv^W zhC5P!<@YX>QFOv`)^JDaul(DEbl)t#zYD3q;xQM}eJ}F;T}b_1{G{3+I7476^Nezn z-Wcq$^rE5K(MSY&#!Hn;woa;(YpAv#62WC7KxV6VE*Pqf;WziPF$Res+feN-oh3nB zHYT+?R{ii7=MB}Se)-1RXFJ{L`ocNBF5mDe8?&r$T0ZwXquZxfO5(}q@trnQJFcB1 zsw)PD{mqkxYSZ|x7=Nyt_R)`#CwRPyR~3({b-Zfa$lCUB!YuW9rct*=V43yiRpX<| z5!=!(D4xzVAZZ28ZGMe;&}AonyvDqy_&$^S74UT~JKeW{pQF|#lgIJD^Rrqv9A5Vm z#;j4%?|=G=t3L$g$KTFBY5Wm*W#g&?F8=Sbwb$tJAD%Ko-kS`R;j+K{r0>I@e>!cP zv|KD%A=Q;fUM#hsQuCH)4f`RId9N&)I8=HyB;Oo zzi=+gSYKjynVPj-c4}WR+WzhR__uj#9`pFVImW7yZ*ARvUd>mY@yq5zgPu#tHOg;m zI4<~kmpxl9+rf(lJzve^Kau?G68qaF?a#{Z6&Cf|T=v6U?VlTYxXEGv%+da) z;zS|!yUPBWt&!sBnvNe8M-(sD*pIU`Qk)d%xKi;&{;&MO#Utc5Dqo*xzdWn`jq-h# z&ezI6$*)yjKB@g#<#DAGl>d`{$TpTqTFE)C5Bt8 zKIwV62GUQn{W?DB`ME})AEutUKI2OrpES;k#-8?XK2vwu$2vZh&ItsyH7{bTj!zor zC4=m%+~QOD6^+_r$us(>ujPPoMOd}II7w_o`5-ZlRGCaZv3-0zMN zIotlM+;M4GIkB#OyWHkw%L`JC+}277Y{*W&#l!!;PSlIfS=Pqq^}6887CSFobHVc zd3_oQm~8l{5wBAtfz#KuvB2eC-B>h`%TS^T-Dg4*exD{ZjtXAXe$GpPleEhX_7ZqI z(@S6w?eh{(M2-r_EJVfDIA>HY(oirb5g z&J!0k6}O~2Zf#2A%4sUlt*SH=*D_(@dNrfxL^Pv$Ti;B`VaJ?lCeFa)JCtuu_vzkT zpm>?tTr`mmzP357+ll5vc3)P?hsM*{M^tUIbIqM;KB5mqS<_WMVxDx^<32neE!aO= z(730z5Wdo}SGEx7zTdZ?_qX_pXYt2ZOvN8xfnx1VUjc9R#lY`;1;*juzM`HChmI{p zQMO8`{9Yfgx)zK4R}I)|>YR{>c_Cp_LPPZSiTp_ZD117?^7FzSlrI08wJK7eY}o6L zIFYY7X+jGj2JZDyv>^QhG)m%#2yx=X6-;y?63|yuDS*odX*>eqvog$yjxl_M3vpp{ z!$q{5@yF9O%K2X!<)noZF2n$8Jzq$E@ah<@5&Ne&ksu~DabbiUP`@Z4>G1beks4*3 zKV0S%l1Dzi@3Dj&;?R7Jl0a^(bz(qz?r>;a-ZyRL^*ON=(BCZ7C~rGpy;H0{ z02c;1G~)Po#|!Ddo3||1I9oEwJ5C{a=#8=tjYyF95{Ve&U)PAlyv8X|uC6yVA~DxV zp#W$bqY)F7>7;<@X=+5m^GYM4T^6fR+Jj=8wBA{EjriQE=|T?w(Cs*l7$4J}ViPP& z`zX6c4DhTBBCvJ|8f83Y_j6Lh@v&=^*YD_MBLW(ns8P~{pNEqOD%++J3GtClpq%TL zq*0Eq|2YRHhRD@6jdFWq#74V7zxXLhBd&XjL&)N3<}8~=T*vMv`OUawjhGKwJA};B zdxzOH`b#F?ZBimxlB`kLzX2IweZeLaO8@uRWl}<1ldMtMH-v)&_p=Fwn20_wsr^f` zMrB7r?_(pv1;lxH%Ov8TlcG`CF@N;1At9FlM1M;%i2yxQH7dKDw`T2aIByb>C__#0 zp42C&QZ*_&CY}a1NYQDY33h)2QsY-SMYYD3rv#`Wcgmdg4-z%8qff-YOIV zP48UeVN#+flde(Ok)Qfng+g4HswNS9V7f+SN8wY|NcQV7riVvGd{#ilfOX0+Y@I6NQJfri~JJGb;&#_I663u^Gg#tCJ~# z`#4D83}Sz4Xr=Z1%O<3(teB8NLa$M>Xa%}pUOxx%c`UE5r&VBlov{hrPJsR>?CvHB zoX=qn6inXl+Jux_rD1-g-{c?Scu3D6zKTsk+%?{T^W%|VBYx{>#RR?lrj7dDNf%Pq z{d_AC$Gvo-L&$Y*lxw4PADcnKt)`s>Vvlrzdh^*tod3gP9LRSYLhKZex0Vur_qF4F z)+|^`f_Z!b`Qs1=eg2c3*CmbeMzo!T0iUne1KcMxy- z$I|>BU4YN8wa-EE2f6?S`t2y<_3s@5>#eR!#A}jp?#;uv`{9Ozcx@^7pSl3!d;1dy z6+EY+Xniltq4894()Cs+@$jc}F)qsZIBETVm{0R(nN9Hz?}@r2*-7)39Zvq*+e!H& zB7)ZU@H7&Vkng0w&RR_R=j6sv>~|~BDc%76zi;_8ny1E-QAqoZbkh9Kc2c|#48r%` z0(uMc_R-O_4g>v>*KdPe79gL2F2llRT7cGb|CioVpUb#@0QbSX+R|U6vSVQY_ecI3 zI~sZSB^W0b2A0VhB_B&W@;Hni`}dqiaR>8&`ETtsjaaXN9raSQlk)7K2+|{MoHXy5 zVH#2AfE{@+-AVj(YCg?#Ri{9`vuBP*6!Kt~*KJtE>-)@Nfpx}B2gT>nc^Z`+3+qiz zitmj}i08o%QMV>7(1`pGcGL^+kq+A78AtiR>7WAZ{vwU2Kfn&tbkuvZQ{eu9Ig`jAApS8wU;2+mxr|%Dj`0n;81>4E6#Cqo{lEPZiUUBr z9_*Ok!<_;*4rZoN-YVyydHimPM$GSEM}H1*l8&vIF1)4TzDb2!zZi|mF6*pcH0Fey&njM0b%KiDy@t2s#rzGkI(xoi@z9gEQ@b=xhl%R;h-AvA8{=u* zpl8tUpg-!Rcq{4m*>>WqN~shV(Fvp{j@yN-$1enOJ_@oD|2|`_-K^FSBKxOuw^_y3yow*mfy_1LN;)JY4! zu;KdN9qk|<^059xe<*#Tbc@m{O25dBjNyg!4Q_OS{z1J9<54L4{kHco>H48dDPCGzNtdQr>3vS6lg_wgr}&=9^^;E; z-LJisbXYSR#c!2#od09D*gsd8w9!?;B?m{h*xx>yI?bw>3=Muzd9kll0cA zWU6<;Pf=%FvqdWYZ$8nT@ zu3O0uUowfuuEmoMAMPN1{j*8>tz#T*)K#+KbIeqe?!PsT;@sw-IJ?RG2Rw!wcuj2d z-cZ+~e|{88I^}BztpmI-#$DT3e7|3SM={@=HOcR5#S%YUaZtSnyes`~=PxmurxkzW zMrclq=5cpBZY09}k;j17aRVIqT=6(=*uwqMkAd$MPs^~AypDbge2)4DcpUeMVf?sJ z2z)O$RBr=MV_gBfj&TL^fb}TwIM%g_*MaY`jt8E`xC367&#?fXW1IqyV|)OwE51iv z#XK!}9XIZQ&v73bcp0Aqyp9{7!1s!$QNIDNV|@gCu6P{Z19%Q?U;y~O2u}mA>x$21o|Zf=`A_mXo{s?EE1pIj1$Cw3bH(F|*HyhK{bNFI z2I-X9Hk#)+GbZx}-V+SjZcU}h7 za}8}Y&wG;g>@gT%2 zm3XPVDe(LS;sy8RgHmZf!OKPmI)OIQliO49eSY*bao$cnWIj2RLUGZ}Mt(oUM)A=t z1@}2tccXaTZgJ2&_fA2cY6SBrw{biN+9>|cCsUmr%;Wyeq;^&^&PN99-=WU$PuHzj zkxcJpn&ej(dELOS_W2ZI-3I%D=%27ps8HLB9EW;8NzPIS3|07mA#&1`Ssm1Qs5(|q z_;kk$^0zbp{n-NiMrZNy0v|IJA1}bcA{;ZITNfWM&~{|;@dC-m;^PIPeK>Y-FP~JI zrOGQ+eyOs{y*yK8oGRZ`d8f)c_wrAbiK;wQ<)bPa-OEc=hN|*Ym8U58ijNnHj~C?c zl@uQ@klZdlUeLc*;{NL-#m5U!$Q2(i6dy0hW1QmSh2rA{`6Vm(^@!r*1^V?FrF)gm zRr=RmAFE>ob*!LJ*^7@CK=~IRFBBgy$X_ojK3>4%isIvi;^PJUenau`g4_2OUJZVB zi2Alw1vp*^4GRugppF*QSF9BN_oIc-|NkF;_}5=rz&{nCziL$i_ns`4l8mJo`I}*u zGTbi9SdOtgV+BTa!%E!dzi(lw!tJVz)flTY)?lp3Sc_5pMGX&b*J1QzT$b1 zV*|#9jExu@Gd5xLV)SNg%GivtIin9_3r1hYmW-_!pJ8mx=*QTGu`Ode#`cUI7&|gP z%h-vrGh-LV=NP*(c4O?$_&j3|#-5D582KCN7QyY_jC~mUGWKJ9fw4d1i;M#p2Qt3I zIEZmD;}FK7jKdiH83P!HGX^q_U>wOfig7gK7{;-T;~2*?PGEeQaU$a+#>tF9jIS_G zVGL#rVVuf1jd40-DB}#qR~ctA&SDH>oXr@{7{NG)F_LjE<2=UsjIS{+U|h(!h%t(B zF=I4i(XU#StQXum89tH!Y5`S6hD@6k5;k{I82z~#MOyM>`J?dZNXzf(C=1R*|3kyE z_zx;kvUVv&5$=Ctkv6ON%C%!k7L9p{!Gm`(;HJyzph4AhQ3<^7{`eQ&x5>3;#`l JX^h0c{~vuDWRm~@ literal 0 HcmV?d00001 diff --git a/ml_peg/calcs/bulk_crystal/diamond_phonons/data/diamond.yaml b/ml_peg/calcs/bulk_crystal/diamond_phonons/data/diamond.yaml new file mode 100644 index 000000000..808cde227 --- /dev/null +++ b/ml_peg/calcs/bulk_crystal/diamond_phonons/data/diamond.yaml @@ -0,0 +1,2135 @@ +phonopy: + version: "2.47.1" + frequency_unit_conversion_factor: 15.633302 + symmetry_tolerance: 1.00000e-05 + +space_group: + type: "Fd-3m" + number: 227 + Hall_symbol: "F 4d 2 3 -1d" + +primitive_matrix: +- [ 0.000000000000000, 0.500000000000000, 0.500000000000000 ] +- [ 0.500000000000000, 0.000000000000000, 0.500000000000000 ] +- [ 0.500000000000000, 0.500000000000000, 0.000000000000000 ] + +supercell_matrix: +- [ 4, 0, 0 ] +- [ 0, 4, 0 ] +- [ 0, 0, 4 ] + +primitive_cell: + lattice: + - [ 0.000000000000000, 1.780372500000000, 1.780372500000000 ] # a + - [ 1.780372500000000, 0.000000000000000, 1.780372500000000 ] # b + - [ 1.780372500000000, 1.780372500000000, 0.000000000000000 ] # c + points: + - symbol: C # 1 + coordinates: [ 0.000000000000000, 0.000000000000000, 0.000000000000000 ] + mass: 12.010700 + - symbol: C # 2 + coordinates: [ 0.250000000000000, 0.250000000000000, 0.250000000000000 ] + mass: 12.010700 + reciprocal_lattice: # without 2pi + - [ -0.280840105090367, 0.280840105090367, 0.280840105090367 ] # a* + - [ 0.280840105090367, -0.280840105090367, 0.280840105090367 ] # b* + - [ 0.280840105090367, 0.280840105090367, -0.280840105090367 ] # c* + +unit_cell: + lattice: + - [ 3.560745000000000, 0.000000000000000, 0.000000000000000 ] # a + - [ 0.000000000000000, 3.560745000000000, 0.000000000000000 ] # b + - [ 0.000000000000000, 0.000000000000000, 3.560745000000000 ] # c + points: + - symbol: C # 1 + coordinates: [ 0.000000000000000, 0.000000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 2 + coordinates: [ 0.000000000000000, 0.500000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 3 + coordinates: [ 0.500000000000000, 0.000000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 4 + coordinates: [ 0.500000000000000, 0.500000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 5 + coordinates: [ 0.250000000000000, 0.250000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 5 + - symbol: C # 6 + coordinates: [ 0.250000000000000, 0.750000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 5 + - symbol: C # 7 + coordinates: [ 0.750000000000000, 0.250000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 5 + - symbol: C # 8 + coordinates: [ 0.750000000000000, 0.750000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 5 + +supercell: + lattice: + - [ 14.242979999999999, 0.000000000000000, 0.000000000000000 ] # a + - [ 0.000000000000000, 14.242979999999999, 0.000000000000000 ] # b + - [ 0.000000000000000, 0.000000000000000, 14.242979999999999 ] # c + points: + - symbol: C # 1 + coordinates: [ 0.000000000000000, 0.000000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 2 + coordinates: [ 0.250000000000000, 0.000000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 3 + coordinates: [ 0.500000000000000, 0.000000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 4 + coordinates: [ 0.750000000000000, 0.000000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 5 + coordinates: [ 0.000000000000000, 0.250000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 6 + coordinates: [ 0.250000000000000, 0.250000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 7 + coordinates: [ 0.500000000000000, 0.250000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 8 + coordinates: [ 0.750000000000000, 0.250000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 9 + coordinates: [ 0.000000000000000, 0.500000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 10 + coordinates: [ 0.250000000000000, 0.500000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 11 + coordinates: [ 0.500000000000000, 0.500000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 12 + coordinates: [ 0.750000000000000, 0.500000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 13 + coordinates: [ 0.000000000000000, 0.750000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 14 + coordinates: [ 0.250000000000000, 0.750000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 15 + coordinates: [ 0.500000000000000, 0.750000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 16 + coordinates: [ 0.750000000000000, 0.750000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 17 + coordinates: [ 0.000000000000000, 0.000000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 18 + coordinates: [ 0.250000000000000, 0.000000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 19 + coordinates: [ 0.500000000000000, 0.000000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 20 + coordinates: [ 0.750000000000000, 0.000000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 21 + coordinates: [ 0.000000000000000, 0.250000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 22 + coordinates: [ 0.250000000000000, 0.250000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 23 + coordinates: [ 0.500000000000000, 0.250000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 24 + coordinates: [ 0.750000000000000, 0.250000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 25 + coordinates: [ 0.000000000000000, 0.500000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 26 + coordinates: [ 0.250000000000000, 0.500000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 27 + coordinates: [ 0.500000000000000, 0.500000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 28 + coordinates: [ 0.750000000000000, 0.500000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 29 + coordinates: [ 0.000000000000000, 0.750000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 30 + coordinates: [ 0.250000000000000, 0.750000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 31 + coordinates: [ 0.500000000000000, 0.750000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 32 + coordinates: [ 0.750000000000000, 0.750000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 33 + coordinates: [ 0.000000000000000, 0.000000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 34 + coordinates: [ 0.250000000000000, 0.000000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 35 + coordinates: [ 0.500000000000000, 0.000000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 36 + coordinates: [ 0.750000000000000, 0.000000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 37 + coordinates: [ 0.000000000000000, 0.250000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 38 + coordinates: [ 0.250000000000000, 0.250000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 39 + coordinates: [ 0.500000000000000, 0.250000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 40 + coordinates: [ 0.750000000000000, 0.250000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 41 + coordinates: [ 0.000000000000000, 0.500000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 42 + coordinates: [ 0.250000000000000, 0.500000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 43 + coordinates: [ 0.500000000000000, 0.500000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 44 + coordinates: [ 0.750000000000000, 0.500000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 45 + coordinates: [ 0.000000000000000, 0.750000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 46 + coordinates: [ 0.250000000000000, 0.750000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 47 + coordinates: [ 0.500000000000000, 0.750000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 48 + coordinates: [ 0.750000000000000, 0.750000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 49 + coordinates: [ 0.000000000000000, 0.000000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 50 + coordinates: [ 0.250000000000000, 0.000000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 51 + coordinates: [ 0.500000000000000, 0.000000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 52 + coordinates: [ 0.750000000000000, 0.000000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 53 + coordinates: [ 0.000000000000000, 0.250000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 54 + coordinates: [ 0.250000000000000, 0.250000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 55 + coordinates: [ 0.500000000000000, 0.250000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 56 + coordinates: [ 0.750000000000000, 0.250000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 57 + coordinates: [ 0.000000000000000, 0.500000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 58 + coordinates: [ 0.250000000000000, 0.500000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 59 + coordinates: [ 0.500000000000000, 0.500000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 60 + coordinates: [ 0.750000000000000, 0.500000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 61 + coordinates: [ 0.000000000000000, 0.750000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 62 + coordinates: [ 0.250000000000000, 0.750000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 63 + coordinates: [ 0.500000000000000, 0.750000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 64 + coordinates: [ 0.750000000000000, 0.750000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 65 + coordinates: [ 0.000000000000000, 0.125000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 66 + coordinates: [ 0.250000000000000, 0.125000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 67 + coordinates: [ 0.500000000000000, 0.125000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 68 + coordinates: [ 0.750000000000000, 0.125000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 69 + coordinates: [ 0.000000000000000, 0.375000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 70 + coordinates: [ 0.250000000000000, 0.375000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 71 + coordinates: [ 0.500000000000000, 0.375000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 72 + coordinates: [ 0.750000000000000, 0.375000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 73 + coordinates: [ 0.000000000000000, 0.625000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 74 + coordinates: [ 0.250000000000000, 0.625000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 75 + coordinates: [ 0.500000000000000, 0.625000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 76 + coordinates: [ 0.750000000000000, 0.625000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 77 + coordinates: [ 0.000000000000000, 0.875000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 78 + coordinates: [ 0.250000000000000, 0.875000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 79 + coordinates: [ 0.500000000000000, 0.875000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 80 + coordinates: [ 0.750000000000000, 0.875000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 81 + coordinates: [ 0.000000000000000, 0.125000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 82 + coordinates: [ 0.250000000000000, 0.125000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 83 + coordinates: [ 0.500000000000000, 0.125000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 84 + coordinates: [ 0.750000000000000, 0.125000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 85 + coordinates: [ 0.000000000000000, 0.375000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 86 + coordinates: [ 0.250000000000000, 0.375000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 87 + coordinates: [ 0.500000000000000, 0.375000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 88 + coordinates: [ 0.750000000000000, 0.375000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 89 + coordinates: [ 0.000000000000000, 0.625000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 90 + coordinates: [ 0.250000000000000, 0.625000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 91 + coordinates: [ 0.500000000000000, 0.625000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 92 + coordinates: [ 0.750000000000000, 0.625000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 93 + coordinates: [ 0.000000000000000, 0.875000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 94 + coordinates: [ 0.250000000000000, 0.875000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 95 + coordinates: [ 0.500000000000000, 0.875000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 96 + coordinates: [ 0.750000000000000, 0.875000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 97 + coordinates: [ 0.000000000000000, 0.125000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 98 + coordinates: [ 0.250000000000000, 0.125000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 99 + coordinates: [ 0.500000000000000, 0.125000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 100 + coordinates: [ 0.750000000000000, 0.125000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 101 + coordinates: [ 0.000000000000000, 0.375000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 102 + coordinates: [ 0.250000000000000, 0.375000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 103 + coordinates: [ 0.500000000000000, 0.375000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 104 + coordinates: [ 0.750000000000000, 0.375000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 105 + coordinates: [ 0.000000000000000, 0.625000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 106 + coordinates: [ 0.250000000000000, 0.625000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 107 + coordinates: [ 0.500000000000000, 0.625000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 108 + coordinates: [ 0.750000000000000, 0.625000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 109 + coordinates: [ 0.000000000000000, 0.875000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 110 + coordinates: [ 0.250000000000000, 0.875000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 111 + coordinates: [ 0.500000000000000, 0.875000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 112 + coordinates: [ 0.750000000000000, 0.875000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 113 + coordinates: [ 0.000000000000000, 0.125000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 114 + coordinates: [ 0.250000000000000, 0.125000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 115 + coordinates: [ 0.500000000000000, 0.125000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 116 + coordinates: [ 0.750000000000000, 0.125000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 117 + coordinates: [ 0.000000000000000, 0.375000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 118 + coordinates: [ 0.250000000000000, 0.375000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 119 + coordinates: [ 0.500000000000000, 0.375000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 120 + coordinates: [ 0.750000000000000, 0.375000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 121 + coordinates: [ 0.000000000000000, 0.625000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 122 + coordinates: [ 0.250000000000000, 0.625000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 123 + coordinates: [ 0.500000000000000, 0.625000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 124 + coordinates: [ 0.750000000000000, 0.625000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 125 + coordinates: [ 0.000000000000000, 0.875000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 126 + coordinates: [ 0.250000000000000, 0.875000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 127 + coordinates: [ 0.500000000000000, 0.875000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 128 + coordinates: [ 0.750000000000000, 0.875000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 129 + coordinates: [ 0.125000000000000, 0.000000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 130 + coordinates: [ 0.375000000000000, 0.000000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 131 + coordinates: [ 0.625000000000000, 0.000000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 132 + coordinates: [ 0.875000000000000, 0.000000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 133 + coordinates: [ 0.125000000000000, 0.250000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 134 + coordinates: [ 0.375000000000000, 0.250000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 135 + coordinates: [ 0.625000000000000, 0.250000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 136 + coordinates: [ 0.875000000000000, 0.250000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 137 + coordinates: [ 0.125000000000000, 0.500000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 138 + coordinates: [ 0.375000000000000, 0.500000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 139 + coordinates: [ 0.625000000000000, 0.500000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 140 + coordinates: [ 0.875000000000000, 0.500000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 141 + coordinates: [ 0.125000000000000, 0.750000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 142 + coordinates: [ 0.375000000000000, 0.750000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 143 + coordinates: [ 0.625000000000000, 0.750000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 144 + coordinates: [ 0.875000000000000, 0.750000000000000, 0.125000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 145 + coordinates: [ 0.125000000000000, 0.000000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 146 + coordinates: [ 0.375000000000000, 0.000000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 147 + coordinates: [ 0.625000000000000, 0.000000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 148 + coordinates: [ 0.875000000000000, 0.000000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 149 + coordinates: [ 0.125000000000000, 0.250000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 150 + coordinates: [ 0.375000000000000, 0.250000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 151 + coordinates: [ 0.625000000000000, 0.250000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 152 + coordinates: [ 0.875000000000000, 0.250000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 153 + coordinates: [ 0.125000000000000, 0.500000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 154 + coordinates: [ 0.375000000000000, 0.500000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 155 + coordinates: [ 0.625000000000000, 0.500000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 156 + coordinates: [ 0.875000000000000, 0.500000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 157 + coordinates: [ 0.125000000000000, 0.750000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 158 + coordinates: [ 0.375000000000000, 0.750000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 159 + coordinates: [ 0.625000000000000, 0.750000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 160 + coordinates: [ 0.875000000000000, 0.750000000000000, 0.375000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 161 + coordinates: [ 0.125000000000000, 0.000000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 162 + coordinates: [ 0.375000000000000, 0.000000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 163 + coordinates: [ 0.625000000000000, 0.000000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 164 + coordinates: [ 0.875000000000000, 0.000000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 165 + coordinates: [ 0.125000000000000, 0.250000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 166 + coordinates: [ 0.375000000000000, 0.250000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 167 + coordinates: [ 0.625000000000000, 0.250000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 168 + coordinates: [ 0.875000000000000, 0.250000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 169 + coordinates: [ 0.125000000000000, 0.500000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 170 + coordinates: [ 0.375000000000000, 0.500000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 171 + coordinates: [ 0.625000000000000, 0.500000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 172 + coordinates: [ 0.875000000000000, 0.500000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 173 + coordinates: [ 0.125000000000000, 0.750000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 174 + coordinates: [ 0.375000000000000, 0.750000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 175 + coordinates: [ 0.625000000000000, 0.750000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 176 + coordinates: [ 0.875000000000000, 0.750000000000000, 0.625000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 177 + coordinates: [ 0.125000000000000, 0.000000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 178 + coordinates: [ 0.375000000000000, 0.000000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 179 + coordinates: [ 0.625000000000000, 0.000000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 180 + coordinates: [ 0.875000000000000, 0.000000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 181 + coordinates: [ 0.125000000000000, 0.250000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 182 + coordinates: [ 0.375000000000000, 0.250000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 183 + coordinates: [ 0.625000000000000, 0.250000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 184 + coordinates: [ 0.875000000000000, 0.250000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 185 + coordinates: [ 0.125000000000000, 0.500000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 186 + coordinates: [ 0.375000000000000, 0.500000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 187 + coordinates: [ 0.625000000000000, 0.500000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 188 + coordinates: [ 0.875000000000000, 0.500000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 189 + coordinates: [ 0.125000000000000, 0.750000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 190 + coordinates: [ 0.375000000000000, 0.750000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 191 + coordinates: [ 0.625000000000000, 0.750000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 192 + coordinates: [ 0.875000000000000, 0.750000000000000, 0.875000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 193 + coordinates: [ 0.125000000000000, 0.125000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 194 + coordinates: [ 0.375000000000000, 0.125000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 195 + coordinates: [ 0.625000000000000, 0.125000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 196 + coordinates: [ 0.875000000000000, 0.125000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 197 + coordinates: [ 0.125000000000000, 0.375000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 198 + coordinates: [ 0.375000000000000, 0.375000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 199 + coordinates: [ 0.625000000000000, 0.375000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 200 + coordinates: [ 0.875000000000000, 0.375000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 201 + coordinates: [ 0.125000000000000, 0.625000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 202 + coordinates: [ 0.375000000000000, 0.625000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 203 + coordinates: [ 0.625000000000000, 0.625000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 204 + coordinates: [ 0.875000000000000, 0.625000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 205 + coordinates: [ 0.125000000000000, 0.875000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 206 + coordinates: [ 0.375000000000000, 0.875000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 207 + coordinates: [ 0.625000000000000, 0.875000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 208 + coordinates: [ 0.875000000000000, 0.875000000000000, 0.000000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 209 + coordinates: [ 0.125000000000000, 0.125000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 210 + coordinates: [ 0.375000000000000, 0.125000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 211 + coordinates: [ 0.625000000000000, 0.125000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 212 + coordinates: [ 0.875000000000000, 0.125000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 213 + coordinates: [ 0.125000000000000, 0.375000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 214 + coordinates: [ 0.375000000000000, 0.375000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 215 + coordinates: [ 0.625000000000000, 0.375000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 216 + coordinates: [ 0.875000000000000, 0.375000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 217 + coordinates: [ 0.125000000000000, 0.625000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 218 + coordinates: [ 0.375000000000000, 0.625000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 219 + coordinates: [ 0.625000000000000, 0.625000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 220 + coordinates: [ 0.875000000000000, 0.625000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 221 + coordinates: [ 0.125000000000000, 0.875000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 222 + coordinates: [ 0.375000000000000, 0.875000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 223 + coordinates: [ 0.625000000000000, 0.875000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 224 + coordinates: [ 0.875000000000000, 0.875000000000000, 0.250000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 225 + coordinates: [ 0.125000000000000, 0.125000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 226 + coordinates: [ 0.375000000000000, 0.125000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 227 + coordinates: [ 0.625000000000000, 0.125000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 228 + coordinates: [ 0.875000000000000, 0.125000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 229 + coordinates: [ 0.125000000000000, 0.375000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 230 + coordinates: [ 0.375000000000000, 0.375000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 231 + coordinates: [ 0.625000000000000, 0.375000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 232 + coordinates: [ 0.875000000000000, 0.375000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 233 + coordinates: [ 0.125000000000000, 0.625000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 234 + coordinates: [ 0.375000000000000, 0.625000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 235 + coordinates: [ 0.625000000000000, 0.625000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 236 + coordinates: [ 0.875000000000000, 0.625000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 237 + coordinates: [ 0.125000000000000, 0.875000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 238 + coordinates: [ 0.375000000000000, 0.875000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 239 + coordinates: [ 0.625000000000000, 0.875000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 240 + coordinates: [ 0.875000000000000, 0.875000000000000, 0.500000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 241 + coordinates: [ 0.125000000000000, 0.125000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 242 + coordinates: [ 0.375000000000000, 0.125000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 243 + coordinates: [ 0.625000000000000, 0.125000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 244 + coordinates: [ 0.875000000000000, 0.125000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 245 + coordinates: [ 0.125000000000000, 0.375000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 246 + coordinates: [ 0.375000000000000, 0.375000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 247 + coordinates: [ 0.625000000000000, 0.375000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 248 + coordinates: [ 0.875000000000000, 0.375000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 249 + coordinates: [ 0.125000000000000, 0.625000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 250 + coordinates: [ 0.375000000000000, 0.625000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 251 + coordinates: [ 0.625000000000000, 0.625000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 252 + coordinates: [ 0.875000000000000, 0.625000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 253 + coordinates: [ 0.125000000000000, 0.875000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 254 + coordinates: [ 0.375000000000000, 0.875000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 255 + coordinates: [ 0.625000000000000, 0.875000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 256 + coordinates: [ 0.875000000000000, 0.875000000000000, 0.750000000000000 ] + mass: 12.010700 + reduced_to: 1 + - symbol: C # 257 + coordinates: [ 0.062500000000000, 0.062500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 258 + coordinates: [ 0.312500000000000, 0.062500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 259 + coordinates: [ 0.562500000000000, 0.062500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 260 + coordinates: [ 0.812500000000000, 0.062500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 261 + coordinates: [ 0.062500000000000, 0.312500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 262 + coordinates: [ 0.312500000000000, 0.312500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 263 + coordinates: [ 0.562500000000000, 0.312500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 264 + coordinates: [ 0.812500000000000, 0.312500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 265 + coordinates: [ 0.062500000000000, 0.562500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 266 + coordinates: [ 0.312500000000000, 0.562500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 267 + coordinates: [ 0.562500000000000, 0.562500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 268 + coordinates: [ 0.812500000000000, 0.562500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 269 + coordinates: [ 0.062500000000000, 0.812500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 270 + coordinates: [ 0.312500000000000, 0.812500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 271 + coordinates: [ 0.562500000000000, 0.812500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 272 + coordinates: [ 0.812500000000000, 0.812500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 273 + coordinates: [ 0.062500000000000, 0.062500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 274 + coordinates: [ 0.312500000000000, 0.062500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 275 + coordinates: [ 0.562500000000000, 0.062500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 276 + coordinates: [ 0.812500000000000, 0.062500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 277 + coordinates: [ 0.062500000000000, 0.312500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 278 + coordinates: [ 0.312500000000000, 0.312500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 279 + coordinates: [ 0.562500000000000, 0.312500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 280 + coordinates: [ 0.812500000000000, 0.312500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 281 + coordinates: [ 0.062500000000000, 0.562500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 282 + coordinates: [ 0.312500000000000, 0.562500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 283 + coordinates: [ 0.562500000000000, 0.562500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 284 + coordinates: [ 0.812500000000000, 0.562500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 285 + coordinates: [ 0.062500000000000, 0.812500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 286 + coordinates: [ 0.312500000000000, 0.812500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 287 + coordinates: [ 0.562500000000000, 0.812500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 288 + coordinates: [ 0.812500000000000, 0.812500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 289 + coordinates: [ 0.062500000000000, 0.062500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 290 + coordinates: [ 0.312500000000000, 0.062500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 291 + coordinates: [ 0.562500000000000, 0.062500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 292 + coordinates: [ 0.812500000000000, 0.062500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 293 + coordinates: [ 0.062500000000000, 0.312500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 294 + coordinates: [ 0.312500000000000, 0.312500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 295 + coordinates: [ 0.562500000000000, 0.312500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 296 + coordinates: [ 0.812500000000000, 0.312500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 297 + coordinates: [ 0.062500000000000, 0.562500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 298 + coordinates: [ 0.312500000000000, 0.562500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 299 + coordinates: [ 0.562500000000000, 0.562500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 300 + coordinates: [ 0.812500000000000, 0.562500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 301 + coordinates: [ 0.062500000000000, 0.812500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 302 + coordinates: [ 0.312500000000000, 0.812500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 303 + coordinates: [ 0.562500000000000, 0.812500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 304 + coordinates: [ 0.812500000000000, 0.812500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 305 + coordinates: [ 0.062500000000000, 0.062500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 306 + coordinates: [ 0.312500000000000, 0.062500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 307 + coordinates: [ 0.562500000000000, 0.062500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 308 + coordinates: [ 0.812500000000000, 0.062500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 309 + coordinates: [ 0.062500000000000, 0.312500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 310 + coordinates: [ 0.312500000000000, 0.312500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 311 + coordinates: [ 0.562500000000000, 0.312500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 312 + coordinates: [ 0.812500000000000, 0.312500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 313 + coordinates: [ 0.062500000000000, 0.562500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 314 + coordinates: [ 0.312500000000000, 0.562500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 315 + coordinates: [ 0.562500000000000, 0.562500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 316 + coordinates: [ 0.812500000000000, 0.562500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 317 + coordinates: [ 0.062500000000000, 0.812500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 318 + coordinates: [ 0.312500000000000, 0.812500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 319 + coordinates: [ 0.562500000000000, 0.812500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 320 + coordinates: [ 0.812500000000000, 0.812500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 321 + coordinates: [ 0.062500000000000, 0.187500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 322 + coordinates: [ 0.312500000000000, 0.187500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 323 + coordinates: [ 0.562500000000000, 0.187500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 324 + coordinates: [ 0.812500000000000, 0.187500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 325 + coordinates: [ 0.062500000000000, 0.437500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 326 + coordinates: [ 0.312500000000000, 0.437500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 327 + coordinates: [ 0.562500000000000, 0.437500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 328 + coordinates: [ 0.812500000000000, 0.437500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 329 + coordinates: [ 0.062500000000000, 0.687500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 330 + coordinates: [ 0.312500000000000, 0.687500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 331 + coordinates: [ 0.562500000000000, 0.687500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 332 + coordinates: [ 0.812500000000000, 0.687500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 333 + coordinates: [ 0.062500000000000, 0.937500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 334 + coordinates: [ 0.312500000000000, 0.937500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 335 + coordinates: [ 0.562500000000000, 0.937500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 336 + coordinates: [ 0.812500000000000, 0.937500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 337 + coordinates: [ 0.062500000000000, 0.187500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 338 + coordinates: [ 0.312500000000000, 0.187500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 339 + coordinates: [ 0.562500000000000, 0.187500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 340 + coordinates: [ 0.812500000000000, 0.187500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 341 + coordinates: [ 0.062500000000000, 0.437500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 342 + coordinates: [ 0.312500000000000, 0.437500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 343 + coordinates: [ 0.562500000000000, 0.437500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 344 + coordinates: [ 0.812500000000000, 0.437500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 345 + coordinates: [ 0.062500000000000, 0.687500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 346 + coordinates: [ 0.312500000000000, 0.687500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 347 + coordinates: [ 0.562500000000000, 0.687500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 348 + coordinates: [ 0.812500000000000, 0.687500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 349 + coordinates: [ 0.062500000000000, 0.937500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 350 + coordinates: [ 0.312500000000000, 0.937500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 351 + coordinates: [ 0.562500000000000, 0.937500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 352 + coordinates: [ 0.812500000000000, 0.937500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 353 + coordinates: [ 0.062500000000000, 0.187500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 354 + coordinates: [ 0.312500000000000, 0.187500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 355 + coordinates: [ 0.562500000000000, 0.187500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 356 + coordinates: [ 0.812500000000000, 0.187500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 357 + coordinates: [ 0.062500000000000, 0.437500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 358 + coordinates: [ 0.312500000000000, 0.437500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 359 + coordinates: [ 0.562500000000000, 0.437500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 360 + coordinates: [ 0.812500000000000, 0.437500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 361 + coordinates: [ 0.062500000000000, 0.687500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 362 + coordinates: [ 0.312500000000000, 0.687500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 363 + coordinates: [ 0.562500000000000, 0.687500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 364 + coordinates: [ 0.812500000000000, 0.687500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 365 + coordinates: [ 0.062500000000000, 0.937500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 366 + coordinates: [ 0.312500000000000, 0.937500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 367 + coordinates: [ 0.562500000000000, 0.937500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 368 + coordinates: [ 0.812500000000000, 0.937500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 369 + coordinates: [ 0.062500000000000, 0.187500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 370 + coordinates: [ 0.312500000000000, 0.187500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 371 + coordinates: [ 0.562500000000000, 0.187500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 372 + coordinates: [ 0.812500000000000, 0.187500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 373 + coordinates: [ 0.062500000000000, 0.437500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 374 + coordinates: [ 0.312500000000000, 0.437500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 375 + coordinates: [ 0.562500000000000, 0.437500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 376 + coordinates: [ 0.812500000000000, 0.437500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 377 + coordinates: [ 0.062500000000000, 0.687500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 378 + coordinates: [ 0.312500000000000, 0.687500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 379 + coordinates: [ 0.562500000000000, 0.687500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 380 + coordinates: [ 0.812500000000000, 0.687500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 381 + coordinates: [ 0.062500000000000, 0.937500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 382 + coordinates: [ 0.312500000000000, 0.937500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 383 + coordinates: [ 0.562500000000000, 0.937500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 384 + coordinates: [ 0.812500000000000, 0.937500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 385 + coordinates: [ 0.187500000000000, 0.062500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 386 + coordinates: [ 0.437500000000000, 0.062500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 387 + coordinates: [ 0.687500000000000, 0.062500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 388 + coordinates: [ 0.937500000000000, 0.062500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 389 + coordinates: [ 0.187500000000000, 0.312500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 390 + coordinates: [ 0.437500000000000, 0.312500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 391 + coordinates: [ 0.687500000000000, 0.312500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 392 + coordinates: [ 0.937500000000000, 0.312500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 393 + coordinates: [ 0.187500000000000, 0.562500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 394 + coordinates: [ 0.437500000000000, 0.562500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 395 + coordinates: [ 0.687500000000000, 0.562500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 396 + coordinates: [ 0.937500000000000, 0.562500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 397 + coordinates: [ 0.187500000000000, 0.812500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 398 + coordinates: [ 0.437500000000000, 0.812500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 399 + coordinates: [ 0.687500000000000, 0.812500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 400 + coordinates: [ 0.937500000000000, 0.812500000000000, 0.187500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 401 + coordinates: [ 0.187500000000000, 0.062500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 402 + coordinates: [ 0.437500000000000, 0.062500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 403 + coordinates: [ 0.687500000000000, 0.062500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 404 + coordinates: [ 0.937500000000000, 0.062500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 405 + coordinates: [ 0.187500000000000, 0.312500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 406 + coordinates: [ 0.437500000000000, 0.312500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 407 + coordinates: [ 0.687500000000000, 0.312500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 408 + coordinates: [ 0.937500000000000, 0.312500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 409 + coordinates: [ 0.187500000000000, 0.562500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 410 + coordinates: [ 0.437500000000000, 0.562500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 411 + coordinates: [ 0.687500000000000, 0.562500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 412 + coordinates: [ 0.937500000000000, 0.562500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 413 + coordinates: [ 0.187500000000000, 0.812500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 414 + coordinates: [ 0.437500000000000, 0.812500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 415 + coordinates: [ 0.687500000000000, 0.812500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 416 + coordinates: [ 0.937500000000000, 0.812500000000000, 0.437500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 417 + coordinates: [ 0.187500000000000, 0.062500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 418 + coordinates: [ 0.437500000000000, 0.062500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 419 + coordinates: [ 0.687500000000000, 0.062500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 420 + coordinates: [ 0.937500000000000, 0.062500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 421 + coordinates: [ 0.187500000000000, 0.312500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 422 + coordinates: [ 0.437500000000000, 0.312500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 423 + coordinates: [ 0.687500000000000, 0.312500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 424 + coordinates: [ 0.937500000000000, 0.312500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 425 + coordinates: [ 0.187500000000000, 0.562500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 426 + coordinates: [ 0.437500000000000, 0.562500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 427 + coordinates: [ 0.687500000000000, 0.562500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 428 + coordinates: [ 0.937500000000000, 0.562500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 429 + coordinates: [ 0.187500000000000, 0.812500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 430 + coordinates: [ 0.437500000000000, 0.812500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 431 + coordinates: [ 0.687500000000000, 0.812500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 432 + coordinates: [ 0.937500000000000, 0.812500000000000, 0.687500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 433 + coordinates: [ 0.187500000000000, 0.062500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 434 + coordinates: [ 0.437500000000000, 0.062500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 435 + coordinates: [ 0.687500000000000, 0.062500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 436 + coordinates: [ 0.937500000000000, 0.062500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 437 + coordinates: [ 0.187500000000000, 0.312500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 438 + coordinates: [ 0.437500000000000, 0.312500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 439 + coordinates: [ 0.687500000000000, 0.312500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 440 + coordinates: [ 0.937500000000000, 0.312500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 441 + coordinates: [ 0.187500000000000, 0.562500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 442 + coordinates: [ 0.437500000000000, 0.562500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 443 + coordinates: [ 0.687500000000000, 0.562500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 444 + coordinates: [ 0.937500000000000, 0.562500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 445 + coordinates: [ 0.187500000000000, 0.812500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 446 + coordinates: [ 0.437500000000000, 0.812500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 447 + coordinates: [ 0.687500000000000, 0.812500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 448 + coordinates: [ 0.937500000000000, 0.812500000000000, 0.937500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 449 + coordinates: [ 0.187500000000000, 0.187500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 450 + coordinates: [ 0.437500000000000, 0.187500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 451 + coordinates: [ 0.687500000000000, 0.187500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 452 + coordinates: [ 0.937500000000000, 0.187500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 453 + coordinates: [ 0.187500000000000, 0.437500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 454 + coordinates: [ 0.437500000000000, 0.437500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 455 + coordinates: [ 0.687500000000000, 0.437500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 456 + coordinates: [ 0.937500000000000, 0.437500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 457 + coordinates: [ 0.187500000000000, 0.687500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 458 + coordinates: [ 0.437500000000000, 0.687500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 459 + coordinates: [ 0.687500000000000, 0.687500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 460 + coordinates: [ 0.937500000000000, 0.687500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 461 + coordinates: [ 0.187500000000000, 0.937500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 462 + coordinates: [ 0.437500000000000, 0.937500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 463 + coordinates: [ 0.687500000000000, 0.937500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 464 + coordinates: [ 0.937500000000000, 0.937500000000000, 0.062500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 465 + coordinates: [ 0.187500000000000, 0.187500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 466 + coordinates: [ 0.437500000000000, 0.187500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 467 + coordinates: [ 0.687500000000000, 0.187500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 468 + coordinates: [ 0.937500000000000, 0.187500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 469 + coordinates: [ 0.187500000000000, 0.437500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 470 + coordinates: [ 0.437500000000000, 0.437500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 471 + coordinates: [ 0.687500000000000, 0.437500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 472 + coordinates: [ 0.937500000000000, 0.437500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 473 + coordinates: [ 0.187500000000000, 0.687500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 474 + coordinates: [ 0.437500000000000, 0.687500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 475 + coordinates: [ 0.687500000000000, 0.687500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 476 + coordinates: [ 0.937500000000000, 0.687500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 477 + coordinates: [ 0.187500000000000, 0.937500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 478 + coordinates: [ 0.437500000000000, 0.937500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 479 + coordinates: [ 0.687500000000000, 0.937500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 480 + coordinates: [ 0.937500000000000, 0.937500000000000, 0.312500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 481 + coordinates: [ 0.187500000000000, 0.187500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 482 + coordinates: [ 0.437500000000000, 0.187500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 483 + coordinates: [ 0.687500000000000, 0.187500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 484 + coordinates: [ 0.937500000000000, 0.187500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 485 + coordinates: [ 0.187500000000000, 0.437500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 486 + coordinates: [ 0.437500000000000, 0.437500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 487 + coordinates: [ 0.687500000000000, 0.437500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 488 + coordinates: [ 0.937500000000000, 0.437500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 489 + coordinates: [ 0.187500000000000, 0.687500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 490 + coordinates: [ 0.437500000000000, 0.687500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 491 + coordinates: [ 0.687500000000000, 0.687500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 492 + coordinates: [ 0.937500000000000, 0.687500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 493 + coordinates: [ 0.187500000000000, 0.937500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 494 + coordinates: [ 0.437500000000000, 0.937500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 495 + coordinates: [ 0.687500000000000, 0.937500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 496 + coordinates: [ 0.937500000000000, 0.937500000000000, 0.562500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 497 + coordinates: [ 0.187500000000000, 0.187500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 498 + coordinates: [ 0.437500000000000, 0.187500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 499 + coordinates: [ 0.687500000000000, 0.187500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 500 + coordinates: [ 0.937500000000000, 0.187500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 501 + coordinates: [ 0.187500000000000, 0.437500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 502 + coordinates: [ 0.437500000000000, 0.437500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 503 + coordinates: [ 0.687500000000000, 0.437500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 504 + coordinates: [ 0.937500000000000, 0.437500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 505 + coordinates: [ 0.187500000000000, 0.687500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 506 + coordinates: [ 0.437500000000000, 0.687500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 507 + coordinates: [ 0.687500000000000, 0.687500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 508 + coordinates: [ 0.937500000000000, 0.687500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 509 + coordinates: [ 0.187500000000000, 0.937500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 510 + coordinates: [ 0.437500000000000, 0.937500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 511 + coordinates: [ 0.687500000000000, 0.937500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + - symbol: C # 512 + coordinates: [ 0.937500000000000, 0.937500000000000, 0.812500000000000 ] + mass: 12.010700 + reduced_to: 257 + +displacements: +- atom: 1 + displacement: + [ 0.0100000000000000, 0.0000000000000000, 0.0000000000000000 ] From fd8f6fdf262d955affb5271e377e3d2b8b676e24 Mon Sep 17 00:00:00 2001 From: Mariia Radova <7radians@gmail.com> Date: Tue, 24 Feb 2026 15:04:48 +0000 Subject: [PATCH 2/6] Add diamond and Ti64 phonon analysis, calcs, apps and utilities --- .../user_guide/benchmarks/bulk_crystal.rst | 114 +++- .../analyse_diamond_phonons.py | 212 +++---- .../bulk_crystal/diamond_phonons/metrics.yml | 8 +- .../ti64_phonons/analyse_ti64_phonons.py | 538 ++++++++++++++++ .../bulk_crystal/ti64_phonons/metrics.yml | 40 ++ .../diamond_phonons/app_diamond_phonons.py | 47 +- .../diamond_interactive_helpers.py | 199 +++--- .../ti64_phonons/app_ti64_phonons.py | 170 +++++ .../ti64_phonons/ti64_interactive_helpers.py | 510 +++++++++++++++ .../diamond_phonons/calc_diamond_phonons.py | 16 +- .../ti64_phonons/calc_ti64_phonons.py | 591 ++++++++++++++++++ ml_peg/calcs/utils/ASE_to_phonons.py | 492 +++++++++++++++ .../utils/CASTEP_reader_phonon_dispersion.py | 213 +++++++ 13 files changed, 2927 insertions(+), 223 deletions(-) create mode 100644 ml_peg/analysis/bulk_crystal/ti64_phonons/analyse_ti64_phonons.py create mode 100644 ml_peg/analysis/bulk_crystal/ti64_phonons/metrics.yml create mode 100644 ml_peg/app/bulk_crystal/ti64_phonons/app_ti64_phonons.py create mode 100644 ml_peg/app/bulk_crystal/ti64_phonons/ti64_interactive_helpers.py create mode 100644 ml_peg/calcs/bulk_crystal/ti64_phonons/calc_ti64_phonons.py create mode 100644 ml_peg/calcs/utils/ASE_to_phonons.py create mode 100644 ml_peg/calcs/utils/CASTEP_reader_phonon_dispersion.py diff --git a/docs/source/user_guide/benchmarks/bulk_crystal.rst b/docs/source/user_guide/benchmarks/bulk_crystal.rst index fc3ada371..ceda6f9b9 100644 --- a/docs/source/user_guide/benchmarks/bulk_crystal.rst +++ b/docs/source/user_guide/benchmarks/bulk_crystal.rst @@ -160,7 +160,7 @@ Medium: tests typically take a few minutes to run on CPU. Data availability ----------------- -Input structures: +Input structures (https://github.com/7radians/ml-peg-data/tree/main/diamond_data): * A primitive bulk diamond unit cell containing two carbon atoms, used to generate a phonopy displacement dataset on a 4×4×4 supercell. @@ -170,3 +170,115 @@ Reference data: * DFT phonon band structure for bulk diamond along a fixed high-symmetry path, provided as ``dft_band.npz``. * RSCAN. + + +Ti64 phonons +============ + +Summary +------- + +Performance in predicting phonon dispersions, vibrational densities of states (DOS/PDOS), +and thermodynamic Helmholtz free energies for a suite of 10 Ti–Al–V alloy phases. + +Each case is evaluated by comparing ML-predicted phonon frequencies to CASTEP reference +phonon frequencies along a fixed high-symmetry q-path. For a subset of cases, +Helmholtz free-energy errors per atom are additionally reported. + + +Metrics +------- + +1. Dispersion RMSE (mean) + + Mean root mean squared error (RMSE) between predicted and reference phonon frequencies, + averaged over 10 Ti64 cases. + + For each case, reference phonon frequencies are parsed from CASTEP ``.castep`` outputs + along a fixed high-symmetry q-path. The structure is then relaxed for each model using + the LBFGS optimiser (maximum 10000 steps, ``fmax=0.001``). Phonon frequencies are computed + using finite displacements in a 2×2×2 supercell with a displacement magnitude of 0.02 Å and + ``plusminus=True``. The reference dispersion is linearly interpolated onto an inferred ML + path-coordinate grid spanning the same path (a uniform grid with the same number of + q-points as the ML dispersion), and the RMSE is evaluated over all q-points and all + phonon branches. + +2. Dispersion RMSE (max) + + Maximum per-case dispersion RMSE (in THz) over the 10 Ti64 cases. + + Computed as in (1), but taking the maximum RMSE value across cases. + +3. ω_avg MAE + + Mean absolute error (MAE) in the average phonon frequency ω_avg over the 10 Ti64 cases. + + For each case, ω_avg is computed as the arithmetic mean of all phonon frequencies after + interpolating the reference dispersion onto the inferred ML grid. The per-case absolute error is + then averaged across cases. Frequencies are averaged as stored; if imaginary modes are present + as negative values, they contribute directly. + +4. ΔF (0 K) mean + + Mean absolute error in Helmholtz free energy at 0 K, reported as eV/atom, over the + subset of cases where thermodynamic outputs are available. + + For applicable cases, CASTEP q-point phonon frequencies and q-point weights are parsed + from CASTEP qpoints ``.castep`` outputs. A reference Helmholtz free energy is computed + in the harmonic approximation by combining a weighted zero-point energy contribution + with a weighted thermal free-energy contribution evaluated on a dense temperature grid + (2000 points spanning 0–2000 K) and interpolated to the ML temperatures. The absolute + difference between ML and reference free energy at 0 K is divided by the number of atoms + and averaged across thermodynamics-enabled cases. Weights are taken directly from CASTEP; + no explicit renormalization is applied. + +5. ΔF (2000 K) mean + + Mean absolute error in Helmholtz free energy at 2000 K, reported as eV/atom, over the + subset of cases where thermodynamic outputs are available. + + Computed as in (4), but using the final temperature point (2000 K). + + +Computational cost +------------------ + +Medium: dispersion, DOS/PDOS and thermodynamic calculations typically take minutes per model on CPU. +Thermodynamic calculations are enabled for a 7/10 subset of cases. + + +Data availability +----------------- + +Full details on the data and benchmark: + +* Allen, C. S. & Bartók, A. P. Multi-phase dataset for Ti and Ti-6Al-4V. + Preprint at https://arxiv.org/abs/2501.06116 (2025). + +* Radova, M., Stark, W. G., Allen, C. S. et al. + Fine-tuning foundation models of materials interatomic potentials + with frozen transfer learning. + npj Comput Mater 11, 237 (2025). + https://doi.org/10.1038/s41524-025-01727-x + +Input structures (https://github.com/7radians/ml-peg-data/tree/main/ti64_data): + +* CASTEP ``.castep`` outputs providing reference phonon dispersions along fixed + high-symmetry q-paths for 10 Ti–Al–V alloy cases. +* Corresponding CASTEP qpoints ``.castep`` outputs (subset) providing q-point phonon + frequencies and weights for thermodynamic reference reconstruction. + +Reference data: + +* CASTEP phonon dispersions parsed from ``.castep`` outputs (q-path dispersion). +* CASTEP q-point phonon frequencies and weights parsed from qpoints ``.castep`` outputs + (subset), used to compute reference Helmholtz free energies in the harmonic + approximation. +* PBE + +Computational environment +------------------------- + +Ti64 phonon calculations were run locally as a single process on CPU on an +x86_64 machine (11th Gen Intel(R) Core(TM) i5-1145G7; 4 cores / 8 threads). No explicit +parallel execution (MPI or multiprocessing) was used in the benchmark driver. diff --git a/ml_peg/analysis/bulk_crystal/diamond_phonons/analyse_diamond_phonons.py b/ml_peg/analysis/bulk_crystal/diamond_phonons/analyse_diamond_phonons.py index 847168e41..11397b5b1 100644 --- a/ml_peg/analysis/bulk_crystal/diamond_phonons/analyse_diamond_phonons.py +++ b/ml_peg/analysis/bulk_crystal/diamond_phonons/analyse_diamond_phonons.py @@ -1,17 +1,8 @@ -""" -Analyse diamond phonon dispersion benchmark (bands only). - -Notes ------ -This analysis produces two JSON artifacts used by the diamond phonon Dash app: - -- ``diamond_phonons_bands_table.json``: metrics table for the Dash table component. -- ``diamond_phonons_bands_interactive.json``: interactive scatter dataset consumed by - the PhononApp callbacks. -""" +"""Analyse diamond phonon dispersion benchmark (bands only).""" from __future__ import annotations +import json from pathlib import Path from typing import Any @@ -20,49 +11,61 @@ import yaml # type: ignore from ml_peg.analysis.utils.decorators import build_table, cell_to_scatter +from ml_peg.analysis.utils.utils import load_metrics_config from ml_peg.app import APP_ROOT from ml_peg.calcs import CALCS_ROOT +from ml_peg.calcs.utils.utils import download_github_data from ml_peg.models.get_models import get_model_names from ml_peg.models.models import current_models +GITHUB_BASE = "https://raw.githubusercontent.com/7radians/ml-peg-data/main" + +EXTRACTED_ROOT = Path( + download_github_data( + filename="diamond_data/data.zip", + github_uri=GITHUB_BASE, + ) +) + +CALC_DATA = EXTRACTED_ROOT / "data" + + MODELS = get_model_names(current_models) CATEGORY = "bulk_crystal" BENCH = "diamond_phonons" CALC_PATH = CALCS_ROOT / CATEGORY / BENCH / "outputs" -CALC_DATA = CALCS_ROOT / CATEGORY / BENCH / "data" + +# CALC_DATA = CALCS_ROOT / CATEGORY / BENCH / "data" + + OUT_PATH = APP_ROOT / "data" / CATEGORY / BENCH +# cm^-1 per THz (to convert the DFT reference to THz) THZ_TO_CM1 = 33.35640951981521 NBANDS = 6 SCATTER_FILENAME = OUT_PATH / "diamond_phonons_bands_interactive.json" -# Internal keys used in the interactive dataset (not shown to user) METRIC_KEY_MAE = "band_mae" METRIC_KEY_RMSE = "band_rmse" -# Visible labels (must match the table column headers exactly) METRIC_LABEL_MAE = "Band MAE" METRIC_LABEL_RMSE = "Band RMSE" + METRICS_YML = Path(__file__).with_name("metrics.yml") -with METRICS_YML.open("r", encoding="utf8") as f: - _cfg = yaml.safe_load(f) or {} - -_metric_specs = _cfg.get("metrics", {}) -if not isinstance(_metric_specs, dict) or not _metric_specs: - raise ValueError(f"{METRICS_YML} must contain a 'metrics:' mapping.") - -THRESHOLDS = { - name: { - "good": spec.get("good"), - "bad": spec.get("bad"), - "unit": spec.get("unit", "cm-1"), - } - for name, spec in _metric_specs.items() -} +THRESHOLDS, METRIC_TOOLTIPS, WEIGHTS = load_metrics_config(METRICS_YML) + +_expected_metric_labels = {METRIC_LABEL_MAE, METRIC_LABEL_RMSE} +_yaml_metric_labels = set(THRESHOLDS.keys()) +missing = _expected_metric_labels - _yaml_metric_labels +if missing: + raise ValueError( + f"{METRICS_YML}: missing metrics for labels {sorted(missing)}. " + f"Found: {sorted(_yaml_metric_labels)}" + ) def _load_reference_npz(path: Path) -> dict[str, Any]: @@ -80,8 +83,8 @@ def _load_reference_npz(path: Path) -> dict[str, Any]: dict[str, Any] Mapping with keys: - - ``freqs``: array of shape ``(Nq, NBANDS)`` in cm-1 - - ``units``: ``"cm-1"`` + - ``freqs``: array of shape ``(Nq, NBANDS)`` in THz + - ``units``: ``"THz"`` - ``path``: input path """ if not path.exists(): @@ -93,7 +96,6 @@ def _load_reference_npz(path: Path) -> dict[str, Any]: freqs_cm1 = np.asarray(d["frequencies"], dtype=float) - # Allow legacy (1, Nq, NBANDS) storage if freqs_cm1.ndim == 3 and freqs_cm1.shape[0] == 1: freqs_cm1 = freqs_cm1[0] @@ -104,7 +106,9 @@ def _load_reference_npz(path: Path) -> dict[str, Any]: if not np.isfinite(freqs_cm1).all(): raise ValueError(f"{path}: contains non-finite reference frequencies.") - return {"freqs": freqs_cm1, "units": "cm-1", "path": path} + freqs_thz = freqs_cm1 / THZ_TO_CM1 + + return {"freqs": freqs_thz, "units": "THz", "path": path} def _load_band_yaml(path: Path) -> dict[str, Any]: @@ -148,33 +152,6 @@ def _load_band_yaml(path: Path) -> dict[str, Any]: return {"freqs": freqs, "units": "THz", "path": path} -def _convert_units(freqs: np.ndarray, src: str, dst: str) -> np.ndarray: - """ - Convert between THz and cm-1. - - Parameters - ---------- - freqs - Frequency array. - src - Source unit (``"THz"`` or ``"cm-1"``). - dst - Destination unit (``"THz"`` or ``"cm-1"``). - - Returns - ------- - numpy.ndarray - Converted frequencies. - """ - if src == dst: - return freqs - if src == "THz" and dst == "cm-1": - return freqs * THZ_TO_CM1 - if src == "cm-1" and dst == "THz": - return freqs / THZ_TO_CM1 - raise ValueError(f"Unsupported unit conversion {src} -> {dst}") - - def _sorted_flat(freqs: np.ndarray) -> np.ndarray: """ Sort each q-point's bands then flatten. @@ -198,38 +175,38 @@ def _sorted_flat(freqs: np.ndarray) -> np.ndarray: def _mae(a: np.ndarray, b: np.ndarray) -> float: """ - Compute mean absolute error. + Compute mean absolute error (THz). Parameters ---------- a - Predicted values. + Predicted values (THz), same shape as ``b``. b - Reference values. + Reference values (THz), same shape as ``a``. Returns ------- float - Mean absolute error. + Mean absolute error in THz. """ return float(np.mean(np.abs(a - b))) def _rmse(a: np.ndarray, b: np.ndarray) -> float: """ - Compute root mean squared error. + Compute root mean squared error (THz). Parameters ---------- a - Predicted values. + Predicted values (THz), same shape as ``b``. b - Reference values. + Reference values (THz), same shape as ``a``. Returns ------- float - Root mean squared error. + Root mean squared error in THz. """ d = a - b return float(np.sqrt(np.mean(d * d))) @@ -243,31 +220,28 @@ def reference() -> dict[str, Any]: Returns ------- dict[str, Any] - Reference data mapping returned by :func:`_load_reference_npz`. + Reference mapping as returned by :func:`_load_reference_npz`. """ OUT_PATH.mkdir(parents=True, exist_ok=True) return _load_reference_npz(CALC_DATA / "dft_band.npz") -def _model_flat(model_name: str, ref_units: str) -> np.ndarray: +def _model_flat(model_name: str) -> np.ndarray: """ - Load, unit-convert, and flatten one model's predicted bands. + Load and flatten one model's predicted bands (THz). Parameters ---------- model_name - Model name under ``outputs/``. - ref_units - Reference unit string. + Model identifier used to locate ``{CALC_PATH}/{model_name}/band.yaml``. Returns ------- numpy.ndarray - Flattened predicted frequencies. + Flattened frequencies of shape ``(Nq * NBANDS,)`` in THz. """ pred = _load_band_yaml(CALC_PATH / model_name / "band.yaml") - freqs = _convert_units(pred["freqs"], pred["units"], ref_units) - return _sorted_flat(freqs) + return _sorted_flat(np.asarray(pred["freqs"], dtype=float)) @pytest.fixture @@ -278,20 +252,19 @@ def flat_bands(reference: dict[str, Any]) -> tuple[np.ndarray, dict[str, np.ndar Parameters ---------- reference - Reference mapping from the ``reference`` fixture. + Reference mapping as returned by :func:`reference`. Returns ------- - tuple - ``(ref_flat, pred_flats)`` where ``ref_flat`` is a 1D array and - ``pred_flats`` maps model name to a 1D array. + tuple[numpy.ndarray, dict[str, numpy.ndarray]] + ``(ref_flat, pred_flats)`` where ``ref_flat`` has shape ``(Nq * NBANDS,)`` + and ``pred_flats`` maps model name to an array of the same shape. """ - ref_flat = _sorted_flat(reference["freqs"]) - ref_units = reference["units"] + ref_flat = _sorted_flat(np.asarray(reference["freqs"], dtype=float)) pred_flats: dict[str, np.ndarray] = {} for model_name in MODELS: - pred_flat = _model_flat(model_name, ref_units) + pred_flat = _model_flat(model_name) if pred_flat.shape != ref_flat.shape: raise ValueError( f"{model_name}: prediction and reference flattened shapes differ " @@ -307,17 +280,17 @@ def band_errors( flat_bands: tuple[np.ndarray, dict[str, np.ndarray]], ) -> dict[str, dict[str, float]]: """ - Compute MAE and RMSE for each model. + Compute MAE and RMSE for each model (THz). Parameters ---------- flat_bands - Output of the ``flat_bands`` fixture. + Tuple ``(ref_flat, pred_flats)`` as returned by :func:`flat_bands`. Returns ------- dict[str, dict[str, float]] - Mapping ``model -> {"mae": ..., "rmse": ...}``. + Mapping ``model_name -> {"mae": float, "rmse": float}`` in THz. """ ref_flat, pred_flats = flat_bands @@ -335,10 +308,8 @@ def band_errors( @build_table( filename=OUT_PATH / "diamond_phonons_bands_table.json", thresholds=THRESHOLDS, - metric_tooltips={ - "Band MAE": "Mean absolute error over all q-points and phonon branches", - "Band RMSE": "Root mean squared error over all q-points and phonon branches", - }, + metric_tooltips=METRIC_TOOLTIPS, + weights=WEIGHTS, ) def metrics(band_errors: dict[str, dict[str, float]]) -> dict[str, dict[str, float]]: """ @@ -347,12 +318,12 @@ def metrics(band_errors: dict[str, dict[str, float]]) -> dict[str, dict[str, flo Parameters ---------- band_errors - Output of the ``band_errors`` fixture. + Per-model MAE/RMSE mapping as returned by :func:`band_errors`. Returns ------- dict[str, dict[str, float]] - Mapping from metric label to per-model values. + Mapping from visible metric label to per-model values. """ return { METRIC_LABEL_MAE: {m: band_errors[m]["mae"] for m in MODELS}, @@ -371,14 +342,15 @@ def band_stats( Parameters ---------- flat_bands - Output of the ``flat_bands`` fixture. + Tuple ``(ref_flat, pred_flats)`` as returned by :func:`flat_bands`. band_errors - Output of the ``band_errors`` fixture. + Per-model MAE/RMSE mapping as returned by :func:`band_errors`. Returns ------- dict[str, dict[str, Any]] - Per-model mapping containing scatter points and scalar metrics. + Per-model structures containing points and metric values used to build the + interactive scatter dataset. """ ref_flat, pred_flats = flat_bands @@ -418,8 +390,8 @@ def band_stats( @pytest.fixture @cell_to_scatter( filename=SCATTER_FILENAME, - x_label="Predicted frequency (cm-1)", - y_label="DFT frequency (cm-1)", + x_label="Predicted frequency (THz)", + y_label="DFT frequency (THz)", ) def interactive_dataset(band_stats: dict[str, dict[str, Any]]) -> dict[str, Any]: """ @@ -428,12 +400,12 @@ def interactive_dataset(band_stats: dict[str, dict[str, Any]]) -> dict[str, Any] Parameters ---------- band_stats - Output of the ``band_stats`` fixture. + Per-model point/metric structures as returned by :func:`band_stats`. Returns ------- dict[str, Any] - Interactive dataset written to JSON by the decorator. + Interactive dataset payload written to JSON by the decorator. """ dataset: dict[str, Any] = { "metrics": { @@ -459,21 +431,41 @@ def interactive_dataset(band_stats: dict[str, dict[str, Any]]) -> dict[str, Any] return dataset -def test_diamond_phonons_analysis(metrics, interactive_dataset) -> None: +def test_diamond_phonons_analysis( + metrics: dict[str, Any], interactive_dataset: dict[str, Any] +) -> None: """ Generate JSON artifacts for the diamond phonons benchmark. Parameters ---------- metrics - Table metrics fixture output (decorator writes JSON). + Table fixture output (decorator writes JSON). interactive_dataset - Scatter dataset fixture output (decorator writes JSON). - - Returns - ------- - None - This test passes if fixtures execute successfully. + Scatter fixture output (decorator writes JSON). """ - _ = metrics - _ = interactive_dataset + assert isinstance(metrics, dict) + assert isinstance(interactive_dataset, dict) + + table_path = OUT_PATH / "diamond_phonons_bands_table.json" + assert table_path.exists() + + table_payload = json.loads(table_path.read_text(encoding="utf8")) + rows = table_payload.get("data", []) + ids = {row.get("id") for row in rows if isinstance(row, dict)} + missing_rows = [m for m in MODELS if m not in ids] + assert not missing_rows, f"Table missing model rows: {missing_rows}" + + assert SCATTER_FILENAME.exists() + scatter_payload = json.loads(SCATTER_FILENAME.read_text(encoding="utf8")) + models = scatter_payload.get("models", {}) + missing_models = [m for m in MODELS if m not in models] + assert not missing_models, f"Interactive dataset missing models: {missing_models}" + + # Ensure each model has both metrics and some points. + for model_name in MODELS: + model_metrics = (models.get(model_name) or {}).get("metrics", {}) + for key in (METRIC_KEY_MAE, METRIC_KEY_RMSE): + assert key in model_metrics, f"{model_name}: missing metric '{key}'" + points = model_metrics[key].get("points", []) + assert points, f"{model_name}: empty points for '{key}'" diff --git a/ml_peg/analysis/bulk_crystal/diamond_phonons/metrics.yml b/ml_peg/analysis/bulk_crystal/diamond_phonons/metrics.yml index 06bb82baf..c296a52b0 100644 --- a/ml_peg/analysis/bulk_crystal/diamond_phonons/metrics.yml +++ b/ml_peg/analysis/bulk_crystal/diamond_phonons/metrics.yml @@ -1,16 +1,16 @@ metrics: Band MAE: good: 0 - bad: 50.0 - unit: cm-1 + bad: 2.0 + unit: THz tooltip: "Mean absolute error of phonon frequencies for diamond over all sampled q-points and phonon branches along the chosen band path." weight: 1.0 level_of_theory: RSCAN Band RMSE: good: 0.0 - bad: 50.0 - unit: cm-1 + bad: 2.0 + unit: THz tooltip: "Root mean squared error of phonon frequencies for diamond over all sampled q-points and phonon branches along the chosen band path." weight: 1.0 level_of_theory: RSCAN diff --git a/ml_peg/analysis/bulk_crystal/ti64_phonons/analyse_ti64_phonons.py b/ml_peg/analysis/bulk_crystal/ti64_phonons/analyse_ti64_phonons.py new file mode 100644 index 000000000..36d75fb2f --- /dev/null +++ b/ml_peg/analysis/bulk_crystal/ti64_phonons/analyse_ti64_phonons.py @@ -0,0 +1,538 @@ +"""Analyse Ti64 phonons benchmark.""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +import numpy as np +import pytest + +from ml_peg.analysis.utils.decorators import build_table, cell_to_scatter +from ml_peg.analysis.utils.utils import load_metrics_config +from ml_peg.app import APP_ROOT +from ml_peg.calcs import CALCS_ROOT +from ml_peg.models.get_models import load_models +from ml_peg.models.models import current_models + +THIS_DIR = Path(__file__).resolve().parent + +CALC_OUT_PATH = CALCS_ROOT / "bulk_crystal" / "ti64_phonons" / "outputs" +APP_OUT_PATH = APP_ROOT / "data" / "bulk_crystal" / "ti64_phonons" + +METRICS_YAML_PATH = THIS_DIR / "metrics.yml" +SCATTER_FILENAME = APP_OUT_PATH / "ti64_phonons_interactive.json" + +TP_ON: set[str] = { + "hcp_Ti6AlV", + "hex_Ti8AlV", + "hcp_Ti6Al2", + "hcp_Ti6V2", + "hcp_Ti7V", + "hex_Ti10Al2", + "hex_Ti10V2", +} + +CASES: list[str] = [ + "hcp_Ti6AlV", + "bcc_Ti6AlV", + "hex_Ti8AlV", + "hcp_Ti6Al2", + "hcp_Ti6V2", + "hcp_Ti7V", + "bcc_Ti6Al2", + "bcc_Ti6V2", + "hex_Ti10Al2", + "hex_Ti10V2", +] + + +THRESHOLDS, METRIC_TOOLTIPS, WEIGHTS = load_metrics_config(METRICS_YAML_PATH) + +METRIC_ID_TO_LABEL: dict[str, str] = { + "dispersion_rmse_thz_avg": "Dispersion RMSE (mean)", + "dispersion_rmse_thz_max": "Dispersion RMSE (max)", + "deltaF_0K_eV_per_atom_avg": "ΔF (0 K) mean", + "deltaF_2000K_eV_per_atom_avg": "ΔF (2000 K) mean", + "omega_avg_thz_mae": "ω_avg MAE", +} + +TABLE_METRIC_LABELS: list[str] = list(METRIC_ID_TO_LABEL.values()) +METRIC_LABELS: dict[str, str] = dict(METRIC_ID_TO_LABEL) # id -> label + + +MODELS = load_models(current_models) +MODEL_ITEMS = list(MODELS.items()) +MODEL_IDS: list[str] = [name for name, _ in MODEL_ITEMS] + + +def rmse(a: np.ndarray, b: np.ndarray) -> float: + """ + Compute root mean squared error. + + Parameters + ---------- + a + First array of values. + b + Second array of values. Must be broadcast-compatible with ``a``. + + Returns + ------- + float + Root mean squared error. + """ + a = np.asarray(a, dtype=float) + b = np.asarray(b, dtype=float) + return float(np.sqrt(np.mean((a - b) ** 2))) + + +def resample_dft_to_ml_grid( + dft_x: np.ndarray, dft_freqs: np.ndarray, n_ml: int +) -> np.ndarray: + """ + Resample DFT frequencies onto an inferred ML grid spanning the same path. + + Parameters + ---------- + dft_x + DFT path coordinate array of shape ``(n_dft,)``. + dft_freqs + DFT frequencies array of shape ``(n_dft, n_branches)``. + n_ml + Number of ML q-points. + + Returns + ------- + numpy.ndarray + DFT frequencies interpolated onto the ML grid, shape + ``(n_ml, n_branches)``. + """ + dft_x = np.asarray(dft_x, dtype=float) + dft_freqs = np.asarray(dft_freqs, dtype=float) + + ml_x = np.linspace(dft_x[0], dft_x[-1], n_ml, dtype=float) + + out = np.empty((n_ml, dft_freqs.shape[1]), dtype=float) + for j in range(dft_freqs.shape[1]): + out[:, j] = np.interp(ml_x, dft_x, dft_freqs[:, j]) + return out + + +def write_json(path: Path, obj: dict[str, Any]) -> None: + """ + Write a JSON object to disk. + + Parameters + ---------- + path + Output file path. + obj + JSON-serialisable mapping. + """ + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(obj, indent=2), encoding="utf8") + + +k_b = 8.617333262e-5 # eV/K +hbar = 6.582119569e-16 # eV*s + + +def zp_energy(weights_flat: np.ndarray, freqs_flat_thz: np.ndarray) -> float: + """ + Compute zero-point energy from phonon frequencies. + + Parameters + ---------- + weights_flat + Flattened q-point weights (tiled over branches), shape ``(n_modes,)``. + freqs_flat_thz + Flattened frequencies in THz, shape ``(n_modes,)``. + + Returns + ------- + float + Zero-point energy contribution (eV). + """ + w = np.asarray(weights_flat, dtype=float).reshape(-1) + f_thz = np.asarray(freqs_flat_thz, dtype=float).reshape(-1) + + omega = f_thz * 2.0 * np.pi * 1e12 # rad/s + zpe = w * (hbar * omega) + zpe = zpe[np.isfinite(zpe)] + zpe[zpe == float("-inf")] = 0.0 + zpe[zpe == float("+inf")] = 0.0 + return float(0.5 * np.sum(zpe)) + + +def helmholtz_free_energy_og( + weights_flat: np.ndarray, freqs_flat_thz: np.ndarray, t: float +) -> float: + """ + Compute Helmholtz free energy. + + Parameters + ---------- + weights_flat + Flattened q-point weights (tiled over branches), shape ``(n_modes,)``. + freqs_flat_thz + Flattened frequencies in THz, shape ``(n_modes,)``. + t + Temperature (K). + + Returns + ------- + float + Helmholtz free-energy thermal contribution (eV). + """ + w = np.asarray(weights_flat, dtype=float).reshape(-1) + f_thz = np.asarray(freqs_flat_thz, dtype=float).reshape(-1) + + if t <= 1e-12: + t = 1e-12 + + omega = f_thz * 2.0 * np.pi * 1e12 # rad/s + arg = -(hbar * omega) / (k_b * t) + + integrand = w * np.log(1.0 - np.exp(arg)) + integrand = integrand[np.isfinite(integrand)] + integrand[integrand == float("-inf")] = 0.0 + integrand[integrand == float("+inf")] = 0.0 + return float(np.sum(integrand) * k_b * t) + + +def analyse_one_model(model_id: str) -> None: + """ + Analyse a single Ti64 phonon model and write per-model metrics. + + Parameters + ---------- + model_id + Identifier of the model under + ``ml_peg/calcs/bulk_crystal/ti64_phonons/outputs``. + + Notes + ----- + Writes per-model metrics to: + + - ``ml_peg/app/data/bulk_crystal/ti64_phonons//metrics.json`` + + The JSON file contains aggregated metrics and per-case values. + """ + model_calc_dir = CALC_OUT_PATH / model_id + assert model_calc_dir.exists(), ( + f"No calc outputs found for model '{model_id}'. Expected:\n" + f" {model_calc_dir}\n\n" + "Calc stage writes to:\n" + " ml_peg/calcs/bulk_crystal/ti64_phonons/outputs//...\n" + ) + + model_app_dir = APP_OUT_PATH / model_id + model_app_dir.mkdir(parents=True, exist_ok=True) + + rmse_by_case: dict[str, float] = {} + df0_by_case: dict[str, float] = {} + df2000_by_case: dict[str, float] = {} + + omega_avg_ref_thz_by_case: dict[str, float] = {} + omega_avg_pred_thz_by_case: dict[str, float] = {} + + for case in CASES: + npz_path = model_calc_dir / f"{case}.npz" + assert npz_path.exists(), f"Missing {npz_path}" + + data = np.load(npz_path, allow_pickle=True) + + dft_x = np.asarray(data["dft_x"], dtype=float) + dft_freq = np.asarray(data["dft_frequencies"], dtype=float) + ml_freq = np.asarray(data["ml_frequencies"], dtype=float) + + dft_on_ml = resample_dft_to_ml_grid(dft_x, dft_freq, n_ml=ml_freq.shape[0]) + + omega_avg_ref = float(np.mean(dft_on_ml)) + omega_avg_pred = float(np.mean(ml_freq)) + omega_avg_ref_thz_by_case[case] = omega_avg_ref + omega_avg_pred_thz_by_case[case] = omega_avg_pred + + rmse_by_case[case] = rmse(dft_on_ml, ml_freq) + + if case in TP_ON: + assert "tp_temperatures" in data and "tp_free_energy" in data, ( + f"TP expected for {case} but tp_* arrays missing in {npz_path}" + ) + + required = ["q_weights", "q_frequencies_dft", "n_atoms"] + missing = [k for k in required if k not in data.files] + assert not missing, ( + f"{case}: missing required thermo keys in {npz_path}: {missing}" + ) + + q_w = np.asarray(data["q_weights"], dtype=float) + q_f = np.asarray(data["q_frequencies_dft"], dtype=float) + + weights_tile = np.tile(q_w[:, None], (1, q_f.shape[1])).reshape( + -1, order="F" + ) + freqs_flat = q_f.reshape(-1, order="F") # THz + + n_atoms = int(np.asarray(data["n_atoms"]).item()) + + ml_t = np.asarray(data["tp_temperatures"], dtype=float) + ml_f = np.asarray(data["tp_free_energy"], dtype=float) + + # Legacy unit fix (keep existing behavior) + if np.nanmax(np.abs(ml_f)) > 100.0: + ml_f = ml_f / 96.32 + + t_dense = np.linspace(0.0, 2000.0, 2000, dtype=float) + zpe_const = zp_energy(weights_tile, freqs_flat) + dft_f_dense = np.array( + [ + helmholtz_free_energy_og(weights_tile, freqs_flat, tt) + zpe_const + for tt in t_dense + ], + dtype=float, + ) + dft_f_on_mlt = np.interp(ml_t, t_dense, dft_f_dense) + + df0_by_case[case] = float(np.abs(dft_f_on_mlt[0] - ml_f[0]) / n_atoms) + df2000_by_case[case] = float(np.abs(dft_f_on_mlt[-1] - ml_f[-1]) / n_atoms) + + rmse_vals = np.asarray(list(rmse_by_case.values()), dtype=float) + + omega_avg_mae = ( + float( + np.mean( + [ + abs(omega_avg_pred_thz_by_case[c] - omega_avg_ref_thz_by_case[c]) + for c in omega_avg_ref_thz_by_case + ] + ) + ) + if omega_avg_ref_thz_by_case + else None + ) + + write_json( + model_app_dir / "metrics.json", + { + "model": model_id, + "n_cases": len(CASES), + "metrics": { + "dispersion_rmse_thz_avg": float(np.mean(rmse_vals)), + "dispersion_rmse_thz_max": float(np.max(rmse_vals)), + "deltaF_0K_eV_per_atom_avg": float(np.mean(list(df0_by_case.values()))) + if df0_by_case + else None, + "deltaF_2000K_eV_per_atom_avg": float( + np.mean(list(df2000_by_case.values())) + ) + if df2000_by_case + else None, + "omega_avg_thz_mae": omega_avg_mae, + }, + "by_case": { + "rmse_thz": rmse_by_case, + "deltaF_0K_eV_per_atom": df0_by_case, + "deltaF_2000K_eV_per_atom": df2000_by_case, + "omega_avg_ref_thz": omega_avg_ref_thz_by_case, + "omega_avg_pred_thz": omega_avg_pred_thz_by_case, + }, + }, + ) + + assert len(rmse_by_case) == len(CASES) + + +@pytest.fixture(scope="session") +def run_all_models() -> None: + """ + Generate per-model ``metrics.json`` for all configured models. + + Returns + ------- + None + This fixture exists for its side effects (writing per-model metrics). + """ + for model_id in MODEL_IDS: + analyse_one_model(model_id) + + +@pytest.fixture(scope="session") +@build_table( + filename=APP_OUT_PATH / "ti64_phonons_metrics_table.json", + thresholds=THRESHOLDS, + metric_tooltips=METRIC_TOOLTIPS, + weights=WEIGHTS, +) +def metrics_table(run_all_models: None) -> dict[str, dict[str, float | None]]: + """ + Build the Ti64 metrics table for the Dash app. + + Parameters + ---------- + run_all_models + Session-scoped fixture ensuring per-model metrics are generated. + + Returns + ------- + dict[str, dict[str, float | None]] + Mapping of metric label to per-model values. + """ + _ = run_all_models + + table: dict[str, dict[str, float | None]] = { + label: {} for label in TABLE_METRIC_LABELS + } + + for model_id in MODEL_IDS: + mpath = APP_OUT_PATH / model_id / "metrics.json" + if not mpath.exists(): + continue + + m = json.loads(mpath.read_text(encoding="utf8")) + metrics = m.get("metrics", {}) + + for metric_id, label in METRIC_ID_TO_LABEL.items(): + table[label][model_id] = metrics.get(metric_id) + + return table + + +@pytest.fixture(scope="session") +@cell_to_scatter( + filename=SCATTER_FILENAME, + x_label="Predicted", + y_label="Reference", +) +def interactive_dataset(run_all_models: None) -> dict[str, Any]: + """ + Build the interactive scatter dataset for the Ti64 phonons Dash app. + + Parameters + ---------- + run_all_models + Session-scoped fixture ensuring per-model metrics are generated. + + Returns + ------- + dict[str, Any] + Interactive dataset written to JSON by the decorator. + """ + _ = run_all_models + + dataset: dict[str, Any] = { + "metrics": METRIC_LABELS, # id -> label + "models": {}, + } + + metric_id = "omega_avg_thz_mae" + + for model_id in MODEL_IDS: + metrics_path = APP_OUT_PATH / model_id / "metrics.json" + if not metrics_path.exists(): + continue + + m = json.loads(metrics_path.read_text(encoding="utf8")) + by_case = m.get("by_case") or {} + ref_map = by_case.get("omega_avg_ref_thz", {}) or {} + pred_map = by_case.get("omega_avg_pred_thz", {}) or {} + + points: list[dict[str, Any]] = [] + for case in CASES: + if case not in ref_map or case not in pred_map: + continue + + data_paths = { + "npz": str( + (CALC_OUT_PATH / model_id / f"{case}.npz").relative_to( + CALC_OUT_PATH.parent + ) + ), + "meta": str( + (CALC_OUT_PATH / model_id / f"{case}.json").relative_to( + CALC_OUT_PATH.parent + ) + ), + } + + points.append( + { + "id": case, + "label": case, + "ref": ref_map[case], + "pred": pred_map[case], + "data_paths": data_paths, + } + ) + + dataset["models"][model_id] = { + "model": model_id, + "metrics": { + metric_id: { + "points": points, + "mae": (m.get("metrics") or {}).get(metric_id), + } + }, + } + + return dataset + + +def test_all_models_metrics_written(run_all_models: None) -> None: + """ + Check per-model ``metrics.json`` exists for every configured model. + + Parameters + ---------- + run_all_models + Session-scoped fixture ensuring per-model metrics are generated. + """ + _ = run_all_models + + missing: list[str] = [] + for model_id in MODEL_IDS: + if not (APP_OUT_PATH / model_id / "metrics.json").exists(): + missing.append(model_id) + + assert not missing, f"Missing metrics.json for models: {missing}" + + +def test_write_metrics_table(metrics_table: dict[str, Any]) -> None: + """ + Check the table JSON artifact is produced and includes all models. + + Parameters + ---------- + metrics_table + Fixture providing the metrics table mapping (and/or triggering JSON writing). + """ + assert isinstance(metrics_table, dict) + + table_path = APP_OUT_PATH / "ti64_phonons_metrics_table.json" + assert table_path.exists() + + payload = json.loads(table_path.read_text(encoding="utf8")) + rows = payload.get("data", []) + ids = {row.get("id") for row in rows if isinstance(row, dict)} + missing = [m for m in MODEL_IDS if m not in ids] + assert not missing, f"Table missing model rows: {missing}" + + +def test_write_interactive_json(interactive_dataset: dict[str, Any]) -> None: + """ + Check the interactive JSON artifact is produced and includes all models. + + Parameters + ---------- + interactive_dataset + Fixture providing the interactive dataset (and/or triggering JSON writing). + """ + assert isinstance(interactive_dataset, dict) + assert SCATTER_FILENAME.exists() + + payload = json.loads(SCATTER_FILENAME.read_text(encoding="utf8")) + models = payload.get("models", {}) + missing = [m for m in MODEL_IDS if m not in models] + assert not missing, f"Interactive dataset missing models: {missing}" diff --git a/ml_peg/analysis/bulk_crystal/ti64_phonons/metrics.yml b/ml_peg/analysis/bulk_crystal/ti64_phonons/metrics.yml new file mode 100644 index 000000000..a3bbc6681 --- /dev/null +++ b/ml_peg/analysis/bulk_crystal/ti64_phonons/metrics.yml @@ -0,0 +1,40 @@ +metrics: + Dispersion RMSE (mean): + good: 0.0 + bad: 2.0 + unit: THz + tooltip: "Average RMSE between ML and DFT phonon dispersions across the 10 Ti64 cases (after resampling onto a common k-grid)." + weight: 1.0 + level_of_theory: PBE + + Dispersion RMSE (max): + good: 0.0 + bad: 4.0 + unit: THz + tooltip: "Worst-case RMSE between ML and DFT phonon dispersions among the 10 Ti64 cases." + weight: 0.5 + level_of_theory: PBE + + ΔF (0 K) mean: + good: 0.0 + bad: 0.007 + unit: eV/atom + tooltip: "Mean |ΔF| (free energy) at 0 K per atom between ML and DFT." + weight: 1.0 + level_of_theory: PBE + + ΔF (2000 K) mean: + good: 0.0 + bad: 0.03 + unit: eV/atom + tooltip: "Mean |ΔF| (free energy) at 2000 K per atom between ML and DFT." + weight: 1.0 + level_of_theory: PBE + + ω_avg MAE: + good: 0.0 + bad: 1.0 + unit: THz + tooltip: "MAE of ω_avg per case, where ω_avg is the mean of all phonon band frequencies for that case, averaged over the 10 Ti64 cases." + weight: 0.5 + level_of_theory: PBE diff --git a/ml_peg/app/bulk_crystal/diamond_phonons/app_diamond_phonons.py b/ml_peg/app/bulk_crystal/diamond_phonons/app_diamond_phonons.py index 7a534bc58..0f9f205a4 100644 --- a/ml_peg/app/bulk_crystal/diamond_phonons/app_diamond_phonons.py +++ b/ml_peg/app/bulk_crystal/diamond_phonons/app_diamond_phonons.py @@ -21,20 +21,34 @@ ) from ml_peg.app.utils.plot_helpers import build_serialized_scatter_content from ml_peg.calcs import CALCS_ROOT +from ml_peg.calcs.utils.utils import download_github_data -DATA_PATH = APP_ROOT / "data" / "bulk_crystal" / "diamond_phonons" +GITHUB_BASE = "https://raw.githubusercontent.com/7radians/ml-peg-data/main" + +EXTRACTED_ROOT = Path( + download_github_data( + filename="diamond_data/data.zip", + github_uri=GITHUB_BASE, + ) +) + +DATA_PATH = EXTRACTED_ROOT / "data" +DFT_REF_PATH = DATA_PATH / "dft_band.npz" + + +BENCHMARK_NAME = "diamond_phonons" + +DATA_PATH = APP_ROOT / "data" / "bulk_crystal" / BENCHMARK_NAME TABLE_PATH = DATA_PATH / "diamond_phonons_bands_table.json" SCATTER_PATH = DATA_PATH / "diamond_phonons_bands_interactive.json" -BENCHMARK_NAME = "diamond_phonons" DOCS_URL = ( "https://ddmms.github.io/ml-peg/user_guide/benchmarks/bulk_crystal.html" - "#diamond_phonons" + f"#{BENCHMARK_NAME}" ) -# IMPORTANT: must match how `data_paths` are built in analysis -CALC_BASE = CALCS_ROOT / "bulk_crystal" / "diamond_phonons" -DFT_REF_PATH = Path("data") / "dft_band.npz" +CALC_BASE = CALCS_ROOT / "bulk_crystal" / BENCHMARK_NAME +# DFT_REF_PATH = Path("data") / "dft_band.npz" PLOT_CONTAINER_ID = f"{BENCHMARK_NAME}-plot-container" DISPERSION_CONTAINER_ID = f"{BENCHMARK_NAME}-dispersion-container" @@ -79,7 +93,7 @@ def model_only_lookup( } -class PhononApp(BaseApp): +class DiamondPhononApp(BaseApp): """Bands-only phonon benchmark app wiring callbacks and layout.""" def register_callbacks(self) -> None: @@ -92,7 +106,9 @@ def register_callbacks(self) -> None: metric_labels = interactive_data.get("metrics", {}) label_to_key = {label: key for key, label in metric_labels.items()} - refresh_msg = "Click on a metric to view scatter plots." + refresh_msg = ( + "Click on a metric to view DFT vs predicted frequency scatter plots." + ) metric_handler = partial( build_serialized_scatter_content, @@ -117,7 +133,7 @@ def register_callbacks(self) -> None: render_dispersion_component, calc_root=calc_root, frequency_scale=1, - frequency_unit="cm⁻¹", + frequency_unit="THz", reference_label="RSCAN", reference_band_npz=DFT_REF_PATH, ) @@ -133,7 +149,7 @@ def register_callbacks(self) -> None: ) -def get_app() -> PhononApp: +def get_app() -> DiamondPhononApp: """ Construct the diamond phonon PhononApp instance. @@ -142,7 +158,7 @@ def get_app() -> PhononApp: PhononApp Configured application with table + scatter/dispersion panels. """ - return PhononApp( + return DiamondPhononApp( name=BENCHMARK_NAME, description=( "Accuracy of MLIPs in predicting phonon dispersions for Carbon diamond " @@ -156,7 +172,8 @@ def get_app() -> PhononApp: html.Div( [ html.Div( - "Click on a metric to view scatter plots.", + "Click on a metric to view DFT vs predicted frequency scatter " + "plots.", id=PLOT_CONTAINER_ID, style={"flex": "1", "minWidth": 0}, ), @@ -179,7 +196,7 @@ def get_app() -> PhononApp: if __name__ == "__main__": full_app = Dash(__name__, assets_folder=DATA_PATH.parent.parent) - phonon_app = get_app() - full_app.layout = phonon_app.layout - phonon_app.register_callbacks() + diamond_phonon_app = get_app() + full_app.layout = diamond_phonon_app.layout + diamond_phonon_app.register_callbacks() full_app.run(port=8060, debug=True) diff --git a/ml_peg/app/bulk_crystal/diamond_phonons/diamond_interactive_helpers.py b/ml_peg/app/bulk_crystal/diamond_phonons/diamond_interactive_helpers.py index d7553c921..729fbf673 100644 --- a/ml_peg/app/bulk_crystal/diamond_phonons/diamond_interactive_helpers.py +++ b/ml_peg/app/bulk_crystal/diamond_phonons/diamond_interactive_helpers.py @@ -1,9 +1,8 @@ """ Utilities for diamond phonon interactive assets (bands-only). -This module only supports rendering a dispersion preview from: - -- Predicted phonopy band.yaml (THz) +Supports rendering a dispersion preview from: +- Predicted Phonopy band.yaml (THz) - Reference dft_band.npz with keys: distance, qpoints, frequencies (cm^-1) """ @@ -35,30 +34,27 @@ def render_dispersion_component( reference_band_npz: Path | None = None, ): """ - Render a Matplotlib dispersion PNG (prediction + reference overlay). + Return a Dash component containing a dispersion PNG, or None. Parameters ---------- - selection_context - Mapping containing the current selection. Must contain a ``selection`` entry - with a ``band_yaml`` path. - calc_root - Root directory for calculation outputs. - frequency_scale - Multiplicative scale applied to both predicted and reference frequencies. - frequency_unit - Unit label shown on the y-axis. - reference_label - Label used for the reference curve in the legend. - reference_band_npz - Optional path to the reference ``dft_band.npz`` file, relative to - ``calc_root``. + selection_context : Mapping[str, Any] + Selection context; expects selection["band_yaml"] and optional label/id. + calc_root : Path + Root directory used to resolve files. + frequency_scale : float + Multiplicative scale applied to frequencies. + frequency_unit : str + Y-axis unit label. + reference_label : str + Legend label for the reference overlay. + reference_band_npz : Path | None + Optional reference .npz (distance, qpoints, frequencies in cm^-1). Returns ------- - dash.html.Div | None - A Dash component containing the rendered PNG, or ``None`` if no band file - is selected or rendering fails. + html.Div | None + Component with an embedded PNG (data URI), or None if unavailable. """ model_display = selection_context.get("model") selected = selection_context.get("selection") or {} @@ -73,7 +69,6 @@ def render_dispersion_component( calc_root=calc_root, band_yaml=str(band_yaml), reference_band_npz=reference_band_npz, - system_label=str(label), frequency_scale=float(frequency_scale), frequency_unit=str(frequency_unit), reference_label=str(reference_label), @@ -87,10 +82,17 @@ def render_dispersion_component( return html.Div( [ - html.H4(label), + html.H4(str(label)) if label else None, html.Img( src=image_src, - style={"maxWidth": "100%", "border": "1px solid #ccc"}, + style={ + "width": "100%", + "maxWidth": "820px", + "height": "auto", + "display": "block", + "borderRadius": "8px", + "border": "1px solid #ddd", + }, ), ] ) @@ -101,55 +103,52 @@ def render_band_yaml_png( calc_root: Path, band_yaml: str, reference_band_npz: Path | None, - system_label: str, frequency_scale: float, frequency_unit: str, reference_label: str, prediction_label: str, ) -> str | None: """ - Render dispersion by parsing a phonopy band.yaml and overlaying a DFT reference. + Render a dispersion PNG from band.yaml with optional reference overlay. Parameters ---------- - calc_root - Root directory for calculation outputs. - band_yaml - Path to the predicted phonopy ``band.yaml`` relative to ``calc_root``. - reference_band_npz - Optional path to the reference ``dft_band.npz`` relative to ``calc_root``. - system_label - Label identifying the system being plotted. - frequency_scale - Multiplicative scale applied to both predicted and reference frequencies. - frequency_unit - Unit label shown on the y-axis. - reference_label - Legend label for the reference curves. - prediction_label - Legend label for the prediction curves. + calc_root : Path + Root directory used to resolve files. + band_yaml : str + Predicted Phonopy band.yaml path (relative to calc_root). + reference_band_npz : Path | None + Optional reference .npz (distance, qpoints, frequencies in cm^-1). + frequency_scale : float + Multiplicative scale applied to frequencies. + frequency_unit : str + Y-axis unit label. + reference_label : str + Legend label for the reference overlay. + prediction_label : str + Legend label for the prediction. Returns ------- str | None - A base64-encoded PNG image (``data:image/png;base64,...``), or ``None`` if - the input data could not be loaded. + Base64 PNG data URI, or None on failure. """ import yaml # type: ignore def _detect_symmetry_boundaries(q_ref: np.ndarray) -> list[int]: """ - Detect symmetry-point boundaries along the reference q-point path. + Return indices of k-path corners (including first and last). Parameters ---------- - q_ref - Reference q-point coordinates of shape ``(Nq, 3)``. + q_ref : np.ndarray + Array of q-points with shape (N, 3) along the band path. Returns ------- list[int] - Indices of q-points corresponding to symmetry boundaries. + Sorted indices of symmetry boundaries (path corners), including + indices 0 and N-1. """ dq = np.diff(q_ref, axis=0) dq_norm = np.linalg.norm(dq, axis=1) @@ -160,10 +159,7 @@ def _detect_symmetry_boundaries(q_ref: np.ndarray) -> list[int]: boundaries = [0] for i in cand: - if i + 1 < len(q_ref) and np.allclose(q_ref[i], q_ref[i + 1], atol=1e-10): - boundaries.append(int(i)) - else: - boundaries.append(int(i)) + boundaries.append(int(i)) boundaries = sorted(set(boundaries)) if boundaries[0] != 0: @@ -186,80 +182,105 @@ def _detect_symmetry_boundaries(q_ref: np.ndarray) -> list[int]: if not isinstance(phonon, list) or not phonon: return None - s_pred = np.asarray([p.get("distance", np.nan) for p in phonon], float) + s_pred = np.asarray([p.get("distance", np.nan) for p in phonon], dtype=float) freqs_pred_thz = np.asarray( [[b.get("frequency", np.nan) for b in p.get("band", [])] for p in phonon], - float, + dtype=float, ) if freqs_pred_thz.ndim != 2 or not np.isfinite(freqs_pred_thz).all(): return None - freqs_pred_cm1 = freqs_pred_thz * THZ_TO_CM1 * frequency_scale + freqs_pred_thz = freqs_pred_thz * float(frequency_scale) - s_ref = None - q_ref = None - freqs_ref_cm1 = None - sym_pos = None + s_ref: np.ndarray | None = None + q_ref: np.ndarray | None = None + freqs_ref_thz: np.ndarray | None = None + sym_pos: np.ndarray | None = None if reference_band_npz is not None: ref_path = Path(calc_root) / reference_band_npz if ref_path.exists(): obj = np.load(ref_path, allow_pickle=False) - s_ref = np.asarray(obj["distance"], float) - q_ref = np.asarray(obj["qpoints"], float) - freqs_ref_cm1 = np.asarray(obj["frequencies"], float) + s_ref = np.asarray(obj["distance"], dtype=float) + q_ref = np.asarray(obj["qpoints"], dtype=float) + freqs_ref_cm1 = np.asarray(obj["frequencies"], dtype=float) if freqs_ref_cm1.ndim == 3 and freqs_ref_cm1.shape[0] == 1: freqs_ref_cm1 = freqs_ref_cm1[0] - if freqs_ref_cm1.ndim != 2: - s_ref = q_ref = freqs_ref_cm1 = None - else: - freqs_ref_cm1 = freqs_ref_cm1 * frequency_scale + if freqs_ref_cm1.ndim == 2 and np.isfinite(freqs_ref_cm1).all(): + freqs_ref_thz = (freqs_ref_cm1 / THZ_TO_CM1) * float(frequency_scale) bounds = _detect_symmetry_boundaries(q_ref) sym_pos = s_ref[bounds] + else: + s_ref = None + q_ref = None + freqs_ref_thz = None + sym_pos = None use_ref_x = s_ref is not None and len(s_ref) == len(s_pred) x = s_ref if use_ref_x else s_pred - fig, ax = plt.subplots(figsize=(8, 5)) + fig, ax = plt.subplots(figsize=(10, 7)) - for band in freqs_pred_cm1.T: - ax.plot(x, band, "--", lw=1.0, color="red") + for j in range(freqs_pred_thz.shape[1]): + ax.plot( + x, + freqs_pred_thz[:, j], + color="#1f77b4", + lw=2.0, + label=prediction_label if j == 0 else None, + ) - if freqs_ref_cm1 is not None: - for band in freqs_ref_cm1.T: - ax.plot(x if use_ref_x else s_ref, band, "-", lw=1.0, color="blue") + if freqs_ref_thz is not None and s_ref is not None: + x_ref = x if use_ref_x else s_ref + for j in range(freqs_ref_thz.shape[1]): + ax.plot( + x_ref, + freqs_ref_thz[:, j], + color="k", + lw=2.0, + ls="--", + label=reference_label if j == 0 else None, + ) if sym_pos is not None and len(sym_pos) == len(HS_LABELS): for xpos in sym_pos: - ax.axvline(float(xpos), color="k", lw=0.8) + ax.axvline(float(xpos), color="k", lw=1.0, alpha=0.6) ax.set_xticks(sym_pos) ax.set_xticklabels(HS_LABELS) ax.set_xlim(float(x[0]), float(x[-1])) - ax.set_xlabel("Wave vector") - ax.set_ylabel(rf"Frequency ({frequency_unit})") - ax.axhline(0.0, color="k", lw=0.8) - ax.grid(True, linestyle=":", linewidth=0.5) - ax.set_ylim(bottom=0.0) + ax.set_xlabel("Wave vector", fontsize=18) + ax.set_ylabel(f"Frequency ({frequency_unit})", fontsize=18) + ax.axhline(0.0, color="k", lw=1.5) + ax.grid(axis="x") + ax.set_ylim( + float( + min( + np.nanmin(freqs_pred_thz), + np.nanmin(freqs_ref_thz) if freqs_ref_thz is not None else np.inf, + ) + ), + float( + max( + np.nanmax(freqs_pred_thz), + np.nanmax(freqs_ref_thz) if freqs_ref_thz is not None else -np.inf, + ) + ), + ) - from matplotlib.lines import Line2D + handles, labels = ax.get_legend_handles_labels() + by_label = dict(zip(labels, handles, strict=False)) + if by_label: + ax.legend(by_label.values(), by_label.keys(), loc=1, fontsize=14) - handles = [] - if freqs_ref_cm1 is not None: - handles.append( - Line2D([0], [0], color="blue", lw=1.0, ls="-", label=reference_label) - ) - handles.append( - Line2D([0], [0], color="red", lw=1.0, ls="--", label=prediction_label) - ) - ax.legend(handles=handles, frameon=False) + fig.tight_layout() buffer = BytesIO() - fig.savefig(buffer, format="png", dpi=150, bbox_inches="tight") + fig.savefig(buffer, format="png", dpi=200) plt.close(fig) encoded = base64.b64encode(buffer.getvalue()).decode("ascii") diff --git a/ml_peg/app/bulk_crystal/ti64_phonons/app_ti64_phonons.py b/ml_peg/app/bulk_crystal/ti64_phonons/app_ti64_phonons.py new file mode 100644 index 000000000..93ad5bd08 --- /dev/null +++ b/ml_peg/app/bulk_crystal/ti64_phonons/app_ti64_phonons.py @@ -0,0 +1,170 @@ +"""Run Ti64 phonon dispersion + DOS + TP app.""" + +from __future__ import annotations + +from functools import partial +import json + +from dash import Dash, dcc, html + +from ml_peg.app import APP_ROOT +from ml_peg.app.base_app import BaseApp +from ml_peg.app.bulk_crystal.ti64_phonons.ti64_interactive_helpers import ( + lookup_system_entry, + render_dispersion_component, +) +from ml_peg.app.utils.build_callbacks import ( + model_asset_from_scatter, + scatter_and_assets_from_table, +) +from ml_peg.app.utils.plot_helpers import ( + build_serialized_scatter_content, + resolve_scatter_selection, +) + +BENCHMARK_NAME = "ti64_phonons" + +DATA_PATH = APP_ROOT / "data" / "bulk_crystal" / BENCHMARK_NAME +TABLE_PATH = DATA_PATH / "ti64_phonons_metrics_table.json" +SCATTER_PATH = DATA_PATH / "ti64_phonons_interactive.json" + +CALC_ROOT = APP_ROOT.parent / "calcs" / "bulk_crystal" / BENCHMARK_NAME + +DOCS_URL = ( + "https://ddmms.github.io/ml-peg/user_guide/benchmarks/bulk_crystal.html#phonons" +) + +PLOT_CONTAINER_ID = f"{BENCHMARK_NAME}-plot-container" +DISPERSION_CONTAINER_ID = f"{BENCHMARK_NAME}-dispersion-container" +LAST_CELL_STORE_ID = f"{BENCHMARK_NAME}-last-cell" +SCATTER_METADATA_STORE_ID = f"{BENCHMARK_NAME}-scatter-meta" +SCATTER_GRAPH_ID = f"{BENCHMARK_NAME}-scatter" + + +class Ti64PhononsApp(BaseApp): + """Ti64 phonons benchmark app wiring callbacks and layout.""" + + def register_callbacks(self) -> None: + """Register scatter/dispersion callbacks via shared helpers.""" + with SCATTER_PATH.open(encoding="utf8") as handle: + interactive_data = json.load(handle) + + models_data = interactive_data.get("models", {}) + + metric_labels = interactive_data.get("metrics", {}) # {metric_id: label} + label_to_key = {label: key for key, label in metric_labels.items()} + + omega_label = metric_labels.get("omega_avg_thz_mae", "ω_avg MAE") + + metric_handler = partial( + build_serialized_scatter_content, + models_data=models_data, + label_map=label_to_key, + scatter_id=SCATTER_GRAPH_ID, + instructions="Click any cell to view ω_avg (ref vs pred) scatter.", + ) + + def omega_only_handler(model_display: str, column_id: str): + """ + Render the ω_avg scatter for the selected model. + + Parameters + ---------- + model_display + Display name of the selected model row. + column_id + Column identifier from the table callback. + + Returns + ------- + Any + Dash component(s) produced by the scatter renderer. + """ + _ = column_id + return metric_handler(model_display, omega_label) + + column_handlers = dict.fromkeys(label_to_key.keys(), omega_only_handler) + + scatter_and_assets_from_table( + table_id=self.table_id, + table_data=self.table.data, + plot_container_id=PLOT_CONTAINER_ID, + scatter_metadata_store_id=SCATTER_METADATA_STORE_ID, + last_cell_store_id=LAST_CELL_STORE_ID, + column_handlers=column_handlers, + default_handler=omega_only_handler, + ) + + selection_lookup = partial( + resolve_scatter_selection, + models_data=models_data, + system_lookup=partial( + lookup_system_entry, + data_root=DATA_PATH, # kept for API compatibility; unused by new helper + assets_prefix=f"bulk_crystal/{BENCHMARK_NAME}", # unused by new helper + ), + ) + + dispersion_renderer = partial(render_dispersion_component, calc_root=CALC_ROOT) + + model_asset_from_scatter( + scatter_id=SCATTER_GRAPH_ID, + metadata_store_id=SCATTER_METADATA_STORE_ID, + asset_container_id=DISPERSION_CONTAINER_ID, + data_lookup=selection_lookup, + asset_renderer=dispersion_renderer, + empty_message="Click on a data point to preview the dispersion + DOS.", + missing_message="No dispersion plot available for this point.", + ) + + +def get_app() -> Ti64PhononsApp: + """ + Construct the Ti64PhononsApp instance. + + Returns + ------- + Ti64PhononsApp + Configured application with table + scatter/dispersion panels. + """ + return Ti64PhononsApp( + name=BENCHMARK_NAME, + description=( + "Accuracy of MLIPs in predicting phonon dispersions and vibrational " + "thermodynamics for Ti64 alloy." + ), + docs_url=DOCS_URL, + table_path=TABLE_PATH, + extra_components=[ + dcc.Store(id=LAST_CELL_STORE_ID), + dcc.Store(id=SCATTER_METADATA_STORE_ID), + html.Div( + [ + html.Div( + "Click any cell to view ω_avg (ref vs pred) scatter.", + id=PLOT_CONTAINER_ID, + style={"flex": "1", "minWidth": 0}, + ), + html.Div( + "Click on a data point to preview the dispersion + DOS.", + id=DISPERSION_CONTAINER_ID, + style={"flex": "1", "minWidth": 0}, + ), + ], + style={ + "display": "flex", + "gap": "24px", + "alignItems": "stretch", + "flexWrap": "wrap", + }, + ), + ], + ) + + +if __name__ == "__main__": + full_app = Dash(__name__, assets_folder=DATA_PATH.parent.parent) + ti64_app = get_app() + full_app.layout = ti64_app.layout + ti64_app.register_callbacks() + full_app.run(port=8060, debug=True) diff --git a/ml_peg/app/bulk_crystal/ti64_phonons/ti64_interactive_helpers.py b/ml_peg/app/bulk_crystal/ti64_phonons/ti64_interactive_helpers.py new file mode 100644 index 000000000..ef2fecaf2 --- /dev/null +++ b/ml_peg/app/bulk_crystal/ti64_phonons/ti64_interactive_helpers.py @@ -0,0 +1,510 @@ +"""Helpers for Ti64 phonon interactive dispersion/DOS rendering.""" + +from __future__ import annotations + +import base64 +from collections.abc import Mapping +from functools import lru_cache +from io import BytesIO +import json +from pathlib import Path +from typing import Any + +import matplotlib +import numpy as np + +matplotlib.use("Agg") +from dash import html +import matplotlib.pyplot as plt + + +def _load_json(path: Path) -> Any: + """ + Load a JSON file from disk. + + Parameters + ---------- + path + Path to a JSON file. + + Returns + ------- + Any + Parsed JSON payload. + """ + with path.open("r", encoding="utf8") as f: + return json.load(f) + + +def lookup_system_entry( + model_entry: Mapping[str, Any], + point_id: str | int, + *, + data_root: Path, + assets_prefix: str, +) -> dict[str, Any] | None: + """ + Resolve a scatter (model, point) selection into a data-path payload. + + Parameters + ---------- + model_entry + Model entry from the interactive dataset (contains ``model`` and ``metrics``). + point_id + Point identifier selected in the scatter. + data_root + Root directory for app data (kept for API compatibility; unused here). + assets_prefix + Assets prefix for app data (kept for API compatibility; unused here). + + Returns + ------- + dict[str, Any] or None + Selection dictionary containing ``model``, ``system``, and ``data_paths`` if the + point is found; otherwise ``None``. + """ + _ = data_root + _ = assets_prefix + + model_name = ( + model_entry.get("model") or model_entry.get("id") or model_entry.get("name") + ) + if not isinstance(model_name, str) or not model_name.strip(): + return None + + system_name = str(point_id) + + metrics = model_entry.get("metrics", {}) + if not isinstance(metrics, dict): + return None + + # Search all metrics for a matching point id + for _metric_id, metric_payload in metrics.items(): + if not isinstance(metric_payload, dict): + continue + points = metric_payload.get("points", []) + if not isinstance(points, list): + continue + for p in points: + if not isinstance(p, dict): + continue + if str(p.get("id")) != system_name: + continue + + data_paths = p.get("data_paths") + if not isinstance(data_paths, dict): + return None + + # must include at least the calc npz path + npz_rel = data_paths.get("npz") + if not isinstance(npz_rel, str) or not npz_rel: + return None + + meta_rel = data_paths.get("meta") + + return { + "model": model_name, + "system": system_name, + "data_paths": { + "npz": npz_rel, + "meta": meta_rel, + }, + "label": p.get("label"), + "ref": p.get("ref"), + "pred": p.get("pred"), + } + + return None + + +def resample_dft_to_ml_grid( + dft_x: np.ndarray, dft_freqs: np.ndarray, n_ml: int +) -> np.ndarray: + """ + Resample DFT frequencies onto a uniform ML grid spanning the same path. + + Parameters + ---------- + dft_x + DFT path coordinate array of shape ``(n_dft,)``. + dft_freqs + DFT frequencies array of shape ``(n_dft, n_branches)``. + n_ml + Number of ML q-points. + + Returns + ------- + numpy.ndarray + DFT frequencies interpolated onto the ML grid, shape + ``(n_ml, n_branches)``. + """ + dft_x = np.asarray(dft_x, dtype=float).reshape(-1) + dft_freqs = np.asarray(dft_freqs, dtype=float) + + ml_x = np.linspace(dft_x[0], dft_x[-1], int(n_ml), dtype=float) + + out = np.empty((ml_x.size, dft_freqs.shape[1]), dtype=float) + for j in range(dft_freqs.shape[1]): + out[:, j] = np.interp(ml_x, dft_x, dft_freqs[:, j]) + return out + + +def gaussian_broadened_dos( + freqs_flat: np.ndarray, + weights_flat: np.ndarray, + grid: np.ndarray, + sigma: float, +) -> np.ndarray: + """ + Compute a Gaussian-broadened DOS on a target frequency grid. + + Parameters + ---------- + freqs_flat + Flattened frequencies (e.g. THz), shape ``(n_modes,)``. + weights_flat + Flattened weights matching ``freqs_flat``, shape ``(n_modes,)``. + grid + Frequency grid to evaluate the DOS on. + sigma + Gaussian broadening (same units as ``grid``). + + Returns + ------- + numpy.ndarray + DOS values evaluated on ``grid``. + """ + f = np.asarray(freqs_flat, dtype=float).reshape(-1) + w = np.asarray(weights_flat, dtype=float).reshape(-1) + x = np.asarray(grid, dtype=float).reshape(-1) + + diff = f[:, None] - x[None, :] + return np.sum(w[:, None] * np.exp(-0.5 * (diff / sigma) ** 2), axis=0) + + +def gaussian_smooth_on_grid(x: np.ndarray, y: np.ndarray, sigma: float) -> np.ndarray: + """ + Smooth 1D data on a uniform grid using a Gaussian kernel. + + Parameters + ---------- + x + Grid values. + y + Values defined on ``x``. + sigma + Gaussian kernel standard deviation in the same units as ``x``. + + Returns + ------- + numpy.ndarray + Smoothed values on the same grid. + """ + x = np.asarray(x, dtype=float).reshape(-1) + y = np.asarray(y, dtype=float).reshape(-1) + + if x.size < 3: + return y + + dx = float(np.median(np.diff(x))) + if dx <= 0: + return y + + sigma_pts = sigma / dx + half = int(max(3, np.ceil(4.0 * sigma_pts))) + t = np.arange(-half, half + 1, dtype=float) + + k = np.exp(-0.5 * (t / sigma_pts) ** 2) + k /= np.sum(k) + + return np.convolve(y, k, mode="same") + + +@lru_cache(maxsize=512) +def _render_npz_to_data_uri( + npz_path_str: str, meta_path_str: str | None +) -> tuple[str | None, dict[str, Any]]: + """ + Render a dispersion/DOS PNG from a calc-stage NPZ and optional meta JSON. + + Parameters + ---------- + npz_path_str + Path to the calc-stage NPZ file (string for cache key stability). + meta_path_str + Optional path to a metadata JSON file (string for cache key stability). + + Returns + ------- + tuple[str | None, dict[str, Any]] + ``(data_uri, extras)`` where ``data_uri`` is a PNG data URI or ``None`` if the + NPZ file is missing. ``extras`` contains values used to build the caption. + """ + npz_path = Path(npz_path_str) + if not npz_path.exists(): + return None, {} + + data = np.load(npz_path, allow_pickle=True) + + labels = None + if meta_path_str: + meta_path = Path(meta_path_str) + if meta_path.exists(): + try: + meta = _load_json(meta_path) + labels = meta.get("labels", None) if isinstance(meta, dict) else None + if not isinstance(labels, list): + labels = None + except Exception: + labels = None + + # Dispersion + dft_x = np.asarray(data["dft_x"], dtype=float) + dft_freq = np.asarray(data["dft_frequencies"], dtype=float) + ml_freq = np.asarray(data["ml_frequencies"], dtype=float) + + dft_on_ml = resample_dft_to_ml_grid(dft_x, dft_freq, n_ml=ml_freq.shape[0]) + + omega_avg_ref = float(np.mean(dft_on_ml)) + omega_avg_pred = float(np.mean(ml_freq)) + + y_lo = float(min(np.min(dft_on_ml), np.min(ml_freq))) + y_hi = float(max(np.max(dft_on_ml), np.max(ml_freq))) + + # DOS + required = [ + "pdos_frequency_points", + "pdos_projected", + "q_weights", + "q_frequencies_dft", + ] + has_dos = all(k in data.files for k in required) + + dos_grid = None + dft_dos_plot = None + ml_dos_plot = None + + if has_dos: + fgrid = np.asarray(data["pdos_frequency_points"], dtype=float).reshape(-1) + pdos_proj = np.asarray(data["pdos_projected"], dtype=float) + + if pdos_proj.ndim == 2 and pdos_proj.shape[0] == fgrid.size: + ml_dos = np.mean(pdos_proj, axis=1) + else: + ml_dos = np.mean(pdos_proj, axis=0) + ml_dos = np.asarray(ml_dos, dtype=float).reshape(-1) + + area_ml = float(np.trapz(ml_dos, fgrid)) + ml_dos_n = ml_dos / area_ml if area_ml > 0 else ml_dos + + q_w = np.asarray(data["q_weights"], dtype=float) + q_f = np.asarray(data["q_frequencies_dft"], dtype=float) + + weights_tile = np.tile(q_w[:, None], (1, q_f.shape[1])).reshape(-1, order="F") + freqs_flat = q_f.reshape(-1, order="F") # THz + + dft_dos = gaussian_broadened_dos(freqs_flat, weights_tile, fgrid, sigma=0.05) + area_dft = float(np.trapz(dft_dos, fgrid)) + dft_dos_n = dft_dos / area_dft if area_dft > 0 else dft_dos + + order = np.argsort(fgrid) + fgrid = fgrid[order] + ml_dos_n = ml_dos_n[order] + dft_dos_n = dft_dos_n[order] + + ml_dos_n = gaussian_smooth_on_grid(fgrid, ml_dos_n, sigma=0.05) + dft_dos_n = gaussian_smooth_on_grid(fgrid, dft_dos_n, sigma=0.05) + + df = float(np.median(np.diff(fgrid))) if fgrid.size > 2 else 0.01 + dos_grid = np.arange(y_lo, y_hi + df, df) + + ml_dos_plot = np.interp(dos_grid, fgrid, ml_dos_n, left=0.0, right=0.0) + + dft_dos_plot = gaussian_broadened_dos( + freqs_flat, + weights_tile, + dos_grid, + sigma=0.05, + ) + area_dft_plot = float(np.trapz(dft_dos_plot, dos_grid)) + dft_dos_plot = ( + dft_dos_plot / area_dft_plot if area_dft_plot > 0 else dft_dos_plot + ) + + ml_dos_plot = gaussian_smooth_on_grid(dos_grid, ml_dos_plot, sigma=0.05) + dft_dos_plot = gaussian_smooth_on_grid(dos_grid, dft_dos_plot, sigma=0.05) + + fig, (a0, a1) = plt.subplots( + 1, + 2, + gridspec_kw={"width_ratios": [4, 1], "wspace": 0.05}, + figsize=(10, 8), + ) + + n_br = ml_freq.shape[1] if ml_freq.ndim == 2 else 1 + x = np.arange(ml_freq.shape[0], dtype=float) + + ml_color = "C0" + dft_color = "k" + + if n_br == 1 and ml_freq.ndim == 1: + a0.plot(x, ml_freq, lw=2.0, color=ml_color, label="ML") + a0.plot(x, dft_on_ml, lw=2.0, ls="--", color=dft_color, label="DFT") + else: + for j in range(n_br): + a0.plot( + x, + ml_freq[:, j], + lw=2.0, + color=ml_color, + label="ML" if j == 0 else None, + ) + a0.plot( + x, + dft_on_ml[:, j], + lw=2.0, + ls="--", + color=dft_color, + label="DFT" if j == 0 else None, + ) + + if "ml_normal_ticks" in data.files and labels is not None: + ticks = np.asarray(data["ml_normal_ticks"], dtype=float) + if np.max(ticks) <= ml_freq.shape[0] - 1 + 1e-6: + xt = ticks + else: + xt = ( + (ticks - ticks.min()) + / (ticks.max() - ticks.min() + 1e-12) + * (ml_freq.shape[0] - 1) + ) + a0.set_xticks(xt) + a0.set_xticklabels(labels) + a0.set_xlim(float(xt[0]), float(xt[-1])) + + a0.set_ylabel("Frequency (THz)", fontsize=20) + a0.axhline(0.0, color="k", lw=1.5) + a0.grid(axis="x") + a0.set_ylim(y_lo, y_hi) + + handles, lbls = a0.get_legend_handles_labels() + by_label = dict(zip(lbls, handles, strict=False)) + if by_label: + a0.legend(by_label.values(), by_label.keys(), loc=1, fontsize=18) + + if ( + has_dos + and dos_grid is not None + and dft_dos_plot is not None + and ml_dos_plot is not None + ): + a1.fill_betweenx(dos_grid, 0.0, dft_dos_plot, color="k", alpha=0.25) + a1.plot(dft_dos_plot, dos_grid, color="k", lw=2.0) + a1.plot(ml_dos_plot, dos_grid, lw=2.0) + a1.set_xlabel("DOS", fontsize=20) + a1.grid(True, linestyle=":", linewidth=0.6) + plt.setp(a1.get_yticklabels(), visible=False) + a1.set_ylim(y_lo, y_hi) + else: + a1.axis("off") + + bio = BytesIO() + fig.savefig(bio, format="png", dpi=200) + plt.close(fig) + + data_uri = "data:image/png;base64," + base64.b64encode(bio.getvalue()).decode( + "ascii" + ) + return ( + data_uri, + { + "omega_avg_ref_thz": omega_avg_ref, + "omega_avg_pred_thz": omega_avg_pred, + }, + ) + + +def render_dispersion_component( + selection_context: dict[str, Any], + *, + calc_root: Path, + **_: Any, +): + """ + Render the dispersion + DOS panel for a selected scatter point. + + Parameters + ---------- + selection_context + Selection payload produced by the scatter/table callbacks. + calc_root + Root directory containing calculation artifacts referenced by ``data_paths``. + **_ + Additional keyword arguments accepted for callback API compatibility. + + Returns + ------- + Any or None + A Dash component containing the rendered image and caption, or ``None`` if the + required artifacts are unavailable. + """ + selected = ( + selection_context.get("selection") + if isinstance(selection_context, dict) + else None + ) + if not isinstance(selected, dict): + selected = selection_context if isinstance(selection_context, dict) else {} + + data_paths = selected.get("data_paths") + if not isinstance(data_paths, dict): + return None + + npz_rel = data_paths.get("npz") + if not isinstance(npz_rel, str) or not npz_rel: + return None + + meta_rel = data_paths.get("meta") + meta_rel_str = meta_rel if isinstance(meta_rel, str) and meta_rel else None + + npz_path = (calc_root / npz_rel).resolve() + meta_path = (calc_root / meta_rel_str).resolve() if meta_rel_str else None + + src, extras = _render_npz_to_data_uri( + str(npz_path), + str(meta_path) if meta_path else None, + ) + if not src: + return None + + caption_bits = [] + oref = extras.get("omega_avg_ref_thz") + opred = extras.get("omega_avg_pred_thz") + if oref is not None and opred is not None: + caption_bits.append(f"ω_avg: DFT {oref:.3f} | ML {opred:.3f} THz") + + caption = " | ".join(caption_bits) + title = selected.get("system") or selected.get("label") or selected.get("id") or "" + + return html.Div( + [ + html.H4(title) if title else None, + html.Img( + src=src, + style={ + "width": "100%", + "maxWidth": "820px", + "height": "auto", + "display": "block", + "borderRadius": "8px", + "border": "1px solid #ddd", + }, + ), + html.Div( + caption, + style={"marginTop": "8px", "fontSize": "0.95rem", "opacity": 0.85}, + ) + if caption + else None, + ] + ) diff --git a/ml_peg/calcs/bulk_crystal/diamond_phonons/calc_diamond_phonons.py b/ml_peg/calcs/bulk_crystal/diamond_phonons/calc_diamond_phonons.py index cfa473d44..2d291c566 100644 --- a/ml_peg/calcs/bulk_crystal/diamond_phonons/calc_diamond_phonons.py +++ b/ml_peg/calcs/bulk_crystal/diamond_phonons/calc_diamond_phonons.py @@ -22,17 +22,24 @@ from phonopy.structure.atoms import PhonopyAtoms import pytest +from ml_peg.calcs.utils.utils import download_github_data from ml_peg.models.get_models import load_models from ml_peg.models.models import current_models -HERE = Path(__file__).parent -DATA_PATH = HERE / "data" -OUT_PATH = HERE / "outputs" +GITHUB_BASE = "https://raw.githubusercontent.com/7radians/ml-peg-data/main" +EXTRACTED_ROOT = Path( + download_github_data( + filename="diamond_data/data.zip", + github_uri=GITHUB_BASE, + ) +) + +DATA_PATH = EXTRACTED_ROOT / "data" DIAMOND_YAML = DATA_PATH / "diamond.yaml" DFT_BAND_NPZ = DATA_PATH / "dft_band.npz" +OUT_PATH = Path(__file__).parent / "outputs" -# Load all MLIP models to benchmark. MODELS = load_models(current_models) @@ -236,6 +243,7 @@ def test_diamond_phonons_band(mlip) -> None: raise RuntimeError(f"No displaced supercells found in {DIAMOND_YAML}.") calc = model.get_calculator() + forces = [] for scp in scells: ase_sc = phonopy_atoms_to_ase(scp) diff --git a/ml_peg/calcs/bulk_crystal/ti64_phonons/calc_ti64_phonons.py b/ml_peg/calcs/bulk_crystal/ti64_phonons/calc_ti64_phonons.py new file mode 100644 index 000000000..e640dab32 --- /dev/null +++ b/ml_peg/calcs/bulk_crystal/ti64_phonons/calc_ti64_phonons.py @@ -0,0 +1,591 @@ +""" +Run Ti64 CASTEP phonon suite (raw outputs only). + +This module writes per-case raw calculation outputs for each MLIP model to +``outputs//`` as: + +- ``.npz``: raw arrays used by analysis +- ``.json``: minimal metadata (no metrics) +""" + +from __future__ import annotations + +import json +from pathlib import Path +import re +from typing import Any + +import ase.io +from ase.optimize import LBFGS +import numpy as np +import pytest + +from ml_peg.calcs import CALCS_ROOT +from ml_peg.calcs.utils.ASE_to_phonons import AtomsToPDOS, AtomsToPhonons +from ml_peg.calcs.utils.CASTEP_reader_phonon_dispersion import PhononFromCastep +from ml_peg.calcs.utils.utils import download_github_data +from ml_peg.models.get_models import load_models +from ml_peg.models.models import current_models + +GITHUB_BASE = "https://raw.githubusercontent.com/7radians/ml-peg-data/main" + +EXTRACTED_ROOT = Path( + download_github_data( + filename="ti64_data/data.zip", + github_uri=GITHUB_BASE, + ) +) + +DATA_PATH = EXTRACTED_ROOT / "data" + +OUT_PATH = CALCS_ROOT / "bulk_crystal" / "ti64_phonons" / "outputs" + +FMAX = 0.001 +STEPS = 10000 +MESH_202020 = [20, 20, 20] + + +def _json_default(obj: Any) -> Any: + """ + JSON serializer for numpy/Path objects. + + Parameters + ---------- + obj + Object to convert. + + Returns + ------- + Any + JSON-serialisable representation. + """ + if isinstance(obj, Path): + return str(obj) + if isinstance(obj, np.integer): + return int(obj) + if isinstance(obj, np.floating): + return float(obj) + if isinstance(obj, np.ndarray): + return obj.tolist() + return str(obj) + + +def write_case_npz(case_name: str, model_name: str, **arrays: Any) -> None: + """ + Write raw per-case arrays to a compressed NPZ file. + + Parameters + ---------- + case_name + Case identifier used in the output filename. + model_name + Model identifier used in the output directory. + **arrays + Keyword arrays to store in the NPZ file. + + Notes + ----- + Output path: + ``outputs//.npz`` + """ + out_dir = OUT_PATH / model_name + out_dir.mkdir(parents=True, exist_ok=True) + np.savez_compressed(out_dir / f"{case_name}.npz", **arrays) + + +def write_case_metadata(case_name: str, model_name: str, meta: dict[str, Any]) -> None: + """ + Write minimal per-case metadata to JSON (no metrics). + + Parameters + ---------- + case_name + Case identifier used in the output filename. + model_name + Model identifier used in the output directory. + meta + Metadata mapping to write. + + Notes + ----- + Output path: + ``outputs//.json`` + """ + out_dir = OUT_PATH / model_name + out_dir.mkdir(parents=True, exist_ok=True) + + meta_out = dict(meta) + meta_out["case"] = case_name + meta_out["model_name"] = model_name + + out_file = out_dir / f"{case_name}.json" + with out_file.open("w", encoding="utf8") as handle: + json.dump(meta_out, handle, indent=2, default=_json_default) + + +def patch_ase_castep_reader_dummy_energy(dummy_energy: float = 0.0) -> None: + """ + Patch ASE CASTEP reader to tolerate missing energy keys. + + ASE's CASTEP reader can raise ``KeyError`` for CASTEP phonon/qpoints outputs. + This patch only adds dummy values when ASE raises ``KeyError``. + + Parameters + ---------- + dummy_energy + Dummy energy/free energy (eV) to use when missing. + """ + import ase.io.castep.castep_reader as cr # noqa: WPS433 (local import is intentional) + + orig = cr._set_energy_and_free_energy + + def _safe_set_energy_and_free_energy(results: dict[str, Any]) -> None: + """ + Set energy/free_energy keys, tolerating missing values. + + Parameters + ---------- + results + ASE results dictionary to patch in-place. + """ + try: + orig(results) + except KeyError: + results.setdefault("energy", float(dummy_energy)) + results.setdefault("free_energy", float(dummy_energy)) + + cr._set_energy_and_free_energy = _safe_set_energy_and_free_energy + + +class PhononFromCastepPDOS(PhononFromCastep): + """ + Parse CASTEP q-point phonon output for PDOS. + + Parameters + ---------- + castep_file + Path to a CASTEP output file containing q-point frequencies and weights. + atoms_in + Structure used to determine the number of branches (3N). + + Attributes + ---------- + frequencies + Phonon frequencies array with shape ``(nq, 3N)``. + weights + Q-point weights array with shape ``(nq,)``. + """ + + def __init__(self, castep_file: str, atoms_in: Any | None = None) -> None: + """ + Initialise from a CASTEP file and extract frequencies/weights. + + Parameters + ---------- + castep_file + Path to the CASTEP q-points output file. + atoms_in + Structure used to determine the number of branches (3N). Required. + """ + if atoms_in is None: + raise ValueError( + "atoms_in must be provided to set number_of_branches (=3N)." + ) + + self.number_of_branches = len(atoms_in) * 3 + self.filename = castep_file + self.read_in_file() + self.get_frequencies() + self.get_weights() + delattr(self, "filelines") + + def __str__(self) -> str: + """ + Return a short human-readable description of this object. + + Returns + ------- + str + Description string. + """ + return "Phonon q-point frequencies+weights from CASTEP file object" + + def get_weights(self) -> None: + """Extract q-point weights from filelines.""" + float_re = re.compile(r"[-+]?(?:\d*\.\d+|\d+)(?:[Ee][-+]?\d+)?") + weights: list[float] = [] + + for line in self.filelines: + if "q-pt" not in line: + continue + + # 1) Preferred: explicit 'weight=' + match = re.search( + r"weight\s*=\s*(" + float_re.pattern + r")", + line, + flags=re.I, + ) + if match: + weights.append(float(match.group(1))) + continue + + # 2) Fallback: take the last float on the q-pt line + nums = float_re.findall(line) + if nums: + weights.append(float(nums[-1])) + + nq_freq = self.frequencies.shape[0] + + if not weights: + # 3) No weights printed -> assume uniform weights + self.weights = np.ones(nq_freq, dtype=float) + return + + # Some files include extra q-pt header lines; align length + w = np.array(weights, dtype=float) + if w.shape[0] > nq_freq: + w = w[-nq_freq:] + + if w.shape[0] != nq_freq: + raise ValueError( + f"Parsed {w.shape[0]} weights but frequencies has {nq_freq} q-points." + ) + + self.weights = w + + +def run_case( + *, + case_name: str, + structure_file: str, + qpoints_file: str | None, + kpath: list, + labels: list, + grid: list, + disp_phonons: float, + disp_pdos: float, + do_tp: bool, + calc: Any, + model_name: str, +) -> None: + """ + Run one Ti64 phonon case and write raw artifacts. + + Parameters + ---------- + case_name + Case identifier. + structure_file + CASTEP structure file path (relative to repository root). + qpoints_file + Optional CASTEP qpoints file path. + kpath + High-symmetry k-path used by CASTEP dispersion output. + labels + Tick labels for the k-path. + grid + Phonon supercell grid (3x3 matrix-like list). + disp_phonons + Displacement magnitude for dispersion calculation. + disp_pdos + Displacement magnitude for DOS/PDOS calculation. + do_tp + Whether to compute thermo/TP outputs. + calc + ASE calculator from the selected model. + model_name + Model identifier used for output paths. + """ + print(f"{case_name} | {model_name}") + + # DFT dispersion along k-path (raw reference) + pfc = PhononFromCastep(castep_file=structure_file, kpath_in=kpath) + + # Relax + atoms = ase.io.read(structure_file) + atoms.calc = calc + dyn = LBFGS(atoms, logfile=None) + dyn.run(fmax=FMAX, steps=STEPS) + + # ML dispersion + atp_ml = AtomsToPhonons( + primitive_cell=atoms, + phonon_grid=grid, + displacement=disp_phonons, + kpath=[kpath], + calculator=calc, + plusminus=True, + ) + + # ML DOS/PDOS + atp_pdos = AtomsToPDOS( + primitive_cell=atoms, + phonon_grid=grid, + displacement=disp_pdos, + calculator=calc, + ) + atp_pdos.get_pdos(MESH_202020) + atp_pdos.get_dos(MESH_202020) + + # ML thermo + if do_tp: + atp_pdos.get_tp(MESH_202020, tmax=2000, tstep=1) + + # DFT qpoints + q_weights: np.ndarray | None = None + q_freq_dft: np.ndarray | None = None + if qpoints_file is not None: + q_atoms = ase.io.read(qpoints_file) + pfc_q = PhononFromCastepPDOS(castep_file=qpoints_file, atoms_in=q_atoms) + q_weights = np.asarray(pfc_q.weights, dtype=float) + q_freq_dft = np.asarray(pfc_q.frequencies, dtype=float) + + # JSON metadata + meta: dict[str, Any] = { + "structure_file": str(structure_file), + "qpoints_file": str(qpoints_file) if qpoints_file is not None else None, + "kpath": [kpath], + "labels": labels, + "phonon_grid": grid, + "displacement_phonons": float(disp_phonons), + "displacement_pdos": float(disp_pdos), + "relax_settings": {"fmax": float(FMAX), "steps": int(STEPS)}, + "did_tp": bool(do_tp), + "tp_settings": {"mesh": MESH_202020, "tmax": 2000, "tstep": 1} + if do_tp + else None, + } + write_case_metadata(case_name, model_name, meta) + + arrays: dict[str, np.ndarray] = { + "n_atoms": np.int64(len(atoms)), + "labels": np.array(labels, dtype=object), + "kpath": np.array([kpath], dtype=object), + "phonon_grid": np.asarray(grid, dtype=int), + # DFT dispersion + "dft_x": np.asarray(pfc.xscale, dtype=float), + "dft_frequencies": np.asarray(pfc.frequencies, dtype=float), + # ML dispersion + "ml_frequencies": np.asarray(atp_ml.frequencies, dtype=float), + } + + if hasattr(atp_ml, "normal_ticks") and atp_ml.normal_ticks is not None: + arrays["ml_normal_ticks"] = np.asarray(atp_ml.normal_ticks, dtype=float) + + # PDOS + if "frequency_points" in atp_pdos.pdos: + arrays["pdos_frequency_points"] = np.asarray( + atp_pdos.pdos["frequency_points"], + dtype=float, + ) + if "projected_dos" in atp_pdos.pdos: + arrays["pdos_projected"] = np.asarray( + atp_pdos.pdos["projected_dos"], dtype=float + ) + if "dos" in atp_pdos.pdos: + arrays["dos_total"] = np.asarray(atp_pdos.pdos["dos"], dtype=float) + + # TP + if do_tp and hasattr(atp_pdos, "tp_dict") and atp_pdos.tp_dict: + if "temperatures" in atp_pdos.tp_dict: + arrays["tp_temperatures"] = np.asarray( + atp_pdos.tp_dict["temperatures"], dtype=float + ) + if "free_energy" in atp_pdos.tp_dict: + arrays["tp_free_energy"] = np.asarray( + atp_pdos.tp_dict["free_energy"], dtype=float + ) + + # qpoints + if qpoints_file is not None: + # These are defined whenever qpoints_file is not None (see above). + arrays["q_weights"] = np.asarray(q_weights, dtype=float) # type: ignore[arg-type] + arrays["q_frequencies_dft"] = np.asarray(q_freq_dft, dtype=float) # type: ignore[arg-type] + + write_case_npz(case_name, model_name, **arrays) + + +def _hex_path() -> tuple[list[list[float]], list[str]]: + """ + Return the high-symmetry path and tick labels for the hexagonal cell. + + Returns + ------- + tuple[list[list[float]], list[str]] + ``(kpath, labels)`` where ``kpath`` is a list of fractional k-points and + ``labels`` are the corresponding tick labels. + """ + gam = [0, 0, 0] + a_pt = [0, 0, 1 / 2] + k_pt = [1 / 3, 1 / 3, 0] + m_pt = [0.5, 0, 0] + return [gam, k_pt, m_pt, gam, a_pt], ["$\\Gamma$", "K", "M", "$\\Gamma$", "A"] + + +def _bcc_path() -> tuple[list[list[float]], list[str]]: + """ + Return the high-symmetry path and tick labels for the BCC cell. + + Returns + ------- + tuple[list[list[float]], list[str]] + ``(kpath, labels)`` where ``kpath`` is a list of fractional k-points and + ``labels`` are the corresponding tick labels. + """ + gam = [0, 0, 0] + h_pt = [0.5, -0.5, 0.5] + p_pt = [0.25, 0.25, 0.25] + n_pt = [0, 0, 0.5] + return ( + [gam, h_pt, n_pt, gam, p_pt, h_pt, p_pt, n_pt], + ["$\\Gamma$", "H", "N", "$\\Gamma$", "P", "H", "P", "N"], + ) + + +GRID_222 = [[2, 0, 0], [0, 2, 0], [0, 0, 2]] +HEX_KPATH, HEX_LABELS = _hex_path() +BCC_KPATH, BCC_LABELS = _bcc_path() + +TP_ON = { + "hcp_Ti6AlV", # 1/10 + "hex_Ti8AlV", # 3/10 + "hcp_Ti6Al2", # 4/10 + "hcp_Ti6V2", # 5/10 + "hcp_Ti7V", # 6/10 + "hex_Ti10Al2", # 9/10 + "hex_Ti10V2", # 10/10 +} + +CASES: list[dict[str, Any]] = [ + { + "case_name": "hcp_Ti6AlV", + "structure_file": DATA_PATH / "ti64_hcp_phonon.castep", + "qpoints_file": DATA_PATH / "ti64_hcp_phonon_qpoints.castep", + "kpath": HEX_KPATH, + "labels": HEX_LABELS, + "grid": GRID_222, + "disp_phonons": 0.02, + "disp_pdos": 0.02, + }, + { + "case_name": "bcc_Ti6AlV", + "structure_file": DATA_PATH / "ti64_bcc_phonon.castep", + "qpoints_file": DATA_PATH / "ti64_bcc_phonon_qpoints.castep", + "kpath": BCC_KPATH, + "labels": BCC_LABELS, + "grid": GRID_222, + "disp_phonons": 0.02, + "disp_pdos": 0.02, + }, + { + "case_name": "hex_Ti8AlV", + "structure_file": DATA_PATH / "ti64_hex_phonon.castep", + "qpoints_file": DATA_PATH / "ti64_hex_phonon_qpoints.castep", + "kpath": HEX_KPATH, + "labels": HEX_LABELS, + "grid": GRID_222, + "disp_phonons": 0.02, + "disp_pdos": 0.01, + }, + { + "case_name": "hcp_Ti6Al2", + "structure_file": DATA_PATH / "ti64_hcp_phonon_AlAl_qpath.castep", + "qpoints_file": DATA_PATH / "ti64_hcp_phonon_AlAl_qpoints.castep", + "kpath": HEX_KPATH, + "labels": HEX_LABELS, + "grid": GRID_222, + "disp_phonons": 0.02, + "disp_pdos": 0.02, + }, + { + "case_name": "hcp_Ti6V2", + "structure_file": DATA_PATH / "ti64_hcp_phonon_VV_qpath.castep", + "qpoints_file": DATA_PATH / "ti64_hcp_phonon_VV_qpoints.castep", + "kpath": HEX_KPATH, + "labels": HEX_LABELS, + "grid": GRID_222, + "disp_phonons": 0.02, + "disp_pdos": 0.02, + }, + { + "case_name": "hcp_Ti7V", + "structure_file": DATA_PATH / "Ti7V_hcp_phonon_qpath.castep", + "qpoints_file": DATA_PATH / "Ti7V_hcp_phonon_qpoints.castep", + "kpath": HEX_KPATH, + "labels": HEX_LABELS, + "grid": GRID_222, + "disp_phonons": 0.02, + "disp_pdos": 0.02, + }, + { + "case_name": "bcc_Ti6Al2", + "structure_file": DATA_PATH / "ti64_bcc_phonon_AlAl_qpath.castep", + "qpoints_file": DATA_PATH / "ti64_bcc_phonon_AlAl_qpoints.castep", + "kpath": BCC_KPATH, + "labels": BCC_LABELS, + "grid": GRID_222, + "disp_phonons": 0.02, + "disp_pdos": 0.02, + }, + { + "case_name": "bcc_Ti6V2", + "structure_file": DATA_PATH / "ti64_bcc_phonon_VV_qpath.castep", + "qpoints_file": DATA_PATH / "ti64_bcc_phonon_VV_qpoints.castep", + "kpath": BCC_KPATH, + "labels": BCC_LABELS, + "grid": GRID_222, + "disp_phonons": 0.02, + "disp_pdos": 0.02, + }, + { + "case_name": "hex_Ti10Al2", + "structure_file": DATA_PATH / "ti64_hex_phonon_AlAl_qpath.castep", + "qpoints_file": DATA_PATH / "ti64_hex_phonon_AlAl_qpoints.castep", + "kpath": HEX_KPATH, + "labels": HEX_LABELS, + "grid": GRID_222, + "disp_phonons": 0.02, + "disp_pdos": 0.02, + }, + { + "case_name": "hex_Ti10V2", + "structure_file": DATA_PATH / "ti64_hex_phonon_VV_qpath.castep", + "qpoints_file": DATA_PATH / "ti64_hex_phonon_VV_qpoints.castep", + "kpath": HEX_KPATH, + "labels": HEX_LABELS, + "grid": GRID_222, + "disp_phonons": 0.02, + "disp_pdos": 0.02, + }, +] + + +MODELS = load_models(current_models) +MODEL_ITEMS = list(MODELS.items()) +MODEL_IDS = [name for name, _ in MODEL_ITEMS] + + +@pytest.mark.parametrize("mlip", MODEL_ITEMS, ids=MODEL_IDS) +def test_phonon_suite(mlip: tuple[str, Any]) -> None: + """ + Run the full Ti64 phonon suite for one model and write artifacts. + + Parameters + ---------- + mlip + Tuple ``(model_name, model)`` from ``MODEL_ITEMS``. + """ + patch_ase_castep_reader_dummy_energy(dummy_energy=0.0) + + model_name, model = mlip + calc = model.get_calculator() + + for spec in CASES: + case_name = spec["case_name"] + do_tp = case_name in TP_ON + + run_case(**spec, do_tp=do_tp, calc=calc, model_name=model_name) + + out_dir = OUT_PATH / model_name + assert (out_dir / f"{case_name}.npz").exists() + assert (out_dir / f"{case_name}.json").exists() diff --git a/ml_peg/calcs/utils/ASE_to_phonons.py b/ml_peg/calcs/utils/ASE_to_phonons.py new file mode 100644 index 000000000..faaf1f6b8 --- /dev/null +++ b/ml_peg/calcs/utils/ASE_to_phonons.py @@ -0,0 +1,492 @@ +"""ASE → phonopy helpers for band structures and DOS/PDOS.""" + +from __future__ import annotations + +from collections.abc import Sequence +from typing import Any + +import ase +import ase.io +import numpy as np +from phonopy import Phonopy +from phonopy.phonon.band_structure import get_band_qpoints_and_path_connections +from phonopy.structure.atoms import PhonopyAtoms + + +class AtomsToPhonons: + """ + Compute phonon band structures from an ASE/phonopy workflow. + + Parameters + ---------- + primitive_cell + Primitive cell as an ASE ``Atoms`` or a ``PhonopyAtoms``-like object. + phonon_grid + Phonopy supercell matrix. + displacement + Displacement magnitude for finite-difference force constants. + kpath + K-path provided to phonopy's band structure routine. + calculator + Either an ASE calculator (default workflow) or an iterable of CASTEP files + (when ``castep=True``). + kpoints + Number of points per k-path segment. + castep + If ``True``, forces are read from CASTEP output structures. + plusminus + Whether to generate plus/minus displacements. + diagonal + Whether to include diagonal displacements. + + Attributes + ---------- + phonon + Phonopy object holding force constants and band structure. + supercells + Displaced supercells used for force evaluation. + forces + List of forces arrays (one per displaced supercell). + frequencies + Concatenated frequencies along the k-path. + xticks + Tick positions corresponding to k-path segment boundaries. + normal_ticks + Evenly spaced tick positions used by some plotting utilities. + """ + + def __init__( + self, + primitive_cell: Any, + phonon_grid: Any, + displacement: float, + kpath: Any, + calculator: Any, + kpoints: int = 100, + castep: bool = False, + plusminus: bool = False, + diagonal: bool = True, + ) -> None: + """ + Initialise the workflow and compute the phonon band structure. + + Parameters + ---------- + primitive_cell + Primitive cell structure. + phonon_grid + Phonopy supercell matrix. + displacement + Displacement magnitude for finite differences. + kpath + K-path definition compatible with phonopy band structure routines. + calculator + ASE calculator or iterable of CASTEP output paths (when ``castep=True``). + kpoints + Number of points per k-path segment. + castep + If ``True``, read forces from CASTEP outputs instead of computing with ASE. + plusminus + Whether to generate ± displacements. + diagonal + Whether to generate diagonal displacements. + """ + self.calculator_string = calculator + self.get_supercell( + primitive_cell, phonon_grid, displacement, plusminus, diagonal + ) + + self.forces: list[np.ndarray] | None = None + if castep is False: + self.get_forces_model() + if castep is True: + self.get_forces_castep() + + if self.forces is None or len(self.forces) == 0: + raise RuntimeError("Forces were not generated.") + + self.get_band_struct(kpath, kpoints) + + def _scaled_positions(self, obj: Any) -> Any: + """ + Return scaled positions from either PhonopyAtoms or ASE Atoms. + + Parameters + ---------- + obj + Object providing either a ``scaled_positions`` attribute or a + ``get_scaled_positions()`` method. + + Returns + ------- + Any + Scaled positions array. + """ + if hasattr(obj, "scaled_positions"): + return obj.scaled_positions + return obj.get_scaled_positions() + + def get_supercell( + self, + primitive_cell: Any, + phonon_grid: Any, + displacement: float, + plusminus: bool, + diagonal: bool, + ) -> None: + """ + Construct phonopy object and displaced supercells. + + Parameters + ---------- + primitive_cell + Primitive cell structure. + phonon_grid + Phonopy supercell matrix. + displacement + Displacement magnitude for finite differences. + plusminus + Whether to use ± displacements. + diagonal + Whether to generate diagonal displacements. + """ + self.unitcell = PhonopyAtoms( + symbols=primitive_cell.symbols + if hasattr(primitive_cell, "symbols") + else primitive_cell.get_chemical_symbols(), + cell=primitive_cell.cell, + scaled_positions=self._scaled_positions(primitive_cell), + ) + + self.phonon = Phonopy(self.unitcell, phonon_grid) + self.phonon.generate_displacements( + distance=displacement, + is_plusminus=plusminus, + is_diagonal=diagonal, + ) + self.supercells = self.phonon.supercells_with_displacements + + def get_forces_model(self) -> None: + """Compute forces using an ASE calculator.""" + potential = self.calculator_string + self.forces = [] + self.atoms: list[ase.Atoms] = [] + + for s in self.supercells: + atoms = ase.Atoms( + symbols=list(s.symbols), + cell=s.cell, + scaled_positions=self._scaled_positions(s), + pbc=True, + ) + self.atoms.append(atoms) + atoms.calc = potential + self.forces.append(atoms.get_forces()) + + def get_forces_castep(self) -> None: + """Read forces from CASTEP output structures.""" + forces: list[np.ndarray] = [] + self.atoms = [] + + for _i, path in enumerate(self.calculator_string): + castep_atoms = ase.io.read(path) + self.atoms.append(castep_atoms) + forces.append(castep_atoms.get_forces()) + + self.forces = forces + + def get_band_struct(self, kpath: Any, kpoints: int) -> None: + """ + Compute band structure along the provided k-path. + + Parameters + ---------- + kpath + K-path definition compatible with + ``get_band_qpoints_and_path_connections``. + kpoints + Number of points per k-path segment. + """ + self.phonon.forces = np.asarray(self.forces, dtype=float) + self.phonon.produce_force_constants() + + qpoints, connections = get_band_qpoints_and_path_connections( + kpath, npoints=kpoints + ) + self.phonon.run_band_structure( + qpoints, + with_eigenvectors=True, + path_connections=connections, + ) + + bs = self.phonon.get_band_structure_dict() + self.frequencies_array = bs["frequencies"] + self.eigenvectors = bs["eigenvectors"] + + self.frequencies = np.array(self.frequencies_array[0], copy=True) + + xticks: list[int] = [] + x = 0 + for val in self.frequencies_array[1:]: + self.frequencies = np.append(self.frequencies, val, axis=0) + x += len(val) + xticks.append(x) + + self.xticks = xticks + n_kpoints = kpoints * len(kpath[0]) + n_trace = int(n_kpoints / (len(kpath[0]))) + self.normal_ticks = [i * n_trace for i in range(len(kpath[0]))] + + +class AtomsToPDOS: + """ + Compute force constants and DOS/PDOS/thermal properties via phonopy. + + Parameters + ---------- + primitive_cell + Primitive cell as an ASE ``Atoms`` or a ``PhonopyAtoms``-like object. + phonon_grid + Phonopy supercell matrix. + displacement + Displacement magnitude for finite-difference force constants. + calculator + Either an ASE calculator (default workflow) or an iterable of CASTEP files + (when ``castep=True``). + kpoints + Unused (kept for API compatibility with :class:`AtomsToPhonons`). + castep + If ``True``, forces are read from CASTEP output structures. + plusminus + Whether to generate plus/minus displacements. + diagonal + Whether to include diagonal displacements. + + Attributes + ---------- + phonon + Phonopy object holding force constants and mesh results. + supercells + Displaced supercells used for force evaluation. + forces + List of forces arrays (one per displaced supercell). + pdos + Projected DOS dictionary (set by :meth:`get_pdos`). + dos + Total DOS dictionary (set by :meth:`get_dos`). + tp_dict + Thermal properties dictionary (set by :meth:`get_tp`). + """ + + def __init__( + self, + primitive_cell: Any, + phonon_grid: Any, + displacement: float, + calculator: Any, + kpoints: int = 100, + castep: bool = False, + plusminus: bool = False, + diagonal: bool = True, + ) -> None: + """ + Initialise the workflow and build force constants. + + Parameters + ---------- + primitive_cell + Primitive cell structure. + phonon_grid + Phonopy supercell matrix. + displacement + Displacement magnitude for finite differences. + calculator + ASE calculator or iterable of CASTEP output paths (when ``castep=True``). + kpoints + Unused (kept for API compatibility). + castep + If ``True``, read forces from CASTEP outputs instead of computing with ASE. + plusminus + Whether to generate ± displacements. + diagonal + Whether to generate diagonal displacements. + """ + _ = kpoints + self.calculator_string = calculator + self.get_supercell( + primitive_cell, phonon_grid, displacement, plusminus, diagonal + ) + + self.forces: list[np.ndarray] | None = None + if castep is False: + self.get_forces_model() + if castep is True: + self.get_forces_castep() + + if self.forces is None: + raise RuntimeError( + "Forces were not generated. Check calculator/castep inputs." + ) + + self.get_dynamical_matrix() + + def _scaled_positions(self, obj: Any) -> Any: + """ + Return scaled positions from either PhonopyAtoms or ASE Atoms. + + Parameters + ---------- + obj + Object providing either a ``scaled_positions`` attribute or a + ``get_scaled_positions()`` method. + + Returns + ------- + Any + Scaled positions array. + """ + return ( + obj.scaled_positions + if hasattr(obj, "scaled_positions") + else obj.get_scaled_positions() + ) + + def get_supercell( + self, + primitive_cell: Any, + phonon_grid: Any, + displacement: float, + plusminus: bool, + diagonal: bool, + ) -> None: + """ + Construct phonopy object and displaced supercells. + + Parameters + ---------- + primitive_cell + Primitive cell structure. + phonon_grid + Phonopy supercell matrix. + displacement + Displacement magnitude for finite differences. + plusminus + Whether to use ± displacements. + diagonal + Whether to generate diagonal displacements. + """ + self.unitcell = PhonopyAtoms( + symbols=primitive_cell.symbols + if hasattr(primitive_cell, "symbols") + else primitive_cell.get_chemical_symbols(), + cell=primitive_cell.cell, + scaled_positions=self._scaled_positions(primitive_cell), + ) + self.phonon = Phonopy(self.unitcell, phonon_grid) + self.phonon.generate_displacements( + distance=displacement, + is_plusminus=plusminus, + is_diagonal=diagonal, + ) + self.supercells = self.phonon.supercells_with_displacements + + def get_forces_model(self) -> None: + """Compute forces using an ASE calculator.""" + potential = self.calculator_string + self.forces = [] + self.atoms: list[ase.Atoms] = [] + + for s in self.supercells: + atoms = ase.Atoms( + symbols=list(s.symbols), + cell=s.cell, + scaled_positions=self._scaled_positions(s), + pbc=True, + ) + self.atoms.append(atoms) + atoms.calc = potential + self.forces.append(atoms.get_forces()) + + def get_forces_castep(self) -> None: + """Read forces from CASTEP output structures.""" + forces: list[np.ndarray] = [] + self.atoms = [] + + for _i, path in enumerate(self.calculator_string): + castep_atoms = ase.io.read(path) + self.atoms.append(castep_atoms) + forces.append(castep_atoms.get_forces()) + + self.forces = forces + + def get_dynamical_matrix(self) -> None: + """ + Build force constants from the stored forces. + + Raises + ------ + ValueError + If the number of force sets does not match the number of supercells. + """ + forces = np.asarray(self.forces, dtype=float) + + if len(forces) != len(self.supercells): + raise ValueError( + f"Number of force sets ({len(forces)}) != number of supercells " + f"({len(self.supercells)})." + ) + + self.phonon.forces = forces + self.phonon.produce_force_constants() + + def get_pdos(self, qmesh: Sequence[int]) -> None: + """ + Compute projected DOS and total DOS on a mesh. + + Parameters + ---------- + qmesh + Q-mesh used for DOS calculations. + """ + self.phonon.run_mesh(qmesh, with_eigenvectors=True, is_mesh_symmetry=False) + self.phonon.run_projected_dos() + self.phonon.run_total_dos() + self.pdos = self.phonon.get_projected_dos_dict() + + def get_tp( + self, + qmesh: Sequence[int], + tmin: float = 0, + tmax: float = 2000, + tstep: float = 100, + ) -> None: + """ + Compute thermal properties. + + Parameters + ---------- + qmesh + Q-mesh for thermal properties. + tmin + Minimum temperature (K). + tmax + Maximum temperature (K). + tstep + Temperature step (K). + """ + self.phonon.run_mesh(qmesh) + self.phonon.run_thermal_properties(t_step=tstep, t_max=tmax, t_min=tmin) + self.tp_dict = self.phonon.get_thermal_properties_dict() + + def get_dos(self, qmesh: Sequence[int]) -> None: + """ + Compute total DOS on a mesh. + + Parameters + ---------- + qmesh + Q-mesh used for total DOS calculation. + """ + self.phonon.run_mesh(qmesh) + self.phonon.run_total_dos() + self.dos = self.phonon.get_total_dos_dict() diff --git a/ml_peg/calcs/utils/CASTEP_reader_phonon_dispersion.py b/ml_peg/calcs/utils/CASTEP_reader_phonon_dispersion.py new file mode 100644 index 000000000..a0e557207 --- /dev/null +++ b/ml_peg/calcs/utils/CASTEP_reader_phonon_dispersion.py @@ -0,0 +1,213 @@ +""" +Ti64 phonons CASTEP phonon dispersion reader. + +This module provides a lightweight parser for CASTEP phonon output files to +extract phonon frequencies (in THz) and q-point coordinates. Optionally, the +dispersion x-axis can be rescaled to span [0, 1] along a provided k-path. + +Notes +----- +- This parser assumes the CASTEP output uses THz units for phonon frequencies. +- The rescaling assumes ``kpath_in`` matches the k-path used in the CASTEP run. +""" + +from __future__ import annotations + +from pathlib import Path +import re +from typing import Any +import warnings + +import ase.io +import numpy as np + +warnings.simplefilter("ignore") + + +class PhononFromCastep: + """ + Extract phonon frequencies and k-points from a CASTEP phonon calculation. + + Parameters + ---------- + castep_file + Path to a CASTEP output file readable by ASE. + kpath_in + Optional k-path (high-symmetry points) used to rescale the dispersion + axis onto [0, 1]. Must match the CASTEP k-path convention. + verbose + If ``True``, print basic debug information. + + Attributes + ---------- + number_of_branches + Number of phonon branches (``3 * n_atoms``). + filename + Input file path as provided. + kpoints + Number of q-points parsed from the file. + frequencies + Frequencies array of shape ``(kpoints, number_of_branches)`` in THz. + kpath + q-point coordinates array of shape ``(kpoints, 3)``. + xscale + Optional rescaled x-axis (only present if ``kpath_in`` was provided). + """ + + RESCALE_TOL = 1e-4 + + def __init__( + self, + castep_file: str, + kpath_in: Any | None = None, + verbose: bool = False, + ) -> None: + """ + Initialise the reader and parse frequencies and q-point coordinates. + + Parameters + ---------- + castep_file + Path to a CASTEP output file readable by ASE. + kpath_in + Optional k-path (high-symmetry points) used to rescale the dispersion + axis onto [0, 1]. Must match the CASTEP k-path convention. + verbose + If ``True``, print basic debug information. + """ + self.filename = castep_file + + if not castep_file: + raise ValueError("castep_file must be provided.") + + try: + atoms = ase.io.read(castep_file) + except AttributeError as exc: + raise TypeError("Invalid input type for castep_file.") from exc + + self.number_of_branches = len(atoms) * 3 + + self.read_in_file() + self.get_frequencies() + self.get_kpath() + + if verbose: + print(f"Atoms object info:\n{atoms}\n") + print(self.__dict__.keys(), "\n") + + if kpath_in is not None: + self.rescale_xaxis(kpath_in) + if verbose: + print("k-path rescaled") + elif verbose: + print("no k-path re-scaling done") + + delattr(self, "filelines") + + def __str__(self) -> str: + """ + Return a short description. + + Returns + ------- + str + Description string. + """ + return "Phonon Dispersion (THz) from CASTEP file object" + + def read_in_file(self) -> None: + """Read the input file into memory as lines.""" + with Path(self.filename).open(encoding="utf8") as handle: + self.filelines = handle.readlines() + + def get_frequencies(self) -> None: + """Parse phonon frequencies (THz) into ``self.frequencies``.""" + headlines = 2 # number of lines before frequency numbers appear + + thz_blocks = [ + self.filelines[i + headlines : i + headlines + self.number_of_branches] + for i, val in enumerate(self.filelines) + if re.search(r" \(THz\) ", val) is not None + ] + + thz_lines = [line for block in thz_blocks for line in block] + thz_vals = [line.split()[2] for line in thz_lines] + + frequencies = np.array(thz_vals, dtype=float) + self.kpoints = int(len(frequencies) / self.number_of_branches) + self.frequencies = np.reshape( + frequencies, + (self.kpoints, self.number_of_branches), + ) + + def get_kpath(self) -> None: + """Parse q-point coordinates into ``self.kpath``.""" + qpt_lines = [ + self.filelines[i] + for i, val in enumerate(self.filelines) + if re.search(r"q-pt=", val) is not None + ] + + qpts: list[list[str]] = [] + for line in qpt_lines: + temp = line.split()[4:7] + temp[2] = temp[2].replace(")", "") + qpts.append(temp) + + self.kpath = np.array(qpts, dtype=float) + + def find_index(self, in_path: np.ndarray) -> None: + """ + Find indices of high-symmetry points along the parsed k-path. + + Parameters + ---------- + in_path + Array of target high-symmetry points with shape ``(n_points, 3)``. + """ + j = 0 + sympoint_idx: list[int] = [] + self.kpath_idx: list[list[int]] = [] + + for i, val in enumerate(self.kpath): + if abs(np.linalg.norm(in_path[j] - val)) < self.RESCALE_TOL: + sympoint_idx.append(i) + j += 1 + + for i in range(len(sympoint_idx) - 1): + idxs = list(range(sympoint_idx[i], sympoint_idx[i + 1] + 1)) + self.kpath_idx.append(idxs) + + def rescale_xaxis(self, rescale_xaxis: Any) -> None: + """ + Rescale the dispersion axis to span [0, 1] over the provided k-path. + + Parameters + ---------- + rescale_xaxis + Iterable of high-symmetry points (each a length-3 coordinate) used + to determine segment boundaries for rescaling. + """ + in_path = np.array(rescale_xaxis, dtype=float) + self.find_index(in_path) + + xsplit = 1.0 / len(self.kpath_idx) + xscale = [0.0] + pos = 0.0 + + kpath_cut: list[list[int]] = [self.kpath_idx[0]] + for i in range(len(self.kpath_idx) - 1): + kpath_cut.append(self.kpath_idx[i + 1][1:]) + + x_inc = xsplit / (len(kpath_cut[0]) - 1) + for _ in range(len(kpath_cut[0]) - 1): + pos += x_inc + xscale.append(pos) + + for i in range(len(kpath_cut) - 1): + x_inc = xsplit / len(kpath_cut[i + 1]) + for _ in range(len(kpath_cut[i + 1])): + pos += x_inc + xscale.append(pos) + + self.xscale = np.array(xscale, dtype=float) From cb9540f2e1c881c52bfeb28206b8140aaac583af Mon Sep 17 00:00:00 2001 From: Mariia Radova <56935802+7radians@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:12:07 +0000 Subject: [PATCH 3/6] Delete ml_peg/calcs/bulk_crystal/diamond_phonons/data directory --- .../diamond_phonons/data/dft_band.npz | Bin 18668 -> 0 bytes .../diamond_phonons/data/diamond.yaml | 2135 ----------------- 2 files changed, 2135 deletions(-) delete mode 100644 ml_peg/calcs/bulk_crystal/diamond_phonons/data/dft_band.npz delete mode 100644 ml_peg/calcs/bulk_crystal/diamond_phonons/data/diamond.yaml diff --git a/ml_peg/calcs/bulk_crystal/diamond_phonons/data/dft_band.npz b/ml_peg/calcs/bulk_crystal/diamond_phonons/data/dft_band.npz deleted file mode 100644 index b016d9a643215dcf34323cce840c062718e6b180..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18668 zcmeHPd3a6N*S_X?7BNc@p<*nG;%q~eni4Tnq!L7lL{LP2m3flfd(XXSP1PD(w5p?4 z4XvSQHD-x~2!d8=4ILEy)?RDveUsbY_k7<||0aF(efGNhoU_l~YrX4Td*{Y~P^r?j zE%f7U`8I9TH2=yLi~Lu~Qri*{K07pQZe-iA@P#ET)zK`w%8%ubW9Y~s{$op6=3AnC zgF_;x%<=8+<=bOwS6@Fb->I|b%$*YyHfi>p;1Fr=A2c&E1niO1gTh0g-LgZ+E`DCm zw(|3e_WGAU6=@Xz{Qun@{;#flZMC63QEkF)ZuV`)#;^8!wg~)0+f_UN=CYlYBX(=M zYFDfD$)<&e_iMXqxAAUrf99xz+OFE)oN66XFaKL@S8clAciLXGovH2g9+?I`=Y+Pa zcH=rThTO|IrR}O6I{TM|BO}geyJ|03w9&6`!*klM+Q|vyPVK&OUfWfBg{|76E?cv; zUA1YvIoht;zutdoY+~1o+FrDsr|tA!s-0)3b)x6G+qd!iU*_xc^tyfW+#g>Dzn8D= zG>+XykFSO=_s+PY?P{G4^w}PIG2*JWt92UmZMmMSMqbl)wN5nt0&Q38MDuoC+toS| z-Y_yh8v9^Z_C=%p;7t|#`>1uhVSqD?@%${;SXIzy^Nj%~`T841m+zk+dcvM#9PN1E z!?s~+J#TRP0)Kv5_owT#`F?7hZ}9W7`1xmbe;SusUxhb}GM#F4>G9QBjm<9n`JWlP z3yl7s-g|s{!5NJ$aRRRygMKUxSDbdktHv;NyptMd9elDg>TbSaTlYtYg7;6j;bmjl z=?c@E&dhYfJY(DT^ONQeJFYQ&Qn};x0xlY#c>Hzj_K{<5c){2k-Kguden&LQUf=Y| zGJ416AAaM@LmJh4()bVX_!XWuMhy2|m$GBOMm264&mJC+!sGnBU2b^9_&Ra(=%XD! z)~KFG_j#Z1qi~OLYRD_q$8_7EQCj3Ir9L;@k^1lB{x0kR-Z|mPH-f5mSulwQ2P#CL`}Qh&v7E^G|Z(Dv^W zhC5P!<@YX>QFOv`)^JDaul(DEbl)t#zYD3q;xQM}eJ}F;T}b_1{G{3+I7476^Nezn z-Wcq$^rE5K(MSY&#!Hn;woa;(YpAv#62WC7KxV6VE*Pqf;WziPF$Res+feN-oh3nB zHYT+?R{ii7=MB}Se)-1RXFJ{L`ocNBF5mDe8?&r$T0ZwXquZxfO5(}q@trnQJFcB1 zsw)PD{mqkxYSZ|x7=Nyt_R)`#CwRPyR~3({b-Zfa$lCUB!YuW9rct*=V43yiRpX<| z5!=!(D4xzVAZZ28ZGMe;&}AonyvDqy_&$^S74UT~JKeW{pQF|#lgIJD^Rrqv9A5Vm z#;j4%?|=G=t3L$g$KTFBY5Wm*W#g&?F8=Sbwb$tJAD%Ko-kS`R;j+K{r0>I@e>!cP zv|KD%A=Q;fUM#hsQuCH)4f`RId9N&)I8=HyB;Oo zzi=+gSYKjynVPj-c4}WR+WzhR__uj#9`pFVImW7yZ*ARvUd>mY@yq5zgPu#tHOg;m zI4<~kmpxl9+rf(lJzve^Kau?G68qaF?a#{Z6&Cf|T=v6U?VlTYxXEGv%+da) z;zS|!yUPBWt&!sBnvNe8M-(sD*pIU`Qk)d%xKi;&{;&MO#Utc5Dqo*xzdWn`jq-h# z&ezI6$*)yjKB@g#<#DAGl>d`{$TpTqTFE)C5Bt8 zKIwV62GUQn{W?DB`ME})AEutUKI2OrpES;k#-8?XK2vwu$2vZh&ItsyH7{bTj!zor zC4=m%+~QOD6^+_r$us(>ujPPoMOd}II7w_o`5-ZlRGCaZv3-0zMN zIotlM+;M4GIkB#OyWHkw%L`JC+}277Y{*W&#l!!;PSlIfS=Pqq^}6887CSFobHVc zd3_oQm~8l{5wBAtfz#KuvB2eC-B>h`%TS^T-Dg4*exD{ZjtXAXe$GpPleEhX_7ZqI z(@S6w?eh{(M2-r_EJVfDIA>HY(oirb5g z&J!0k6}O~2Zf#2A%4sUlt*SH=*D_(@dNrfxL^Pv$Ti;B`VaJ?lCeFa)JCtuu_vzkT zpm>?tTr`mmzP357+ll5vc3)P?hsM*{M^tUIbIqM;KB5mqS<_WMVxDx^<32neE!aO= z(730z5Wdo}SGEx7zTdZ?_qX_pXYt2ZOvN8xfnx1VUjc9R#lY`;1;*juzM`HChmI{p zQMO8`{9Yfgx)zK4R}I)|>YR{>c_Cp_LPPZSiTp_ZD117?^7FzSlrI08wJK7eY}o6L zIFYY7X+jGj2JZDyv>^QhG)m%#2yx=X6-;y?63|yuDS*odX*>eqvog$yjxl_M3vpp{ z!$q{5@yF9O%K2X!<)noZF2n$8Jzq$E@ah<@5&Ne&ksu~DabbiUP`@Z4>G1beks4*3 zKV0S%l1Dzi@3Dj&;?R7Jl0a^(bz(qz?r>;a-ZyRL^*ON=(BCZ7C~rGpy;H0{ z02c;1G~)Po#|!Ddo3||1I9oEwJ5C{a=#8=tjYyF95{Ve&U)PAlyv8X|uC6yVA~DxV zp#W$bqY)F7>7;<@X=+5m^GYM4T^6fR+Jj=8wBA{EjriQE=|T?w(Cs*l7$4J}ViPP& z`zX6c4DhTBBCvJ|8f83Y_j6Lh@v&=^*YD_MBLW(ns8P~{pNEqOD%++J3GtClpq%TL zq*0Eq|2YRHhRD@6jdFWq#74V7zxXLhBd&XjL&)N3<}8~=T*vMv`OUawjhGKwJA};B zdxzOH`b#F?ZBimxlB`kLzX2IweZeLaO8@uRWl}<1ldMtMH-v)&_p=Fwn20_wsr^f` zMrB7r?_(pv1;lxH%Ov8TlcG`CF@N;1At9FlM1M;%i2yxQH7dKDw`T2aIByb>C__#0 zp42C&QZ*_&CY}a1NYQDY33h)2QsY-SMYYD3rv#`Wcgmdg4-z%8qff-YOIV zP48UeVN#+flde(Ok)Qfng+g4HswNS9V7f+SN8wY|NcQV7riVvGd{#ilfOX0+Y@I6NQJfri~JJGb;&#_I663u^Gg#tCJ~# z`#4D83}Sz4Xr=Z1%O<3(teB8NLa$M>Xa%}pUOxx%c`UE5r&VBlov{hrPJsR>?CvHB zoX=qn6inXl+Jux_rD1-g-{c?Scu3D6zKTsk+%?{T^W%|VBYx{>#RR?lrj7dDNf%Pq z{d_AC$Gvo-L&$Y*lxw4PADcnKt)`s>Vvlrzdh^*tod3gP9LRSYLhKZex0Vur_qF4F z)+|^`f_Z!b`Qs1=eg2c3*CmbeMzo!T0iUne1KcMxy- z$I|>BU4YN8wa-EE2f6?S`t2y<_3s@5>#eR!#A}jp?#;uv`{9Ozcx@^7pSl3!d;1dy z6+EY+Xniltq4894()Cs+@$jc}F)qsZIBETVm{0R(nN9Hz?}@r2*-7)39Zvq*+e!H& zB7)ZU@H7&Vkng0w&RR_R=j6sv>~|~BDc%76zi;_8ny1E-QAqoZbkh9Kc2c|#48r%` z0(uMc_R-O_4g>v>*KdPe79gL2F2llRT7cGb|CioVpUb#@0QbSX+R|U6vSVQY_ecI3 zI~sZSB^W0b2A0VhB_B&W@;Hni`}dqiaR>8&`ETtsjaaXN9raSQlk)7K2+|{MoHXy5 zVH#2AfE{@+-AVj(YCg?#Ri{9`vuBP*6!Kt~*KJtE>-)@Nfpx}B2gT>nc^Z`+3+qiz zitmj}i08o%QMV>7(1`pGcGL^+kq+A78AtiR>7WAZ{vwU2Kfn&tbkuvZQ{eu9Ig`jAApS8wU;2+mxr|%Dj`0n;81>4E6#Cqo{lEPZiUUBr z9_*Ok!<_;*4rZoN-YVyydHimPM$GSEM}H1*l8&vIF1)4TzDb2!zZi|mF6*pcH0Fey&njM0b%KiDy@t2s#rzGkI(xoi@z9gEQ@b=xhl%R;h-AvA8{=u* zpl8tUpg-!Rcq{4m*>>WqN~shV(Fvp{j@yN-$1enOJ_@oD|2|`_-K^FSBKxOuw^_y3yow*mfy_1LN;)JY4! zu;KdN9qk|<^059xe<*#Tbc@m{O25dBjNyg!4Q_OS{z1J9<54L4{kHco>H48dDPCGzNtdQr>3vS6lg_wgr}&=9^^;E; z-LJisbXYSR#c!2#od09D*gsd8w9!?;B?m{h*xx>yI?bw>3=Muzd9kll0cA zWU6<;Pf=%FvqdWYZ$8nT@ zu3O0uUowfuuEmoMAMPN1{j*8>tz#T*)K#+KbIeqe?!PsT;@sw-IJ?RG2Rw!wcuj2d z-cZ+~e|{88I^}BztpmI-#$DT3e7|3SM={@=HOcR5#S%YUaZtSnyes`~=PxmurxkzW zMrclq=5cpBZY09}k;j17aRVIqT=6(=*uwqMkAd$MPs^~AypDbge2)4DcpUeMVf?sJ z2z)O$RBr=MV_gBfj&TL^fb}TwIM%g_*MaY`jt8E`xC367&#?fXW1IqyV|)OwE51iv z#XK!}9XIZQ&v73bcp0Aqyp9{7!1s!$QNIDNV|@gCu6P{Z19%Q?U;y~O2u}mA>x$21o|Zf=`A_mXo{s?EE1pIj1$Cw3bH(F|*HyhK{bNFI z2I-X9Hk#)+GbZx}-V+SjZcU}h7 za}8}Y&wG;g>@gT%2 zm3XPVDe(LS;sy8RgHmZf!OKPmI)OIQliO49eSY*bao$cnWIj2RLUGZ}Mt(oUM)A=t z1@}2tccXaTZgJ2&_fA2cY6SBrw{biN+9>|cCsUmr%;Wyeq;^&^&PN99-=WU$PuHzj zkxcJpn&ej(dELOS_W2ZI-3I%D=%27ps8HLB9EW;8NzPIS3|07mA#&1`Ssm1Qs5(|q z_;kk$^0zbp{n-NiMrZNy0v|IJA1}bcA{;ZITNfWM&~{|;@dC-m;^PIPeK>Y-FP~JI zrOGQ+eyOs{y*yK8oGRZ`d8f)c_wrAbiK;wQ<)bPa-OEc=hN|*Ym8U58ijNnHj~C?c zl@uQ@klZdlUeLc*;{NL-#m5U!$Q2(i6dy0hW1QmSh2rA{`6Vm(^@!r*1^V?FrF)gm zRr=RmAFE>ob*!LJ*^7@CK=~IRFBBgy$X_ojK3>4%isIvi;^PJUenau`g4_2OUJZVB zi2Alw1vp*^4GRugppF*QSF9BN_oIc-|NkF;_}5=rz&{nCziL$i_ns`4l8mJo`I}*u zGTbi9SdOtgV+BTa!%E!dzi(lw!tJVz)flTY)?lp3Sc_5pMGX&b*J1QzT$b1 zV*|#9jExu@Gd5xLV)SNg%GivtIin9_3r1hYmW-_!pJ8mx=*QTGu`Ode#`cUI7&|gP z%h-vrGh-LV=NP*(c4O?$_&j3|#-5D582KCN7QyY_jC~mUGWKJ9fw4d1i;M#p2Qt3I zIEZmD;}FK7jKdiH83P!HGX^q_U>wOfig7gK7{;-T;~2*?PGEeQaU$a+#>tF9jIS_G zVGL#rVVuf1jd40-DB}#qR~ctA&SDH>oXr@{7{NG)F_LjE<2=UsjIS{+U|h(!h%t(B zF=I4i(XU#StQXum89tH!Y5`S6hD@6k5;k{I82z~#MOyM>`J?dZNXzf(C=1R*|3kyE z_zx;kvUVv&5$=Ctkv6ON%C%!k7L9p{!Gm`(;HJyzph4AhQ3<^7{`eQ&x5>3;#`l JX^h0c{~vuDWRm~@ diff --git a/ml_peg/calcs/bulk_crystal/diamond_phonons/data/diamond.yaml b/ml_peg/calcs/bulk_crystal/diamond_phonons/data/diamond.yaml deleted file mode 100644 index 808cde227..000000000 --- a/ml_peg/calcs/bulk_crystal/diamond_phonons/data/diamond.yaml +++ /dev/null @@ -1,2135 +0,0 @@ -phonopy: - version: "2.47.1" - frequency_unit_conversion_factor: 15.633302 - symmetry_tolerance: 1.00000e-05 - -space_group: - type: "Fd-3m" - number: 227 - Hall_symbol: "F 4d 2 3 -1d" - -primitive_matrix: -- [ 0.000000000000000, 0.500000000000000, 0.500000000000000 ] -- [ 0.500000000000000, 0.000000000000000, 0.500000000000000 ] -- [ 0.500000000000000, 0.500000000000000, 0.000000000000000 ] - -supercell_matrix: -- [ 4, 0, 0 ] -- [ 0, 4, 0 ] -- [ 0, 0, 4 ] - -primitive_cell: - lattice: - - [ 0.000000000000000, 1.780372500000000, 1.780372500000000 ] # a - - [ 1.780372500000000, 0.000000000000000, 1.780372500000000 ] # b - - [ 1.780372500000000, 1.780372500000000, 0.000000000000000 ] # c - points: - - symbol: C # 1 - coordinates: [ 0.000000000000000, 0.000000000000000, 0.000000000000000 ] - mass: 12.010700 - - symbol: C # 2 - coordinates: [ 0.250000000000000, 0.250000000000000, 0.250000000000000 ] - mass: 12.010700 - reciprocal_lattice: # without 2pi - - [ -0.280840105090367, 0.280840105090367, 0.280840105090367 ] # a* - - [ 0.280840105090367, -0.280840105090367, 0.280840105090367 ] # b* - - [ 0.280840105090367, 0.280840105090367, -0.280840105090367 ] # c* - -unit_cell: - lattice: - - [ 3.560745000000000, 0.000000000000000, 0.000000000000000 ] # a - - [ 0.000000000000000, 3.560745000000000, 0.000000000000000 ] # b - - [ 0.000000000000000, 0.000000000000000, 3.560745000000000 ] # c - points: - - symbol: C # 1 - coordinates: [ 0.000000000000000, 0.000000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 2 - coordinates: [ 0.000000000000000, 0.500000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 3 - coordinates: [ 0.500000000000000, 0.000000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 4 - coordinates: [ 0.500000000000000, 0.500000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 5 - coordinates: [ 0.250000000000000, 0.250000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 5 - - symbol: C # 6 - coordinates: [ 0.250000000000000, 0.750000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 5 - - symbol: C # 7 - coordinates: [ 0.750000000000000, 0.250000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 5 - - symbol: C # 8 - coordinates: [ 0.750000000000000, 0.750000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 5 - -supercell: - lattice: - - [ 14.242979999999999, 0.000000000000000, 0.000000000000000 ] # a - - [ 0.000000000000000, 14.242979999999999, 0.000000000000000 ] # b - - [ 0.000000000000000, 0.000000000000000, 14.242979999999999 ] # c - points: - - symbol: C # 1 - coordinates: [ 0.000000000000000, 0.000000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 2 - coordinates: [ 0.250000000000000, 0.000000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 3 - coordinates: [ 0.500000000000000, 0.000000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 4 - coordinates: [ 0.750000000000000, 0.000000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 5 - coordinates: [ 0.000000000000000, 0.250000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 6 - coordinates: [ 0.250000000000000, 0.250000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 7 - coordinates: [ 0.500000000000000, 0.250000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 8 - coordinates: [ 0.750000000000000, 0.250000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 9 - coordinates: [ 0.000000000000000, 0.500000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 10 - coordinates: [ 0.250000000000000, 0.500000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 11 - coordinates: [ 0.500000000000000, 0.500000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 12 - coordinates: [ 0.750000000000000, 0.500000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 13 - coordinates: [ 0.000000000000000, 0.750000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 14 - coordinates: [ 0.250000000000000, 0.750000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 15 - coordinates: [ 0.500000000000000, 0.750000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 16 - coordinates: [ 0.750000000000000, 0.750000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 17 - coordinates: [ 0.000000000000000, 0.000000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 18 - coordinates: [ 0.250000000000000, 0.000000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 19 - coordinates: [ 0.500000000000000, 0.000000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 20 - coordinates: [ 0.750000000000000, 0.000000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 21 - coordinates: [ 0.000000000000000, 0.250000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 22 - coordinates: [ 0.250000000000000, 0.250000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 23 - coordinates: [ 0.500000000000000, 0.250000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 24 - coordinates: [ 0.750000000000000, 0.250000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 25 - coordinates: [ 0.000000000000000, 0.500000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 26 - coordinates: [ 0.250000000000000, 0.500000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 27 - coordinates: [ 0.500000000000000, 0.500000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 28 - coordinates: [ 0.750000000000000, 0.500000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 29 - coordinates: [ 0.000000000000000, 0.750000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 30 - coordinates: [ 0.250000000000000, 0.750000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 31 - coordinates: [ 0.500000000000000, 0.750000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 32 - coordinates: [ 0.750000000000000, 0.750000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 33 - coordinates: [ 0.000000000000000, 0.000000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 34 - coordinates: [ 0.250000000000000, 0.000000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 35 - coordinates: [ 0.500000000000000, 0.000000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 36 - coordinates: [ 0.750000000000000, 0.000000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 37 - coordinates: [ 0.000000000000000, 0.250000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 38 - coordinates: [ 0.250000000000000, 0.250000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 39 - coordinates: [ 0.500000000000000, 0.250000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 40 - coordinates: [ 0.750000000000000, 0.250000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 41 - coordinates: [ 0.000000000000000, 0.500000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 42 - coordinates: [ 0.250000000000000, 0.500000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 43 - coordinates: [ 0.500000000000000, 0.500000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 44 - coordinates: [ 0.750000000000000, 0.500000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 45 - coordinates: [ 0.000000000000000, 0.750000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 46 - coordinates: [ 0.250000000000000, 0.750000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 47 - coordinates: [ 0.500000000000000, 0.750000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 48 - coordinates: [ 0.750000000000000, 0.750000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 49 - coordinates: [ 0.000000000000000, 0.000000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 50 - coordinates: [ 0.250000000000000, 0.000000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 51 - coordinates: [ 0.500000000000000, 0.000000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 52 - coordinates: [ 0.750000000000000, 0.000000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 53 - coordinates: [ 0.000000000000000, 0.250000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 54 - coordinates: [ 0.250000000000000, 0.250000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 55 - coordinates: [ 0.500000000000000, 0.250000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 56 - coordinates: [ 0.750000000000000, 0.250000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 57 - coordinates: [ 0.000000000000000, 0.500000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 58 - coordinates: [ 0.250000000000000, 0.500000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 59 - coordinates: [ 0.500000000000000, 0.500000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 60 - coordinates: [ 0.750000000000000, 0.500000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 61 - coordinates: [ 0.000000000000000, 0.750000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 62 - coordinates: [ 0.250000000000000, 0.750000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 63 - coordinates: [ 0.500000000000000, 0.750000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 64 - coordinates: [ 0.750000000000000, 0.750000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 65 - coordinates: [ 0.000000000000000, 0.125000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 66 - coordinates: [ 0.250000000000000, 0.125000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 67 - coordinates: [ 0.500000000000000, 0.125000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 68 - coordinates: [ 0.750000000000000, 0.125000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 69 - coordinates: [ 0.000000000000000, 0.375000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 70 - coordinates: [ 0.250000000000000, 0.375000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 71 - coordinates: [ 0.500000000000000, 0.375000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 72 - coordinates: [ 0.750000000000000, 0.375000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 73 - coordinates: [ 0.000000000000000, 0.625000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 74 - coordinates: [ 0.250000000000000, 0.625000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 75 - coordinates: [ 0.500000000000000, 0.625000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 76 - coordinates: [ 0.750000000000000, 0.625000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 77 - coordinates: [ 0.000000000000000, 0.875000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 78 - coordinates: [ 0.250000000000000, 0.875000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 79 - coordinates: [ 0.500000000000000, 0.875000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 80 - coordinates: [ 0.750000000000000, 0.875000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 81 - coordinates: [ 0.000000000000000, 0.125000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 82 - coordinates: [ 0.250000000000000, 0.125000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 83 - coordinates: [ 0.500000000000000, 0.125000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 84 - coordinates: [ 0.750000000000000, 0.125000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 85 - coordinates: [ 0.000000000000000, 0.375000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 86 - coordinates: [ 0.250000000000000, 0.375000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 87 - coordinates: [ 0.500000000000000, 0.375000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 88 - coordinates: [ 0.750000000000000, 0.375000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 89 - coordinates: [ 0.000000000000000, 0.625000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 90 - coordinates: [ 0.250000000000000, 0.625000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 91 - coordinates: [ 0.500000000000000, 0.625000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 92 - coordinates: [ 0.750000000000000, 0.625000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 93 - coordinates: [ 0.000000000000000, 0.875000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 94 - coordinates: [ 0.250000000000000, 0.875000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 95 - coordinates: [ 0.500000000000000, 0.875000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 96 - coordinates: [ 0.750000000000000, 0.875000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 97 - coordinates: [ 0.000000000000000, 0.125000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 98 - coordinates: [ 0.250000000000000, 0.125000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 99 - coordinates: [ 0.500000000000000, 0.125000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 100 - coordinates: [ 0.750000000000000, 0.125000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 101 - coordinates: [ 0.000000000000000, 0.375000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 102 - coordinates: [ 0.250000000000000, 0.375000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 103 - coordinates: [ 0.500000000000000, 0.375000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 104 - coordinates: [ 0.750000000000000, 0.375000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 105 - coordinates: [ 0.000000000000000, 0.625000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 106 - coordinates: [ 0.250000000000000, 0.625000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 107 - coordinates: [ 0.500000000000000, 0.625000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 108 - coordinates: [ 0.750000000000000, 0.625000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 109 - coordinates: [ 0.000000000000000, 0.875000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 110 - coordinates: [ 0.250000000000000, 0.875000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 111 - coordinates: [ 0.500000000000000, 0.875000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 112 - coordinates: [ 0.750000000000000, 0.875000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 113 - coordinates: [ 0.000000000000000, 0.125000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 114 - coordinates: [ 0.250000000000000, 0.125000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 115 - coordinates: [ 0.500000000000000, 0.125000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 116 - coordinates: [ 0.750000000000000, 0.125000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 117 - coordinates: [ 0.000000000000000, 0.375000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 118 - coordinates: [ 0.250000000000000, 0.375000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 119 - coordinates: [ 0.500000000000000, 0.375000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 120 - coordinates: [ 0.750000000000000, 0.375000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 121 - coordinates: [ 0.000000000000000, 0.625000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 122 - coordinates: [ 0.250000000000000, 0.625000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 123 - coordinates: [ 0.500000000000000, 0.625000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 124 - coordinates: [ 0.750000000000000, 0.625000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 125 - coordinates: [ 0.000000000000000, 0.875000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 126 - coordinates: [ 0.250000000000000, 0.875000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 127 - coordinates: [ 0.500000000000000, 0.875000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 128 - coordinates: [ 0.750000000000000, 0.875000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 129 - coordinates: [ 0.125000000000000, 0.000000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 130 - coordinates: [ 0.375000000000000, 0.000000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 131 - coordinates: [ 0.625000000000000, 0.000000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 132 - coordinates: [ 0.875000000000000, 0.000000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 133 - coordinates: [ 0.125000000000000, 0.250000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 134 - coordinates: [ 0.375000000000000, 0.250000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 135 - coordinates: [ 0.625000000000000, 0.250000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 136 - coordinates: [ 0.875000000000000, 0.250000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 137 - coordinates: [ 0.125000000000000, 0.500000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 138 - coordinates: [ 0.375000000000000, 0.500000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 139 - coordinates: [ 0.625000000000000, 0.500000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 140 - coordinates: [ 0.875000000000000, 0.500000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 141 - coordinates: [ 0.125000000000000, 0.750000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 142 - coordinates: [ 0.375000000000000, 0.750000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 143 - coordinates: [ 0.625000000000000, 0.750000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 144 - coordinates: [ 0.875000000000000, 0.750000000000000, 0.125000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 145 - coordinates: [ 0.125000000000000, 0.000000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 146 - coordinates: [ 0.375000000000000, 0.000000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 147 - coordinates: [ 0.625000000000000, 0.000000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 148 - coordinates: [ 0.875000000000000, 0.000000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 149 - coordinates: [ 0.125000000000000, 0.250000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 150 - coordinates: [ 0.375000000000000, 0.250000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 151 - coordinates: [ 0.625000000000000, 0.250000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 152 - coordinates: [ 0.875000000000000, 0.250000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 153 - coordinates: [ 0.125000000000000, 0.500000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 154 - coordinates: [ 0.375000000000000, 0.500000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 155 - coordinates: [ 0.625000000000000, 0.500000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 156 - coordinates: [ 0.875000000000000, 0.500000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 157 - coordinates: [ 0.125000000000000, 0.750000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 158 - coordinates: [ 0.375000000000000, 0.750000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 159 - coordinates: [ 0.625000000000000, 0.750000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 160 - coordinates: [ 0.875000000000000, 0.750000000000000, 0.375000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 161 - coordinates: [ 0.125000000000000, 0.000000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 162 - coordinates: [ 0.375000000000000, 0.000000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 163 - coordinates: [ 0.625000000000000, 0.000000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 164 - coordinates: [ 0.875000000000000, 0.000000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 165 - coordinates: [ 0.125000000000000, 0.250000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 166 - coordinates: [ 0.375000000000000, 0.250000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 167 - coordinates: [ 0.625000000000000, 0.250000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 168 - coordinates: [ 0.875000000000000, 0.250000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 169 - coordinates: [ 0.125000000000000, 0.500000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 170 - coordinates: [ 0.375000000000000, 0.500000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 171 - coordinates: [ 0.625000000000000, 0.500000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 172 - coordinates: [ 0.875000000000000, 0.500000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 173 - coordinates: [ 0.125000000000000, 0.750000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 174 - coordinates: [ 0.375000000000000, 0.750000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 175 - coordinates: [ 0.625000000000000, 0.750000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 176 - coordinates: [ 0.875000000000000, 0.750000000000000, 0.625000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 177 - coordinates: [ 0.125000000000000, 0.000000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 178 - coordinates: [ 0.375000000000000, 0.000000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 179 - coordinates: [ 0.625000000000000, 0.000000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 180 - coordinates: [ 0.875000000000000, 0.000000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 181 - coordinates: [ 0.125000000000000, 0.250000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 182 - coordinates: [ 0.375000000000000, 0.250000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 183 - coordinates: [ 0.625000000000000, 0.250000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 184 - coordinates: [ 0.875000000000000, 0.250000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 185 - coordinates: [ 0.125000000000000, 0.500000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 186 - coordinates: [ 0.375000000000000, 0.500000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 187 - coordinates: [ 0.625000000000000, 0.500000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 188 - coordinates: [ 0.875000000000000, 0.500000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 189 - coordinates: [ 0.125000000000000, 0.750000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 190 - coordinates: [ 0.375000000000000, 0.750000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 191 - coordinates: [ 0.625000000000000, 0.750000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 192 - coordinates: [ 0.875000000000000, 0.750000000000000, 0.875000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 193 - coordinates: [ 0.125000000000000, 0.125000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 194 - coordinates: [ 0.375000000000000, 0.125000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 195 - coordinates: [ 0.625000000000000, 0.125000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 196 - coordinates: [ 0.875000000000000, 0.125000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 197 - coordinates: [ 0.125000000000000, 0.375000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 198 - coordinates: [ 0.375000000000000, 0.375000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 199 - coordinates: [ 0.625000000000000, 0.375000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 200 - coordinates: [ 0.875000000000000, 0.375000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 201 - coordinates: [ 0.125000000000000, 0.625000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 202 - coordinates: [ 0.375000000000000, 0.625000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 203 - coordinates: [ 0.625000000000000, 0.625000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 204 - coordinates: [ 0.875000000000000, 0.625000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 205 - coordinates: [ 0.125000000000000, 0.875000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 206 - coordinates: [ 0.375000000000000, 0.875000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 207 - coordinates: [ 0.625000000000000, 0.875000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 208 - coordinates: [ 0.875000000000000, 0.875000000000000, 0.000000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 209 - coordinates: [ 0.125000000000000, 0.125000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 210 - coordinates: [ 0.375000000000000, 0.125000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 211 - coordinates: [ 0.625000000000000, 0.125000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 212 - coordinates: [ 0.875000000000000, 0.125000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 213 - coordinates: [ 0.125000000000000, 0.375000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 214 - coordinates: [ 0.375000000000000, 0.375000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 215 - coordinates: [ 0.625000000000000, 0.375000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 216 - coordinates: [ 0.875000000000000, 0.375000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 217 - coordinates: [ 0.125000000000000, 0.625000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 218 - coordinates: [ 0.375000000000000, 0.625000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 219 - coordinates: [ 0.625000000000000, 0.625000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 220 - coordinates: [ 0.875000000000000, 0.625000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 221 - coordinates: [ 0.125000000000000, 0.875000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 222 - coordinates: [ 0.375000000000000, 0.875000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 223 - coordinates: [ 0.625000000000000, 0.875000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 224 - coordinates: [ 0.875000000000000, 0.875000000000000, 0.250000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 225 - coordinates: [ 0.125000000000000, 0.125000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 226 - coordinates: [ 0.375000000000000, 0.125000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 227 - coordinates: [ 0.625000000000000, 0.125000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 228 - coordinates: [ 0.875000000000000, 0.125000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 229 - coordinates: [ 0.125000000000000, 0.375000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 230 - coordinates: [ 0.375000000000000, 0.375000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 231 - coordinates: [ 0.625000000000000, 0.375000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 232 - coordinates: [ 0.875000000000000, 0.375000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 233 - coordinates: [ 0.125000000000000, 0.625000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 234 - coordinates: [ 0.375000000000000, 0.625000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 235 - coordinates: [ 0.625000000000000, 0.625000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 236 - coordinates: [ 0.875000000000000, 0.625000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 237 - coordinates: [ 0.125000000000000, 0.875000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 238 - coordinates: [ 0.375000000000000, 0.875000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 239 - coordinates: [ 0.625000000000000, 0.875000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 240 - coordinates: [ 0.875000000000000, 0.875000000000000, 0.500000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 241 - coordinates: [ 0.125000000000000, 0.125000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 242 - coordinates: [ 0.375000000000000, 0.125000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 243 - coordinates: [ 0.625000000000000, 0.125000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 244 - coordinates: [ 0.875000000000000, 0.125000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 245 - coordinates: [ 0.125000000000000, 0.375000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 246 - coordinates: [ 0.375000000000000, 0.375000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 247 - coordinates: [ 0.625000000000000, 0.375000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 248 - coordinates: [ 0.875000000000000, 0.375000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 249 - coordinates: [ 0.125000000000000, 0.625000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 250 - coordinates: [ 0.375000000000000, 0.625000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 251 - coordinates: [ 0.625000000000000, 0.625000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 252 - coordinates: [ 0.875000000000000, 0.625000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 253 - coordinates: [ 0.125000000000000, 0.875000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 254 - coordinates: [ 0.375000000000000, 0.875000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 255 - coordinates: [ 0.625000000000000, 0.875000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 256 - coordinates: [ 0.875000000000000, 0.875000000000000, 0.750000000000000 ] - mass: 12.010700 - reduced_to: 1 - - symbol: C # 257 - coordinates: [ 0.062500000000000, 0.062500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 258 - coordinates: [ 0.312500000000000, 0.062500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 259 - coordinates: [ 0.562500000000000, 0.062500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 260 - coordinates: [ 0.812500000000000, 0.062500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 261 - coordinates: [ 0.062500000000000, 0.312500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 262 - coordinates: [ 0.312500000000000, 0.312500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 263 - coordinates: [ 0.562500000000000, 0.312500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 264 - coordinates: [ 0.812500000000000, 0.312500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 265 - coordinates: [ 0.062500000000000, 0.562500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 266 - coordinates: [ 0.312500000000000, 0.562500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 267 - coordinates: [ 0.562500000000000, 0.562500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 268 - coordinates: [ 0.812500000000000, 0.562500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 269 - coordinates: [ 0.062500000000000, 0.812500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 270 - coordinates: [ 0.312500000000000, 0.812500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 271 - coordinates: [ 0.562500000000000, 0.812500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 272 - coordinates: [ 0.812500000000000, 0.812500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 273 - coordinates: [ 0.062500000000000, 0.062500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 274 - coordinates: [ 0.312500000000000, 0.062500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 275 - coordinates: [ 0.562500000000000, 0.062500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 276 - coordinates: [ 0.812500000000000, 0.062500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 277 - coordinates: [ 0.062500000000000, 0.312500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 278 - coordinates: [ 0.312500000000000, 0.312500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 279 - coordinates: [ 0.562500000000000, 0.312500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 280 - coordinates: [ 0.812500000000000, 0.312500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 281 - coordinates: [ 0.062500000000000, 0.562500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 282 - coordinates: [ 0.312500000000000, 0.562500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 283 - coordinates: [ 0.562500000000000, 0.562500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 284 - coordinates: [ 0.812500000000000, 0.562500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 285 - coordinates: [ 0.062500000000000, 0.812500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 286 - coordinates: [ 0.312500000000000, 0.812500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 287 - coordinates: [ 0.562500000000000, 0.812500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 288 - coordinates: [ 0.812500000000000, 0.812500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 289 - coordinates: [ 0.062500000000000, 0.062500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 290 - coordinates: [ 0.312500000000000, 0.062500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 291 - coordinates: [ 0.562500000000000, 0.062500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 292 - coordinates: [ 0.812500000000000, 0.062500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 293 - coordinates: [ 0.062500000000000, 0.312500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 294 - coordinates: [ 0.312500000000000, 0.312500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 295 - coordinates: [ 0.562500000000000, 0.312500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 296 - coordinates: [ 0.812500000000000, 0.312500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 297 - coordinates: [ 0.062500000000000, 0.562500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 298 - coordinates: [ 0.312500000000000, 0.562500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 299 - coordinates: [ 0.562500000000000, 0.562500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 300 - coordinates: [ 0.812500000000000, 0.562500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 301 - coordinates: [ 0.062500000000000, 0.812500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 302 - coordinates: [ 0.312500000000000, 0.812500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 303 - coordinates: [ 0.562500000000000, 0.812500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 304 - coordinates: [ 0.812500000000000, 0.812500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 305 - coordinates: [ 0.062500000000000, 0.062500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 306 - coordinates: [ 0.312500000000000, 0.062500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 307 - coordinates: [ 0.562500000000000, 0.062500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 308 - coordinates: [ 0.812500000000000, 0.062500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 309 - coordinates: [ 0.062500000000000, 0.312500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 310 - coordinates: [ 0.312500000000000, 0.312500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 311 - coordinates: [ 0.562500000000000, 0.312500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 312 - coordinates: [ 0.812500000000000, 0.312500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 313 - coordinates: [ 0.062500000000000, 0.562500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 314 - coordinates: [ 0.312500000000000, 0.562500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 315 - coordinates: [ 0.562500000000000, 0.562500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 316 - coordinates: [ 0.812500000000000, 0.562500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 317 - coordinates: [ 0.062500000000000, 0.812500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 318 - coordinates: [ 0.312500000000000, 0.812500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 319 - coordinates: [ 0.562500000000000, 0.812500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 320 - coordinates: [ 0.812500000000000, 0.812500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 321 - coordinates: [ 0.062500000000000, 0.187500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 322 - coordinates: [ 0.312500000000000, 0.187500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 323 - coordinates: [ 0.562500000000000, 0.187500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 324 - coordinates: [ 0.812500000000000, 0.187500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 325 - coordinates: [ 0.062500000000000, 0.437500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 326 - coordinates: [ 0.312500000000000, 0.437500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 327 - coordinates: [ 0.562500000000000, 0.437500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 328 - coordinates: [ 0.812500000000000, 0.437500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 329 - coordinates: [ 0.062500000000000, 0.687500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 330 - coordinates: [ 0.312500000000000, 0.687500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 331 - coordinates: [ 0.562500000000000, 0.687500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 332 - coordinates: [ 0.812500000000000, 0.687500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 333 - coordinates: [ 0.062500000000000, 0.937500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 334 - coordinates: [ 0.312500000000000, 0.937500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 335 - coordinates: [ 0.562500000000000, 0.937500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 336 - coordinates: [ 0.812500000000000, 0.937500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 337 - coordinates: [ 0.062500000000000, 0.187500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 338 - coordinates: [ 0.312500000000000, 0.187500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 339 - coordinates: [ 0.562500000000000, 0.187500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 340 - coordinates: [ 0.812500000000000, 0.187500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 341 - coordinates: [ 0.062500000000000, 0.437500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 342 - coordinates: [ 0.312500000000000, 0.437500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 343 - coordinates: [ 0.562500000000000, 0.437500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 344 - coordinates: [ 0.812500000000000, 0.437500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 345 - coordinates: [ 0.062500000000000, 0.687500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 346 - coordinates: [ 0.312500000000000, 0.687500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 347 - coordinates: [ 0.562500000000000, 0.687500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 348 - coordinates: [ 0.812500000000000, 0.687500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 349 - coordinates: [ 0.062500000000000, 0.937500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 350 - coordinates: [ 0.312500000000000, 0.937500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 351 - coordinates: [ 0.562500000000000, 0.937500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 352 - coordinates: [ 0.812500000000000, 0.937500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 353 - coordinates: [ 0.062500000000000, 0.187500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 354 - coordinates: [ 0.312500000000000, 0.187500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 355 - coordinates: [ 0.562500000000000, 0.187500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 356 - coordinates: [ 0.812500000000000, 0.187500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 357 - coordinates: [ 0.062500000000000, 0.437500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 358 - coordinates: [ 0.312500000000000, 0.437500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 359 - coordinates: [ 0.562500000000000, 0.437500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 360 - coordinates: [ 0.812500000000000, 0.437500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 361 - coordinates: [ 0.062500000000000, 0.687500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 362 - coordinates: [ 0.312500000000000, 0.687500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 363 - coordinates: [ 0.562500000000000, 0.687500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 364 - coordinates: [ 0.812500000000000, 0.687500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 365 - coordinates: [ 0.062500000000000, 0.937500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 366 - coordinates: [ 0.312500000000000, 0.937500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 367 - coordinates: [ 0.562500000000000, 0.937500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 368 - coordinates: [ 0.812500000000000, 0.937500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 369 - coordinates: [ 0.062500000000000, 0.187500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 370 - coordinates: [ 0.312500000000000, 0.187500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 371 - coordinates: [ 0.562500000000000, 0.187500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 372 - coordinates: [ 0.812500000000000, 0.187500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 373 - coordinates: [ 0.062500000000000, 0.437500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 374 - coordinates: [ 0.312500000000000, 0.437500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 375 - coordinates: [ 0.562500000000000, 0.437500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 376 - coordinates: [ 0.812500000000000, 0.437500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 377 - coordinates: [ 0.062500000000000, 0.687500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 378 - coordinates: [ 0.312500000000000, 0.687500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 379 - coordinates: [ 0.562500000000000, 0.687500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 380 - coordinates: [ 0.812500000000000, 0.687500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 381 - coordinates: [ 0.062500000000000, 0.937500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 382 - coordinates: [ 0.312500000000000, 0.937500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 383 - coordinates: [ 0.562500000000000, 0.937500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 384 - coordinates: [ 0.812500000000000, 0.937500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 385 - coordinates: [ 0.187500000000000, 0.062500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 386 - coordinates: [ 0.437500000000000, 0.062500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 387 - coordinates: [ 0.687500000000000, 0.062500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 388 - coordinates: [ 0.937500000000000, 0.062500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 389 - coordinates: [ 0.187500000000000, 0.312500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 390 - coordinates: [ 0.437500000000000, 0.312500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 391 - coordinates: [ 0.687500000000000, 0.312500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 392 - coordinates: [ 0.937500000000000, 0.312500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 393 - coordinates: [ 0.187500000000000, 0.562500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 394 - coordinates: [ 0.437500000000000, 0.562500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 395 - coordinates: [ 0.687500000000000, 0.562500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 396 - coordinates: [ 0.937500000000000, 0.562500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 397 - coordinates: [ 0.187500000000000, 0.812500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 398 - coordinates: [ 0.437500000000000, 0.812500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 399 - coordinates: [ 0.687500000000000, 0.812500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 400 - coordinates: [ 0.937500000000000, 0.812500000000000, 0.187500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 401 - coordinates: [ 0.187500000000000, 0.062500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 402 - coordinates: [ 0.437500000000000, 0.062500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 403 - coordinates: [ 0.687500000000000, 0.062500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 404 - coordinates: [ 0.937500000000000, 0.062500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 405 - coordinates: [ 0.187500000000000, 0.312500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 406 - coordinates: [ 0.437500000000000, 0.312500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 407 - coordinates: [ 0.687500000000000, 0.312500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 408 - coordinates: [ 0.937500000000000, 0.312500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 409 - coordinates: [ 0.187500000000000, 0.562500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 410 - coordinates: [ 0.437500000000000, 0.562500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 411 - coordinates: [ 0.687500000000000, 0.562500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 412 - coordinates: [ 0.937500000000000, 0.562500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 413 - coordinates: [ 0.187500000000000, 0.812500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 414 - coordinates: [ 0.437500000000000, 0.812500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 415 - coordinates: [ 0.687500000000000, 0.812500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 416 - coordinates: [ 0.937500000000000, 0.812500000000000, 0.437500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 417 - coordinates: [ 0.187500000000000, 0.062500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 418 - coordinates: [ 0.437500000000000, 0.062500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 419 - coordinates: [ 0.687500000000000, 0.062500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 420 - coordinates: [ 0.937500000000000, 0.062500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 421 - coordinates: [ 0.187500000000000, 0.312500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 422 - coordinates: [ 0.437500000000000, 0.312500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 423 - coordinates: [ 0.687500000000000, 0.312500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 424 - coordinates: [ 0.937500000000000, 0.312500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 425 - coordinates: [ 0.187500000000000, 0.562500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 426 - coordinates: [ 0.437500000000000, 0.562500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 427 - coordinates: [ 0.687500000000000, 0.562500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 428 - coordinates: [ 0.937500000000000, 0.562500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 429 - coordinates: [ 0.187500000000000, 0.812500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 430 - coordinates: [ 0.437500000000000, 0.812500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 431 - coordinates: [ 0.687500000000000, 0.812500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 432 - coordinates: [ 0.937500000000000, 0.812500000000000, 0.687500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 433 - coordinates: [ 0.187500000000000, 0.062500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 434 - coordinates: [ 0.437500000000000, 0.062500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 435 - coordinates: [ 0.687500000000000, 0.062500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 436 - coordinates: [ 0.937500000000000, 0.062500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 437 - coordinates: [ 0.187500000000000, 0.312500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 438 - coordinates: [ 0.437500000000000, 0.312500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 439 - coordinates: [ 0.687500000000000, 0.312500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 440 - coordinates: [ 0.937500000000000, 0.312500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 441 - coordinates: [ 0.187500000000000, 0.562500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 442 - coordinates: [ 0.437500000000000, 0.562500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 443 - coordinates: [ 0.687500000000000, 0.562500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 444 - coordinates: [ 0.937500000000000, 0.562500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 445 - coordinates: [ 0.187500000000000, 0.812500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 446 - coordinates: [ 0.437500000000000, 0.812500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 447 - coordinates: [ 0.687500000000000, 0.812500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 448 - coordinates: [ 0.937500000000000, 0.812500000000000, 0.937500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 449 - coordinates: [ 0.187500000000000, 0.187500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 450 - coordinates: [ 0.437500000000000, 0.187500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 451 - coordinates: [ 0.687500000000000, 0.187500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 452 - coordinates: [ 0.937500000000000, 0.187500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 453 - coordinates: [ 0.187500000000000, 0.437500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 454 - coordinates: [ 0.437500000000000, 0.437500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 455 - coordinates: [ 0.687500000000000, 0.437500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 456 - coordinates: [ 0.937500000000000, 0.437500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 457 - coordinates: [ 0.187500000000000, 0.687500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 458 - coordinates: [ 0.437500000000000, 0.687500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 459 - coordinates: [ 0.687500000000000, 0.687500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 460 - coordinates: [ 0.937500000000000, 0.687500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 461 - coordinates: [ 0.187500000000000, 0.937500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 462 - coordinates: [ 0.437500000000000, 0.937500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 463 - coordinates: [ 0.687500000000000, 0.937500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 464 - coordinates: [ 0.937500000000000, 0.937500000000000, 0.062500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 465 - coordinates: [ 0.187500000000000, 0.187500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 466 - coordinates: [ 0.437500000000000, 0.187500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 467 - coordinates: [ 0.687500000000000, 0.187500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 468 - coordinates: [ 0.937500000000000, 0.187500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 469 - coordinates: [ 0.187500000000000, 0.437500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 470 - coordinates: [ 0.437500000000000, 0.437500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 471 - coordinates: [ 0.687500000000000, 0.437500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 472 - coordinates: [ 0.937500000000000, 0.437500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 473 - coordinates: [ 0.187500000000000, 0.687500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 474 - coordinates: [ 0.437500000000000, 0.687500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 475 - coordinates: [ 0.687500000000000, 0.687500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 476 - coordinates: [ 0.937500000000000, 0.687500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 477 - coordinates: [ 0.187500000000000, 0.937500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 478 - coordinates: [ 0.437500000000000, 0.937500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 479 - coordinates: [ 0.687500000000000, 0.937500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 480 - coordinates: [ 0.937500000000000, 0.937500000000000, 0.312500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 481 - coordinates: [ 0.187500000000000, 0.187500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 482 - coordinates: [ 0.437500000000000, 0.187500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 483 - coordinates: [ 0.687500000000000, 0.187500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 484 - coordinates: [ 0.937500000000000, 0.187500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 485 - coordinates: [ 0.187500000000000, 0.437500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 486 - coordinates: [ 0.437500000000000, 0.437500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 487 - coordinates: [ 0.687500000000000, 0.437500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 488 - coordinates: [ 0.937500000000000, 0.437500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 489 - coordinates: [ 0.187500000000000, 0.687500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 490 - coordinates: [ 0.437500000000000, 0.687500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 491 - coordinates: [ 0.687500000000000, 0.687500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 492 - coordinates: [ 0.937500000000000, 0.687500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 493 - coordinates: [ 0.187500000000000, 0.937500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 494 - coordinates: [ 0.437500000000000, 0.937500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 495 - coordinates: [ 0.687500000000000, 0.937500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 496 - coordinates: [ 0.937500000000000, 0.937500000000000, 0.562500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 497 - coordinates: [ 0.187500000000000, 0.187500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 498 - coordinates: [ 0.437500000000000, 0.187500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 499 - coordinates: [ 0.687500000000000, 0.187500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 500 - coordinates: [ 0.937500000000000, 0.187500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 501 - coordinates: [ 0.187500000000000, 0.437500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 502 - coordinates: [ 0.437500000000000, 0.437500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 503 - coordinates: [ 0.687500000000000, 0.437500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 504 - coordinates: [ 0.937500000000000, 0.437500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 505 - coordinates: [ 0.187500000000000, 0.687500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 506 - coordinates: [ 0.437500000000000, 0.687500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 507 - coordinates: [ 0.687500000000000, 0.687500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 508 - coordinates: [ 0.937500000000000, 0.687500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 509 - coordinates: [ 0.187500000000000, 0.937500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 510 - coordinates: [ 0.437500000000000, 0.937500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 511 - coordinates: [ 0.687500000000000, 0.937500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - - symbol: C # 512 - coordinates: [ 0.937500000000000, 0.937500000000000, 0.812500000000000 ] - mass: 12.010700 - reduced_to: 257 - -displacements: -- atom: 1 - displacement: - [ 0.0100000000000000, 0.0000000000000000, 0.0000000000000000 ] From e9403f9748af144c786422dd902d00bbb47242a6 Mon Sep 17 00:00:00 2001 From: Mariia Radova <56935802+7radians@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:18:05 +0000 Subject: [PATCH 4/6] Update docs --- docs/source/user_guide/benchmarks/bulk_crystal.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/user_guide/benchmarks/bulk_crystal.rst b/docs/source/user_guide/benchmarks/bulk_crystal.rst index ceda6f9b9..60a7ceda8 100644 --- a/docs/source/user_guide/benchmarks/bulk_crystal.rst +++ b/docs/source/user_guide/benchmarks/bulk_crystal.rst @@ -279,6 +279,6 @@ Reference data: Computational environment ------------------------- -Ti64 phonon calculations were run locally as a single process on CPU on an +Ti64 phonon calculations were run as a single process on CPU on an x86_64 machine (11th Gen Intel(R) Core(TM) i5-1145G7; 4 cores / 8 threads). No explicit parallel execution (MPI or multiprocessing) was used in the benchmark driver. From 9136948a40fcea51f303c8dac92bde1d849d1ffa Mon Sep 17 00:00:00 2001 From: Mariia Radova <56935802+7radians@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:21:00 +0000 Subject: [PATCH 5/6] update docs --- docs/source/user_guide/benchmarks/bulk_crystal.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/user_guide/benchmarks/bulk_crystal.rst b/docs/source/user_guide/benchmarks/bulk_crystal.rst index 60a7ceda8..34cd81960 100644 --- a/docs/source/user_guide/benchmarks/bulk_crystal.rst +++ b/docs/source/user_guide/benchmarks/bulk_crystal.rst @@ -255,7 +255,7 @@ Full details on the data and benchmark: * Allen, C. S. & Bartók, A. P. Multi-phase dataset for Ti and Ti-6Al-4V. Preprint at https://arxiv.org/abs/2501.06116 (2025). -* Radova, M., Stark, W. G., Allen, C. S. et al. +* Radova, M., Stark, W. G., Allen, C. S., Maurer, R.J. & Bartók, A. P. Fine-tuning foundation models of materials interatomic potentials with frozen transfer learning. npj Comput Mater 11, 237 (2025). From 8bbc961e0ce39b5c79711f4167ae496059bffca7 Mon Sep 17 00:00:00 2001 From: Mariia Radova <56935802+7radians@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:23:03 +0000 Subject: [PATCH 6/6] update docs --- docs/source/user_guide/benchmarks/bulk_crystal.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/user_guide/benchmarks/bulk_crystal.rst b/docs/source/user_guide/benchmarks/bulk_crystal.rst index 34cd81960..e8b9dc0c0 100644 --- a/docs/source/user_guide/benchmarks/bulk_crystal.rst +++ b/docs/source/user_guide/benchmarks/bulk_crystal.rst @@ -230,7 +230,7 @@ Metrics (2000 points spanning 0–2000 K) and interpolated to the ML temperatures. The absolute difference between ML and reference free energy at 0 K is divided by the number of atoms and averaged across thermodynamics-enabled cases. Weights are taken directly from CASTEP; - no explicit renormalization is applied. + no explicit renormalisation is applied. 5. ΔF (2000 K) mean