Spaces:
Sleeping
Sleeping
| """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 | |