CrysText / evaluation.py
andreamena's picture
Create evaluation.py
71741d2 verified
Raw History Blame Contribute Delete
4.12 kB
import os
import numpy as np
import itertools
import shutil
from collections import Counter
from pymatgen.core import Structure
from smact import element_dictionary, metals, neutral_ratios
from smact.screening import pauling_test
from pymatgen.analysis.structure_matcher import StructureMatcher
from tqdm import tqdm
# --- FIX: Use relative paths for Hugging Face ---
input_folder = "generation_cifs"
output_folder = "valid_generation_cifs"
unique_folder = "unique_structures"
novel_folder = "novel_structures"
# ------------------------------------------------
def get_safe_space_group_number(structure):
try:
spg_info = structure.get_space_group_info()
return spg_info[1] if spg_info else None
except Exception:
return None
def structure_validity(structure, cutoff=0.5):
dist_mat = structure.distance_matrix
dist_mat = dist_mat + np.diag(np.ones(dist_mat.shape[0]) * (cutoff + 10.))
if dist_mat.min() < cutoff or structure.volume < 0.1:
return False
spg_number = get_safe_space_group_number(structure)
if spg_number is None:
return False
return True
def smact_validity(atom_types, use_pauling_test=True, include_alloys=True):
elem_counter = Counter(atom_types)
elems = [(elem, elem_counter[elem]) for elem in sorted(elem_counter.keys())]
comp, elem_counts = list(zip(*elems))
elem_counts = np.array(elem_counts)
elem_counts = elem_counts / np.gcd.reduce(elem_counts)
count = tuple(elem_counts.astype("int").tolist())
elem_symbols = tuple(comp)
try:
space = element_dictionary(elem_symbols)
except:
return False # Handle elements not in SMACT
smact_elems = [e[1] for e in space.items()]
electronegs = [e.pauling_eneg for e in smact_elems]
ox_combos = [e.oxidation_states for e in smact_elems]
if len(set(elem_symbols)) == 1:
return True
if include_alloys:
is_metal_list = [elem_s in metals for elem_s in elem_symbols]
if all(is_metal_list):
return True
threshold = np.max(count)
oxn = 1
for oxc in ox_combos:
oxn *= len(oxc)
if oxn > 1e7:
return False
for ox_states in itertools.product(*ox_combos):
stoichs = [(c,) for c in count]
cn_e, cn_r = neutral_ratios(ox_states, stoichs=stoichs, threshold=threshold)
if cn_e:
if use_pauling_test:
electroneg_OK = pauling_test(ox_states, electronegs)
else:
electroneg_OK = True
if electroneg_OK:
return True
return False
def get_atom_types_from_structure(structure):
return [str(element) for element in structure.species]
# Create directories
os.makedirs(output_folder, exist_ok=True)
os.makedirs(unique_folder, exist_ok=True)
os.makedirs(novel_folder, exist_ok=True)
total = 0
structure_valid_count = 0
composition_valid_count = 0
both_valid_count = 0
if os.path.exists(input_folder):
for filename in os.listdir(input_folder):
if filename.endswith(".cif"):
total += 1
full_path = os.path.join(input_folder, filename)
try:
structure = Structure.from_file(full_path)
atom_types = get_atom_types_from_structure(structure)
struct_valid = structure_validity(structure)
comp_valid = smact_validity(atom_types)
if struct_valid:
structure_valid_count += 1
if comp_valid:
composition_valid_count += 1
if struct_valid and comp_valid:
both_valid_count += 1
shutil.copy(full_path, os.path.join(output_folder, filename))
except Exception as e:
print(f"Error processing {filename}: {e}")
print(f"\n=== Validity Summary ===")
print(f"Total CIFs checked: {total}")
print(f"Structural validity passed: {structure_valid_count}")
print(f"Compositional validity passed: {composition_valid_count}")
print(f"Both valid (saved): {both_valid_count}")