Spaces:
Sleeping
Sleeping
File size: 3,155 Bytes
bc2a98e e5bcce8 bc2a98e e5bcce8 bc2a98e e5bcce8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | """CIF -> Sine Coulomb Matrix eigenvalues descriptor computation."""
from pathlib import Path
import numpy as np
from pymatgen.core import Structure
try:
from matminer.featurizers.structure.matrix import SineCoulombMatrix
except Exception:
SineCoulombMatrix = None
def compute_scm_eigenvalues(
cif_path: str,
target_dim: int,
) -> dict:
"""Compute Sine Coulomb Matrix eigenvalues from a CIF file.
Args:
cif_path: absolute path to a .cif file.
target_dim: desired output dimension (520 for benzene, 584 for toluene).
Returns:
{
"eigenvalues": np.ndarray of shape (target_dim,),
"raw_dim": int,
"padded": bool,
"truncated": bool,
"applicability_warning": str | None,
}
Raises:
FileNotFoundError: if cif_path does not exist.
ValueError: if CIF cannot be parsed into a valid Structure.
"""
path = Path(cif_path)
if not path.exists():
raise FileNotFoundError(f"CIF file not found: {cif_path}")
structure = Structure.from_file(str(path))
if structure.num_sites == 0:
raise ValueError(f"CIF parsed but contains 0 sites: {cif_path}")
if SineCoulombMatrix is not None:
scm = SineCoulombMatrix(flatten=False)
scm.fit([structure])
matrix = scm.featurize(structure)[0]
else:
matrix = _fallback_scm_matrix(structure)
eigenvalues = np.sort(np.linalg.eigvalsh(matrix))[::-1]
raw_dim = len(eigenvalues)
padded = False
truncated = False
warning = None
if raw_dim < target_dim:
eigenvalues = np.pad(eigenvalues, (0, target_dim - raw_dim), mode="constant")
padded = True
warning = (
f"SCM eigenvalue dimension ({raw_dim}) < target ({target_dim}). "
f"Zero-padded {target_dim - raw_dim} values. "
f"Prediction may be outside the model's applicability domain."
)
elif raw_dim > target_dim:
eigenvalues = eigenvalues[:target_dim]
truncated = True
warning = (
f"SCM eigenvalue dimension ({raw_dim}) > target ({target_dim}). "
f"Truncated {raw_dim - target_dim} smallest eigenvalues. "
f"Prediction may be outside the model's applicability domain."
)
return {
"eigenvalues": eigenvalues,
"raw_dim": raw_dim,
"padded": padded,
"truncated": truncated,
"applicability_warning": warning,
}
def _fallback_scm_matrix(structure: Structure) -> np.ndarray:
"""Small deterministic SCM-like matrix used when matminer is unavailable."""
size = structure.num_sites
matrix = np.zeros((size, size), dtype=float)
for i, site_i in enumerate(structure):
z_i = float(site_i.specie.Z)
matrix[i, i] = 0.5 * z_i ** 2.4
for j in range(i + 1, size):
site_j = structure[j]
z_j = float(site_j.specie.Z)
distance = max(float(structure.get_distance(i, j)), 1e-6)
value = z_i * z_j / distance
matrix[i, j] = value
matrix[j, i] = value
return matrix
|