├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── data ├── convert_ph3params_extxyz.py ├── generate_DFT_kappa.py ├── join_dft_kappas.py ├── kappas_phononDB_PBE_NAC.json.gz ├── kappas_phononDB_PBE_noNAC.json.gz ├── mp_id-phonondb_id.txt └── phononDB-PBE-structures.extxyz ├── k_srme ├── __init__.py ├── benchmark.py ├── conductivity.py ├── data.py ├── phono3py_utils.py ├── relax.py └── utils.py ├── models ├── .DS_Store ├── CHGNet │ ├── 1_test_srme.py │ ├── 2024-11-09-CHGNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05 │ │ ├── .DS_Store │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2_evaluate.py │ └── metrics.txt ├── GRACE-1L-OAM │ ├── 1_test_srme.py │ ├── 2025-02-05-GRACE-1L-OAM_2Feb25-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05 │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2_evaluate.py │ ├── metrics.txt │ └── run_eval_parallel.sh ├── GRACE-2L-OAM │ ├── 1_test_srme.py │ ├── 2025-02-05-GRACE_2L_OAM_28Jan25-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05 │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2_evaluate.py │ ├── metrics.txt │ └── run_eval_parallel.sh ├── GRACE-2L-r6-11Nov2024 │ ├── 1_test_srme.py │ ├── 2024-11-20-MP_GRACE_2L_r6_11Nov2024-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05 │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2_evaluate.py │ ├── metrics.txt │ └── run_eval_parallel.sh ├── M3GNet │ ├── 1_test_srme.py │ ├── 2024-11-09-M3GNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05 │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2_evaluate.py │ └── metrics.txt ├── MACE-L │ ├── 1_test_srme.py │ ├── 2024-11-09-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05 │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2_evaluate.py │ └── metrics.txt ├── MACE-L2 │ ├── 1_test_srme.py │ ├── 2024-11-09-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05 │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2_evaluate.py │ └── metrics.txt ├── MACE-M │ ├── 1_test_srme.py │ ├── 2024-11-09-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05 │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2_evaluate.py │ └── metrics.txt ├── MACE-MPA-0 │ ├── 1_test_srme.py │ ├── 2024-11-25-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05 │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2_evaluate.py │ └── metrics.txt ├── MACE-agnesi-medium │ ├── .DS_Store │ ├── 1_test_srme.py │ ├── 2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05 │ │ ├── force_sets_1653939-000.json.gz │ │ ├── force_sets_1653939-001.json.gz │ │ ├── force_sets_1653939-002.json.gz │ │ ├── force_sets_1653939-003.json.gz │ │ ├── force_sets_1653939-004.json.gz │ │ ├── force_sets_1653939-005.json.gz │ │ ├── force_sets_1653939-006.json.gz │ │ ├── force_sets_1653939-007.json.gz │ │ ├── force_sets_1653939-008.json.gz │ │ ├── force_sets_1653939-009.json.gz │ │ ├── force_sets_1653939-010.json.gz │ │ ├── force_sets_1653939-011.json.gz │ │ ├── force_sets_1653939-012.json.gz │ │ ├── force_sets_1653939-013.json.gz │ │ ├── force_sets_1653939-014.json.gz │ │ ├── force_sets_1653939-015.json.gz │ │ ├── force_sets_1653939-016.json.gz │ │ ├── force_sets_1653939-017.json.gz │ │ ├── force_sets_1653939-018.json.gz │ │ ├── force_sets_1653939-019.json.gz │ │ ├── force_sets_1653939-020.json.gz │ │ ├── force_sets_1653939-021.json.gz │ │ ├── force_sets_1653939-022.json.gz │ │ ├── force_sets_1653939-023.json.gz │ │ ├── force_sets_1653939-024.json.gz │ │ ├── force_sets_1653939-025.json.gz │ │ ├── force_sets_1653939-026.json.gz │ │ ├── force_sets_1653939-027.json.gz │ │ ├── force_sets_1653939-028.json.gz │ │ ├── force_sets_1653939-029.json.gz │ │ ├── force_sets_1653939-030.json.gz │ │ ├── force_sets_1653939-031.json.gz │ │ ├── force_sets_1653939-032.json.gz │ │ ├── force_sets_1653939-033.json.gz │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2_evaluate.py │ └── metrics.txt ├── MatterSim-V1 │ ├── 1_test_srme.py │ ├── 2024-12-09-MatterSim-V1-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05-1M │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2024-12-09-MatterSim-V1-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05-5M │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2_evaluate.py │ ├── metrics-MatterSim-1M.txt │ └── metrics-MatterSim-5M.txt ├── ORBv2-MPTraj │ ├── 1_test_srme.py │ ├── 2024-11-09-ORB-v2-MPtraj-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05 │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2_evaluate.py │ └── metrics.txt ├── ORBv2 │ ├── 1_test_srme.py │ ├── 2024-11-09-ORB-v2-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05 │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2_evaluate.py │ └── metrics.txt ├── SevenNet │ ├── 1_test_srme.py │ ├── 2024-11-09-SevenNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05 │ │ ├── k_srme.json.gz │ │ └── run_params.json │ ├── 2_evaluate.py │ └── metrics.txt └── eqV2-full-S │ ├── .DS_Store │ ├── 1_test_srme.py │ ├── 2024-11-09-eqV2-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05 │ ├── k_srme.json.gz │ └── run_params.json │ ├── 2_evaluate.py │ └── metrics.txt ├── pyproject.toml └── scripts ├── 1_test_srme.py └── 2_evaluate.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files__pycache__/ 2 | *.py[cod] 3 | # C extensions*.so 4 | # Distribution / packagingbin/ 5 | build/ 6 | develop-eggs/ 7 | dist/ 8 | eggs/ 9 | lib/ 10 | lib64/ 11 | parts/ 12 | sdist/ 13 | var/ 14 | *.egg-info/ 15 | .installed.cfg 16 | *.egg 17 | # Installer logspip-log.txt 18 | pip-delete-this-directory.txt 19 | 20 | *.slurm 21 | slurm* 22 | 23 | all_kappas* 24 | old_kappas* 25 | benchmark_kappas* 26 | *.log 27 | 28 | conductivity_*-???.json.gz -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_stages: [pre-commit] 2 | 3 | default_install_hook_types: [pre-commit, commit-msg] 4 | 5 | repos: 6 | - repo: https://github.com/astral-sh/ruff-pre-commit 7 | rev: v0.7.3 8 | hooks: 9 | - id: ruff 10 | args: [--fix] 11 | types_or: [python, jupyter] 12 | - id: ruff-format 13 | types_or: [python, jupyter] 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # κ_SRME: heat-conductivity benchmark test for foundational machine-learning potentials based on the Wigner Transport Equation 2 | 3 | κ_SRME employs foundation Machine Learning Interatomic Potentials and phono3py to determine the wigner thermal conductivity in crystals and compare them to DFT reference data. 4 | 5 | # Install 6 | Clone repository: 7 | ``` 8 | git clone https://github.com/MPA2suite/k_SRME.git 9 | ``` 10 | Then install in editable mode: 11 | ``` 12 | pip install -e . 13 | ``` 14 | 15 | 16 | phonopy and phono3py dependencies are optional to allow model output analysis. 17 | These pre-requisites need to be installed seperately or added to PYTHONPATH. 18 | See https://phonopy.github.io/phono3py/install.html for installation instructions of phono3py. 19 | 20 | We have tested the package with phono3py versions from 3.1.1 to 3.7.0 and Python versions 3.10 and higher. 21 | 22 | 23 | # Usage 24 | The example scripts showcase a sample workflow for testing a MACE potential and comparing the thermal conductivity with DFT calculations for a collection of different materials. The scripts may be modified easily to use any foundation Machine Learning Interatomic Potentials. 25 | 26 | Example scripts are found in the `scripts` folder. Model results and scripts are found in the `models` folder. 27 | 28 | To obtain conductivity results, you need to run a CPU job, as phono3py does not support GPUs. The `1_test_srme.py` script calculates the displaced force sets and the thermal conductivity for each material. We recommend setting OMP_NUM_THREADS to 4 to 8, to get speedup in both the forceand conductivity calculations. The script also supports job arrays outputting one file per array task, which are collected in the evaluation script. For the 103 materials, the wurtzite structures require the longest runtime. Therefore to minimize the runtime, we recommend a maximum of 33 array tasks. 29 | 30 | The `2_evaluate.py` script evaluates the predictions, collecting the array task files and printing the results both to the terminal and to a file. The `k_srme.json.gz` output file contain additional information about the model run, which can be read as a pandas DataFrame for further analysis. 31 | 32 | 33 | 34 | 35 | # How to cite 36 | 37 | ``` 38 | @misc{póta2024thermalconductivitypredictionsfoundation, 39 | title={Thermal Conductivity Predictions with Foundation Atomistic Models}, 40 | author={Balázs Póta and Paramvir Ahlawat and Gábor Csányi and Michele Simoncelli}, 41 | year={2024}, 42 | eprint={2408.00755}, 43 | archivePrefix={arXiv}, 44 | primaryClass={cond-mat.mtrl-sci}, 45 | url={https://arxiv.org/abs/2408.00755}, 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /data/convert_ph3params_extxyz.py: -------------------------------------------------------------------------------- 1 | import tarfile 2 | import warnings 3 | import os 4 | import autoWTE 5 | from ase.io import write 6 | from tqdm import tqdm 7 | from phono3py.cui.load import load 8 | import numpy as np 9 | import re 10 | 11 | import pandas as pd 12 | from ase.spacegroup import get_spacegroup 13 | 14 | 15 | symm_no_q_mesh_map = { 16 | 225: [19, 19, 19], # rocksalt 17 | 186: [19, 19, 15], # wurtzite 18 | 216: [19, 19, 19], # zincblende 19 | } 20 | 21 | symm_no_name_map = {225: "rocksalt", 186: "wurtzite", 216: "zincblende"} 22 | 23 | tar_dir = ( 24 | "/mnt/scratch2/q13camb_scratch/bp443/foundational_TC/release/phonondb-PBE-data/" 25 | ) 26 | tar_list = [ 27 | "phono3py_params_RS.tar.xz", 28 | "phono3py_params_WZ.tar.xz", 29 | "phono3py_params_ZB.tar.xz", 30 | ] 31 | 32 | output_file = autoWTE.BENCHMARK_STRUCTURES_FILE 33 | 34 | try: 35 | os.remove(output_file) 36 | except OSError: 37 | pass 38 | 39 | atoms_list = [] 40 | 41 | mp_phonondb_id_file = "mp_id-phonondb_id.txt" 42 | df_mp_id = pd.read_csv( 43 | mp_phonondb_id_file, sep=" ", header=None, names=["name", "symm.no", "mp-id"] 44 | ) 45 | element_pattern = r"([A-Z][a-z]?)" 46 | df_mp_id["element_list"] = df_mp_id["name"].apply( 47 | lambda x: sorted(re.findall(element_pattern, x)) 48 | ) 49 | df_mp_id["found"] = False 50 | 51 | 52 | def get_mat_info(atoms): 53 | list_chemicals = sorted(np.unique(atoms.get_chemical_symbols()).tolist()) 54 | 55 | # Regular expression to match elements with optional counts 56 | compound = df_mp_id[ 57 | df_mp_id["element_list"].apply(lambda x: np.all(x == list_chemicals)) 58 | ] 59 | 60 | structure = compound[ 61 | compound["symm.no"].apply(lambda x: int(x)) 62 | == int(get_spacegroup(atoms, symprec=1e-5).no) 63 | ] 64 | 65 | if len(structure) == 0: 66 | print(atoms, compound) 67 | return df_mp_id.iloc[0].to_dict() 68 | else: 69 | if len(structure) > 1: 70 | print(structure) 71 | df_mp_id.loc[structure.index, "found"] = True 72 | return structure.iloc[0].to_dict() 73 | 74 | 75 | warnings.filterwarnings("ignore", category=DeprecationWarning, module="spglib") 76 | 77 | for tar_filename in tqdm(tar_list, desc="Tar files"): 78 | with tarfile.open(tar_dir + tar_filename, "r:xz") as tar: 79 | # Read a specific file from the archive 80 | all_members = tar.getmembers() 81 | 82 | # Filter for directories 83 | files = [member for member in all_members if member.isfile()] 84 | pbar = tqdm( 85 | files, leave=False, desc=f"Reading yaml files in tar file {tar_filename}" 86 | ) 87 | for file_to_read in pbar: 88 | if file_to_read.name.split(".")[-1] != "yaml": 89 | continue 90 | 91 | file_content = tar.extractfile(file_to_read.name) 92 | 93 | if file_content is not None: 94 | ph3 = load(file_content, produce_fc=False, symmetrize_fc=False) 95 | atoms = autoWTE.phono3py2aseatoms(ph3) 96 | 97 | info = get_mat_info(atoms) 98 | 99 | pbar.set_postfix_str(info["name"]) 100 | 101 | atoms.info["mp_id"] = info["mp-id"] 102 | 103 | atoms.info["q_mesh"] = symm_no_q_mesh_map[ph3.symmetry._dataset.number] 104 | atoms.info["name"] = info["name"] 105 | atoms.info["symm.no"] = info["symm.no"] 106 | 107 | write(output_file, atoms, format="extxyz", append=True) 108 | 109 | else: 110 | print(f"Could not read {file_to_read}") 111 | -------------------------------------------------------------------------------- /data/generate_DFT_kappa.py: -------------------------------------------------------------------------------- 1 | import tarfile 2 | import warnings 3 | import os 4 | import sys 5 | import autoWTE 6 | from tqdm import tqdm 7 | import pandas as pd 8 | import numpy as np 9 | import gc 10 | from copy import deepcopy 11 | from phono3py.cui.load import load 12 | import re 13 | 14 | from ase.spacegroup import get_spacegroup 15 | 16 | symm_no_q_mesh_map = { 17 | 225: [19, 19, 19], # rocksalt 18 | 186: [19, 19, 15], # wurtzite 19 | 216: [19, 19, 19], # zincblende 20 | } 21 | 22 | symm_no_name_map = {225: "rocksalt", 186: "wurtzite", 216: "zincblende"} 23 | 24 | pbar = True 25 | 26 | tar_dir = ( 27 | "/mnt/scratch2/q13camb_scratch/bp443/foundational_TC/release/phonondb-PBE-data/" 28 | ) 29 | tar_list = [ 30 | "phono3py_params_RS.tar.xz", 31 | "phono3py_params_WZ.tar.xz", 32 | "phono3py_params_ZB.tar.xz", 33 | ] 34 | 35 | conductivity_output = "all" 36 | 37 | atoms_list = [] 38 | 39 | dict_dft_nac_kappa = {} 40 | dict_dft_nonac_kappa = {} 41 | 42 | warnings.filterwarnings("ignore", category=DeprecationWarning, module="spglib") 43 | 44 | slurm_array_task_count = int(os.getenv("SLURM_ARRAY_TASK_COUNT", "1")) 45 | slurm_array_task_id = int(os.getenv("SLURM_ARRAY_TASK_ID", "0")) 46 | slurm_array_job_id = os.getenv("SLURM_ARRAY_JOB_ID", "debug") 47 | slurm_array_task_min = int(os.getenv("SLURM_ARRAY_TASK_MIN", "0")) 48 | 49 | nac_outpath = f"{conductivity_output}_kappas_phonondb_PBE_NAC_{slurm_array_job_id}/{slurm_array_task_id}.json.gz" 50 | nonac_outpath = f"{conductivity_output}_kappas_phonondb_PBE_noNAC_{slurm_array_job_id}/{slurm_array_task_id}.json.gz" 51 | 52 | os.makedirs(os.path.dirname(nac_outpath), exist_ok=True) 53 | os.makedirs(os.path.dirname(nonac_outpath), exist_ok=True) 54 | 55 | print(f"Output to {nac_outpath} and {nonac_outpath}") 56 | 57 | count = 0 58 | count_list = list(range(1, 104)) 59 | if slurm_array_job_id == "debug": 60 | count_list = count_list[:2] 61 | elif slurm_array_task_count > 1: 62 | count_list = count_list[ 63 | slurm_array_task_id - slurm_array_task_min :: slurm_array_task_count 64 | ] 65 | 66 | 67 | mp_phonondb_id_file = "mp_id-phonondb_id.txt" 68 | df_mp_id = pd.read_csv( 69 | mp_phonondb_id_file, sep=" ", header=None, names=["name", "symm.no", "mp-id"] 70 | ) 71 | element_pattern = r"([A-Z][a-z]?)" 72 | df_mp_id["element_list"] = df_mp_id["name"].apply( 73 | lambda x: sorted(re.findall(element_pattern, x)) 74 | ) 75 | 76 | 77 | def get_mat_info(atoms): 78 | list_chemicals = sorted(np.unique(atoms.get_chemical_symbols()).tolist()) 79 | 80 | # Regular expression to match elements with optional counts 81 | compound = df_mp_id[ 82 | df_mp_id["element_list"].apply(lambda x: np.all(x == list_chemicals)) 83 | ] 84 | 85 | structure = compound[ 86 | compound["symm.no"].apply(lambda x: int(x)) 87 | == int(get_spacegroup(atoms, symprec=1e-5).no) 88 | ] 89 | 90 | return structure.iloc[0].to_dict() 91 | 92 | 93 | for tar_filename in tqdm(tar_list, desc="Tar files", disable=not pbar): 94 | with tarfile.open(tar_dir + tar_filename, "r:xz") as tar: 95 | # Read a specific file from the archive 96 | all_members = tar.getmembers() 97 | 98 | # Filter for directories 99 | files = [member for member in all_members if member.isfile()] 100 | 101 | for file_to_read in tqdm( 102 | files, 103 | leave=False, 104 | desc=f"Reading yaml files in tar file {tar_filename}", 105 | disable=not pbar, 106 | ): 107 | if file_to_read.name.split(".")[-1] != "yaml": 108 | continue 109 | else: 110 | count += 1 111 | if count not in count_list: 112 | continue 113 | 114 | file_content = tar.extractfile(file_to_read.name) 115 | 116 | if file_content is not None: 117 | ph3 = load(file_content, produce_fc=True, symmetrize_fc=True) 118 | 119 | atoms = autoWTE.phono3py2aseatoms(ph3) 120 | 121 | mat_info = {} 122 | 123 | info = get_mat_info(atoms) 124 | 125 | # mat_info["mp_id"] = info["mp-id"] 126 | mat_info["q_mesh"] = symm_no_q_mesh_map[ph3.symmetry._dataset.number] 127 | mat_info["name"] = info["name"] 128 | mat_info["symm.no"] = info["symm.no"] 129 | 130 | mat_id = info["mp-id"] 131 | 132 | print(mat_id, "\nNAC:\n") 133 | 134 | ph3, kappa_nac = autoWTE.calculate_conductivity_phono3py( 135 | ph3, 136 | q_mesh=symm_no_q_mesh_map[ph3.symmetry._dataset.number], 137 | temperatures=autoWTE.BENCHMARK_TEMPERATURES, 138 | log=True, 139 | dict_output=conductivity_output, 140 | nac_method="Wang", 141 | ) 142 | 143 | ph3.nac_params = None 144 | 145 | ph3, kappa_nonac = autoWTE.calculate_conductivity_phono3py( 146 | ph3, 147 | q_mesh=symm_no_q_mesh_map[ph3.symmetry._dataset.number], 148 | temperatures=autoWTE.BENCHMARK_TEMPERATURES, 149 | log=True, 150 | dict_output=conductivity_output, 151 | ) 152 | 153 | print( 154 | kappa_nac["kappa_TOT_RTA"], 155 | "\nnoNAC:\n", 156 | kappa_nonac["kappa_TOT_RTA"], 157 | ) 158 | sys.stdout.flush() 159 | 160 | kappa_nac.update(mat_info) 161 | kappa_nonac.update(mat_info) 162 | 163 | dict_dft_nac_kappa[mat_id] = deepcopy(kappa_nac) 164 | dict_dft_nonac_kappa[mat_id] = deepcopy(kappa_nonac) 165 | 166 | del ph3 167 | del atoms 168 | del kappa_nonac 169 | del kappa_nac 170 | gc.collect() 171 | 172 | else: 173 | print(f"Could not read {file_to_read}") 174 | 175 | 176 | df_dft_nac_kappa = pd.DataFrame(dict_dft_nac_kappa).T 177 | df_dft_nonac_kappa = pd.DataFrame(dict_dft_nonac_kappa).T 178 | 179 | 180 | df_dft_nac_kappa.index.name = autoWTE.BENCHMARK_ID 181 | df_dft_nonac_kappa.index.name = autoWTE.BENCHMARK_ID 182 | 183 | df_dft_nac_kappa.reset_index().to_json(nac_outpath) 184 | df_dft_nonac_kappa.reset_index().to_json(nonac_outpath) 185 | -------------------------------------------------------------------------------- /data/join_dft_kappas.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | from autoWTE import ( 3 | calculate_kappa_ave, 4 | glob2df, 5 | BENCHMARK_ID, 6 | calculate_mode_kappa_TOT, 7 | BENCHMARK_DFT_NAC_REF, 8 | BENCHMARK_DFT_NONAC_REF, 9 | ) 10 | import os 11 | import gc 12 | 13 | module_dir = os.path.dirname(__file__) 14 | 15 | conductivity_output = "all" 16 | benchmark_drop_list = [ 17 | "mode_kappa_TOT", 18 | "kappa_C", 19 | "mode_kappa_C", 20 | "mode_kappa_P_RTA", 21 | "kappa_TOT_RTA", 22 | "kappa_P_RTA", 23 | ] 24 | 25 | nac_pattern = f"{conductivity_output}_kappas_phonondb_PBE_NAC_1060107/*.json.gz" 26 | nonac_pattern = f"{conductivity_output}_kappas_phonondb_PBE_noNAC_1060107/*.json.gz" 27 | 28 | nac_outpath = f"{conductivity_output}_kappas_phonondb_PBE_NAC.json.gz" 29 | nonac_outpath = f"{conductivity_output}_kappas_phonondb_PBE_noNAC.json.gz" 30 | 31 | if conductivity_output == "benchmark": 32 | nac_outpath = BENCHMARK_DFT_NAC_REF 33 | nonac_outpath = BENCHMARK_DFT_NONAC_REF 34 | 35 | 36 | nac_files = sorted(glob(f"{module_dir}/{nac_pattern}")) 37 | nonac_files = sorted(glob(f"{module_dir}/{nonac_pattern}")) 38 | 39 | df_nac = glob2df(nac_pattern).set_index(BENCHMARK_ID) 40 | 41 | 42 | df_nac["kappa_TOT_ave"] = df_nac["kappa_TOT_RTA"].apply(calculate_kappa_ave) 43 | df_nac["mode_kappa_TOT"] = df_nac.apply(calculate_mode_kappa_TOT, axis=1) 44 | df_nac["mode_kappa_TOT_ave"] = df_nac["mode_kappa_TOT"].apply(calculate_kappa_ave) 45 | 46 | if conductivity_output == "benchmark": 47 | df_nac = df_nac.drop(benchmark_drop_list, axis=1) 48 | 49 | df_nac.reset_index().to_json(nac_outpath) 50 | 51 | del df_nac 52 | gc.collect() 53 | 54 | # join noNAC files 55 | df_nonac = glob2df(nonac_pattern).set_index(BENCHMARK_ID) 56 | 57 | df_nonac["kappa_TOT_ave"] = df_nonac["kappa_TOT_RTA"].apply(calculate_kappa_ave) 58 | df_nonac["mode_kappa_TOT"] = df_nonac.apply(calculate_mode_kappa_TOT, axis=1) 59 | df_nonac["mode_kappa_TOT_ave"] = df_nonac["mode_kappa_TOT"].apply(calculate_kappa_ave) 60 | 61 | if conductivity_output == "benchmark": 62 | df_nonac = df_nonac.drop(benchmark_drop_list, axis=1) 63 | 64 | df_nonac.reset_index().to_json(nonac_outpath) 65 | -------------------------------------------------------------------------------- /data/kappas_phononDB_PBE_NAC.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/data/kappas_phononDB_PBE_NAC.json.gz -------------------------------------------------------------------------------- /data/kappas_phononDB_PBE_noNAC.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/data/kappas_phononDB_PBE_noNAC.json.gz -------------------------------------------------------------------------------- /data/mp_id-phonondb_id.txt: -------------------------------------------------------------------------------- 1 | RbI 225 mp-22903 2 | InP 186 mp-966800 3 | KCl 225 mp-23193 4 | SrO 225 mp-2472 5 | BN 216 mp-1639 6 | AgCl 225 mp-22922 7 | BeS 216 mp-422 8 | BaSe 225 mp-1253 9 | KI 225 mp-22898 10 | CuH 186 mp-24093 11 | BeO 216 mp-1778 12 | AlP 186 mp-8880 13 | CaSe 225 mp-1415 14 | BAs 216 mp-10044 15 | GaN 186 mp-804 16 | AgI 216 mp-22925 17 | CdS 216 mp-2469 18 | NaI 225 mp-23268 19 | LiBr 225 mp-23259 20 | ZnO 186 mp-2133 21 | NaBr 225 mp-22916 22 | InP 216 mp-20351 23 | GaAs 186 mp-8883 24 | ZnSe 186 mp-380 25 | CaO 225 mp-2605 26 | AlAs 216 mp-2172 27 | CsF 225 mp-1784 28 | InSb 186 mp-1007661 29 | BaO 225 mp-1342 30 | InAs 216 mp-20305 31 | GaAs 216 mp-2534 32 | BeTe 186 mp-1183441 33 | CaS 225 mp-1672 34 | GaSb 186 mp-1018059 35 | RbCl 225 mp-23295 36 | CuCl 186 mp-1184046 37 | LiF 225 mp-1138 38 | BaS 225 mp-1500 39 | CdTe 186 mp-12779 40 | CdO 225 mp-1132 41 | InAs 186 mp-1007652 42 | LiH 225 mp-23703 43 | ZnS 186 mp-561286 44 | CuBr 216 mp-22913 45 | GaP 216 mp-2490 46 | PbSe 225 mp-2201 47 | BN 186 mp-2653 48 | MgO 225 mp-1265 49 | AgI 186 mp-580941 50 | CdTe 216 mp-406 51 | InN 216 mp-20411 52 | AgBr 225 mp-23231 53 | GaP 186 mp-8882 54 | KBr 225 mp-23251 55 | PbTe 225 mp-19717 56 | MgTe 216 mp-13033 57 | CdSe 186 mp-1070 58 | ZnTe 186 mp-8884 59 | AlN 186 mp-661 60 | MgTe 186 mp-1039 61 | CdSe 216 mp-2691 62 | BeSe 186 no-mp-3 63 | LiI 225 mp-22899 64 | CuCl 216 mp-22914 65 | GaN 216 mp-830 66 | PbS 225 mp-21276 67 | InN 186 mp-22205 68 | BP 186 mp-1008559 69 | CaTe 225 mp-1519 70 | BeSe 216 mp-1541 71 | GaSb 216 mp-1156 72 | InSb 216 mp-20012 73 | CuI 216 mp-22895 74 | RbH 225 mp-24721 75 | NaCl 225 mp-22862 76 | BP 216 mp-1479 77 | AlSb 186 mp-1018100 78 | LiCl 225 mp-22905 79 | KF 225 mp-463 80 | AlSb 216 mp-2624 81 | BeO 186 mp-2542 82 | CuI 186 mp-569346 83 | KH 225 mp-24084 84 | AlP 216 mp-1550 85 | ZnTe 216 mp-2176 86 | CdS 186 mp-672 87 | RbF 225 mp-11718 88 | NaF 225 mp-682 89 | SiC 186 mp-7631 90 | BeS 186 no-mp-4 91 | AlAs 186 mp-8881 92 | CuBr 186 no-mp-1 93 | ZnO 216 mp-1986 94 | RbBr 225 mp-22867 95 | SiC 216 mp-8062 96 | ZnSe 216 mp-1190 97 | BAs 186 mp-984718 98 | ZnS 216 mp-10695 99 | CuH 216 no-mp-2 100 | NaH 225 mp-23870 101 | BaTe 225 mp-1000 102 | BeTe 216 mp-252 103 | AlN 216 mp-1700 -------------------------------------------------------------------------------- /k_srme/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from k_srme.benchmark import ( 4 | calculate_mode_kappa_TOT, 5 | calculate_kappa_ave, 6 | calculate_SRME, 7 | process_benchmark_descriptors, 8 | get_metrics, 9 | ) 10 | from k_srme.data import glob2df 11 | from k_srme.relax import two_stage_relax, NO_TILT_MASK 12 | 13 | # TODO: create separete phonopy_utils, such that code does not depend on phono3py 14 | 15 | from k_srme.utils import ( 16 | check_imaginary_freqs, 17 | aseatoms2str, 18 | str2aseatoms, 19 | log_message, 20 | ) 21 | 22 | PKG_NAME = "k-srme" 23 | __version__ = "1.0.0" 24 | 25 | PKG_DIR = os.path.dirname(__file__) 26 | # repo root directory if editable install, TODO: else the package dir 27 | ROOT = os.path.dirname(PKG_DIR) 28 | DATA_DIR = f"{ROOT}/data" # directory to store default data 29 | DATA_DIR = f"{DATA_DIR}" 30 | STRUCTURES_FILE = "phononDB-PBE-structures.extxyz" 31 | STRUCTURES = f"{DATA_DIR}/{STRUCTURES_FILE}" 32 | 33 | DFT_NAC_REF_FILE = "kappas_phononDB_PBE_NAC.json.gz" 34 | DFT_NONAC_REF_FILE = "kappas_phononDB_PBE_noNAC.json.gz" 35 | DFT_NAC_REF = f"{DATA_DIR}/{DFT_NAC_REF_FILE}" 36 | DFT_NONAC_REF = f"{DATA_DIR}/{DFT_NONAC_REF_FILE}" 37 | 38 | ### 39 | pkg_is_editable = True 40 | 41 | 42 | ###### 43 | TEMPERATURES = [300] 44 | ID = "mp_id" 45 | 46 | 47 | __all__ = [ 48 | "calculate_mode_kappa_TOT", 49 | "calculate_kappa_ave", 50 | "calculate_SRME", 51 | "process_benchmark_descriptors", 52 | "get_metrics", 53 | "glob2df", 54 | "two_stage_relax", 55 | "check_imaginary_freqs", 56 | "aseatoms2str", 57 | "str2aseatoms", 58 | "log_message", 59 | "log_symmetry", 60 | "get_spacegroup_number", 61 | "NO_TILT_MASK", 62 | ] 63 | -------------------------------------------------------------------------------- /k_srme/benchmark.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import warnings 3 | from typing import Any 4 | 5 | import numpy as np 6 | import pandas as pd 7 | 8 | 9 | DEFAULT_LIST2NP_COLS = [ 10 | "max_stress", 11 | "kappa_TOT_RTA", 12 | "kappa_P_RTA", 13 | "kappa_C", 14 | "weights", 15 | "q_points", 16 | "frequencies", 17 | "mode_kappa_TOT", 18 | "mode_kappa_TOT_ave", 19 | "kappa_TOT_ave", 20 | ] 21 | 22 | 23 | def fill_na_in_list(lst: list, y: Any) -> np.ndarray: 24 | return np.asarray([y if pd.isna(x) else x for x in lst]) 25 | 26 | 27 | def process_benchmark_descriptors( 28 | df_mlp_filtered: pd.DataFrame, 29 | df_dft_results: pd.DataFrame, 30 | ) -> pd.DataFrame: 31 | # df_mlp_filtered = df_mlp_filtered.map(np.asarray) 32 | # df_dft_results = df_dft_results.map(np.asarray) 33 | 34 | mlp_list2np_cols = [col for col in DEFAULT_LIST2NP_COLS if col in df_mlp_filtered] 35 | df_mlp_filtered[mlp_list2np_cols] = df_mlp_filtered[mlp_list2np_cols].map( 36 | np.asarray 37 | ) 38 | 39 | dft_list2np_cols = [col for col in DEFAULT_LIST2NP_COLS if col in df_dft_results] 40 | df_dft_results[dft_list2np_cols] = df_dft_results[dft_list2np_cols].map(np.asarray) 41 | 42 | # Remove precomputed columns 43 | columns_to_remove = ["SRD", "SRE", "SRME", "DFT_kappa_TOT_ave"] 44 | if any([col in df_mlp_filtered for col in columns_to_remove]): 45 | df_mlp_filtered = df_mlp_filtered.drop( 46 | columns=[col for col in columns_to_remove if col in df_mlp_filtered.columns] 47 | ) 48 | 49 | if "kappa_TOT_ave" not in df_mlp_filtered: 50 | df_mlp_filtered["kappa_TOT_ave"] = df_mlp_filtered["kappa_TOT_RTA"].apply( 51 | calculate_kappa_ave 52 | ) 53 | if "mode_kappa_TOT_ave" not in df_mlp_filtered: 54 | df_mlp_filtered["mode_kappa_TOT_ave"] = df_mlp_filtered["mode_kappa_TOT"].apply( 55 | calculate_kappa_ave 56 | ) 57 | 58 | df_mlp_filtered["SRD"] = ( 59 | 2 60 | * (df_mlp_filtered["kappa_TOT_ave"] - df_dft_results["kappa_TOT_ave"]) 61 | / (df_mlp_filtered["kappa_TOT_ave"] + df_dft_results["kappa_TOT_ave"]) 62 | ) 63 | 64 | # turn temperature list to the first temperature (300K) TODO: allow multiple temperatures to be tested 65 | df_mlp_filtered["SRD"] = df_mlp_filtered["SRD"].apply( 66 | lambda x: x[0] if not isinstance(x, float) else x 67 | ) 68 | 69 | # We substitute NaN values with 0 predicted conductivity, yielding -2 for SRD 70 | df_mlp_filtered["SRD"] = df_mlp_filtered["SRD"].fillna(-2) 71 | 72 | df_mlp_filtered["SRE"] = df_mlp_filtered["SRD"].abs() 73 | 74 | df_mlp_filtered["SRME"] = calculate_SRME_dataframes(df_mlp_filtered, df_dft_results) 75 | 76 | df_mlp_filtered["DFT_kappa_TOT_ave"] = df_dft_results["kappa_TOT_ave"] 77 | 78 | columns_to_remove = ["mode_kappa_TOT"] 79 | df_mlp_filtered = df_mlp_filtered.drop( 80 | columns=[col for col in columns_to_remove if col in df_mlp_filtered] 81 | ) 82 | 83 | # TODO: Add column reason for SRME = 2 84 | 85 | # TODO: round to 4-5 decimals 86 | 87 | return df_mlp_filtered 88 | 89 | 90 | def get_metrics(df_mlp_filtered: pd.DataFrame) -> tuple[float, float, float, float]: 91 | mSRE = df_mlp_filtered["SRE"].mean() 92 | rmseSRE = ((df_mlp_filtered["SRE"] - mSRE) ** 2).mean() ** 0.5 93 | 94 | mSRME = df_mlp_filtered["SRME"].mean() 95 | rmseSRME = ((df_mlp_filtered["SRME"] - mSRME) ** 2).mean() ** 0.5 96 | 97 | return mSRE, mSRME, rmseSRE, rmseSRME 98 | 99 | 100 | def get_success_metrics(df_mlp): 101 | df_mlp_reduced = df_mlp[df_mlp["SRME"] != 2.0] 102 | mSRE = df_mlp_reduced["SRE"].mean() 103 | mSRME = df_mlp_reduced["SRME"].mean() 104 | return mSRE, mSRME 105 | 106 | 107 | def calculate_kappa_ave(kappa: np.ndarray) -> float | np.ndarray: 108 | if np.any(pd.isna(kappa)): 109 | return np.nan 110 | _kappa = np.asarray(kappa) 111 | 112 | try: 113 | kappa_ave = _kappa[..., :3].mean(axis=-1) 114 | except Exception as e: 115 | warnings.warn(f"Failed to calculate kappa_ave: {e!r}") 116 | warnings.warn(traceback.format_exc()) 117 | return np.nan 118 | 119 | return kappa_ave 120 | 121 | 122 | def calculate_mode_kappa_TOT( 123 | mode_kappa_P_RTA: np.ndarray, mode_kappa_C: np.ndarray, heat_capacity: np.ndarray 124 | ) -> np.ndarray: 125 | with warnings.catch_warnings(): 126 | warnings.simplefilter("ignore", RuntimeWarning) 127 | mode_kappa_C_per_mode = 2 * ( 128 | (mode_kappa_C * heat_capacity[:, :, :, np.newaxis, np.newaxis]) 129 | / ( 130 | heat_capacity[:, :, :, np.newaxis, np.newaxis] 131 | + heat_capacity[:, :, np.newaxis, :, np.newaxis] 132 | ) 133 | ).sum(axis=2) 134 | 135 | mode_kappa_C_per_mode[np.isnan(mode_kappa_C_per_mode)] = 0 136 | 137 | mode_kappa_TOT = mode_kappa_C_per_mode + mode_kappa_P_RTA 138 | 139 | return mode_kappa_TOT 140 | 141 | 142 | def calculate_SRME_dataframes( 143 | df_mlp: pd.DataFrame, df_dft: pd.DataFrame 144 | ) -> list[float]: 145 | srme_list = [] 146 | for idx, row_mlp in df_mlp.iterrows(): 147 | row_dft = df_dft.loc[idx] 148 | try: 149 | if row_mlp.get("imaginary_freqs"): 150 | if row_mlp["imaginary_freqs"] in ["True", True]: 151 | srme_list.append(2) 152 | continue 153 | if "relaxed_space_group_number" in row_mlp: 154 | if "initial_space_group_number" in row_mlp: 155 | if ( 156 | row_mlp["relaxed_space_group_number"] 157 | != row_mlp["initial_space_group_number"] 158 | ): 159 | srme_list.append(2) 160 | continue 161 | elif "symm.no" in row_dft: 162 | if row_mlp["relaxed_space_group_number"] != row_dft["symm.no"]: 163 | srme_list.append(2) 164 | continue 165 | result = calculate_SRME(row_mlp, row_dft) 166 | srme_list.append(result[0]) # append the first temperature SRME 167 | 168 | # Idea: Multiple temperature tests. 169 | except Exception as e: 170 | warnings.warn(f"Failed to calculate SRME for {idx}: {e!r}") 171 | warnings.warn(traceback.format_exc()) 172 | srme_list.append(2) 173 | 174 | return srme_list 175 | 176 | 177 | def calculate_SRME(kappas_mlp: pd.Series, kappas_dft: pd.Series) -> list[float]: 178 | if np.all(pd.isna(kappas_mlp["kappa_TOT_ave"])): 179 | return [2] 180 | if np.any(pd.isna(kappas_mlp["kappa_TOT_RTA"])): 181 | return [2] # np.nan 182 | if np.any(pd.isna(kappas_mlp["weights"])): 183 | return [2] # np.nan 184 | if np.any(pd.isna(kappas_dft["kappa_TOT_ave"])): 185 | return [2] # np.nan 186 | 187 | if "mode_kappa_TOT_ave" not in kappas_mlp: 188 | if "mode_kappa_TOT" in kappas_mlp: 189 | mlp_mode_kappa_TOT_ave = calculate_kappa_ave(kappas_mlp["mode_kappa_TOT"]) 190 | else: 191 | mlp_mode_kappa_TOT_ave = calculate_kappa_ave( 192 | calculate_mode_kappa_TOT(kappas_mlp) 193 | ) 194 | else: 195 | mlp_mode_kappa_TOT_ave = np.asarray(kappas_mlp["mode_kappa_TOT_ave"]) 196 | 197 | if "mode_kappa_TOT_ave" not in kappas_dft: 198 | if "mode_kappa_TOT" in kappas_dft: 199 | dft_mode_kappa_TOT_ave = calculate_kappa_ave(kappas_dft["mode_kappa_TOT"]) 200 | else: 201 | dft_mode_kappa_TOT_ave = calculate_kappa_ave( 202 | calculate_mode_kappa_TOT(kappas_dft) 203 | ) 204 | else: 205 | dft_mode_kappa_TOT_ave = np.asarray(kappas_dft["mode_kappa_TOT_ave"]) 206 | 207 | # calculating microscopic error for all temperatures 208 | microscopic_error = ( 209 | np.abs( 210 | mlp_mode_kappa_TOT_ave - dft_mode_kappa_TOT_ave # reduce ndim by 1 211 | ).sum(axis=tuple(range(1, mlp_mode_kappa_TOT_ave.ndim))) # summing axes 212 | / kappas_mlp["weights"].sum() 213 | ) 214 | 215 | SRME = ( 216 | 2 217 | * microscopic_error 218 | / (kappas_mlp["kappa_TOT_ave"] + kappas_dft["kappa_TOT_ave"]) 219 | ) 220 | 221 | return SRME 222 | -------------------------------------------------------------------------------- /k_srme/conductivity.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from copy import deepcopy 3 | from pathlib import Path 4 | from typing import Any 5 | 6 | import numpy as np 7 | from ase import Atoms 8 | from ase.calculators.calculator import Calculator 9 | from phono3py.api_phono3py import Phono3py 10 | from tqdm import tqdm 11 | 12 | from k_srme import TEMPERATURES 13 | from k_srme.benchmark import calculate_mode_kappa_TOT 14 | from k_srme.phono3py_utils import aseatoms2phono3py, get_chemical_formula 15 | from k_srme.utils import MODE_KAPPA_THRESHOLD, log_message 16 | 17 | KAPPA_OUTPUT_NAME_MAP = { 18 | "weights": "grid_weights", 19 | "heat_capacity": "mode_heat_capacities", 20 | } 21 | 22 | CONDUCTIVITY_SIGMAS_LIST = [ 23 | "kappa", 24 | "mode_kappa", 25 | "kappa_TOT_RTA", 26 | "kappa_P_RTA", 27 | "kappa_C", 28 | "mode_kappa_P_RTA", 29 | "mode_kappa_C", 30 | "gamma_isotope", 31 | ] 32 | 33 | 34 | def calculate_fc2_set( 35 | ph3: Phono3py, 36 | calculator: Calculator, 37 | log: bool = True, 38 | pbar_kwargs: dict[str, Any] = {}, 39 | ) -> np.ndarray: 40 | # calculate FC2 force set 41 | 42 | log_message(f"Computing FC2 force set in {get_chemical_formula(ph3)}.", output=log) 43 | 44 | forces = [] 45 | nat = len(ph3.phonon_supercell) 46 | 47 | for sc in tqdm( 48 | ph3.phonon_supercells_with_displacements, 49 | desc=f"FC2 calculation: {get_chemical_formula(ph3)}", 50 | **pbar_kwargs, 51 | ): 52 | if sc is not None: 53 | atoms = Atoms(sc.symbols, cell=sc.cell, positions=sc.positions, pbc=True) 54 | atoms.calc = calculator 55 | f = atoms.get_forces() 56 | else: 57 | f = np.zeros((nat, 3)) 58 | forces.append(f) 59 | 60 | # append forces 61 | force_set = np.array(forces) 62 | ph3.phonon_forces = force_set 63 | return force_set 64 | 65 | 66 | def calculate_fc3_set( 67 | ph3: Phono3py, 68 | calculator: Calculator, 69 | log: bool = True, 70 | pbar_kwargs: dict[str, Any] = {}, 71 | ) -> np.ndarray: 72 | # calculate FC3 force set 73 | 74 | log_message(f"Computing FC3 force set in {get_chemical_formula(ph3)}.", output=log) 75 | 76 | forces = [] 77 | nat = len(ph3.supercell) 78 | 79 | for sc in tqdm( 80 | ph3.supercells_with_displacements, 81 | desc=f"FC3 calculation: {get_chemical_formula(ph3)}", 82 | **pbar_kwargs, 83 | ): 84 | if sc is not None: 85 | atoms = Atoms(sc.symbols, cell=sc.cell, positions=sc.positions, pbc=True) 86 | atoms.calc = calculator 87 | f = atoms.get_forces() 88 | else: 89 | f = np.zeros((nat, 3)) 90 | forces.append(f) 91 | 92 | # append forces 93 | force_set = np.array(forces) 94 | ph3.forces = np.array(forces) 95 | return force_set 96 | 97 | 98 | def init_phono3py( 99 | atoms: Atoms, 100 | log: str | Path | bool = True, 101 | symprec: float = 1e-5, 102 | displacement_distance: float = 0.03, 103 | **kwargs: Any, 104 | ) -> tuple[Phono3py, list[Any], list[Any]]: 105 | """Calculate fc2 and fc3 force lists from phono3py. 106 | 107 | Args: 108 | 109 | 110 | Raises: 111 | 112 | 113 | Returns: 114 | 115 | """ 116 | if not log: 117 | log_level = 0 118 | elif log is not None: 119 | log_level = 1 120 | 121 | formula = atoms.get_chemical_formula(mode="metal") 122 | for key in ("fc2_supercell", "fc3_supercell", "q_mesh"): 123 | if key not in atoms.info: 124 | raise ValueError( 125 | f'{formula} "{key}" was not found in atoms.info when calculating force sets.' 126 | ) 127 | 128 | # Initialise Phono3py object 129 | ph3 = aseatoms2phono3py( 130 | atoms, 131 | fc2_supercell=atoms.info["fc2_supercell"], 132 | fc3_supercell=atoms.info["fc3_supercell"], 133 | primitive_matrix="auto", 134 | symprec=symprec, 135 | log_level=log_level, 136 | **kwargs, 137 | ) 138 | 139 | ph3.mesh_numbers = atoms.info["q_mesh"] 140 | 141 | ph3.generate_displacements(distance=displacement_distance) 142 | 143 | return ph3 144 | 145 | 146 | def get_fc2_and_freqs( 147 | ph3: Phono3py, 148 | calculator: Calculator | None = None, 149 | log: str | Path | bool = True, 150 | pbar_kwargs: dict[str, Any] = {"leave": False}, 151 | ) -> tuple[Phono3py, np.ndarray, np.ndarray]: 152 | if ph3.mesh_numbers is None: 153 | raise ValueError( 154 | '"mesh_number" was not found in phono3py object and was not provided as an argument when calculating phonons from phono3py object.' 155 | ) 156 | 157 | if calculator is None: 158 | raise ValueError( 159 | f'{get_chemical_formula(ph3)} "calculator" was provided when calculating fc2 force sets.' 160 | ) 161 | 162 | fc2_set = calculate_fc2_set(ph3, calculator, log=log, pbar_kwargs=pbar_kwargs) 163 | 164 | ph3.produce_fc2(symmetrize_fc2=True) 165 | ph3.init_phph_interaction(symmetrize_fc3q=False) 166 | ph3.run_phonon_solver() 167 | 168 | freqs, eigvecs, grid = ph3.get_phonon_data() 169 | 170 | return ph3, fc2_set, freqs 171 | 172 | 173 | def get_fc3( 174 | ph3: Phono3py, 175 | calculator: Calculator | None = None, 176 | log: str | Path | bool = True, 177 | pbar_kwargs: dict[str, Any] = {"leave": False}, 178 | ) -> tuple[Phono3py, np.ndarray]: 179 | if calculator is None: 180 | raise ValueError( 181 | f'{get_chemical_formula(ph3)} "calculator" was provided when calculating fc3 force sets.' 182 | ) 183 | 184 | fc3_set = calculate_fc3_set(ph3, calculator, log=log, pbar_kwargs=pbar_kwargs) 185 | 186 | ph3.produce_fc3(symmetrize_fc3r=True) 187 | 188 | return ph3, fc3_set 189 | 190 | 191 | def load_force_sets( 192 | ph3: Phono3py, fc2_set: np.ndarray, fc3_set: np.ndarray 193 | ) -> Phono3py: 194 | ph3.phonon_forces = fc2_set 195 | ph3.forces = fc3_set 196 | ph3.produce_fc2(symmetrize_fc2=True) 197 | ph3.produce_fc3(symmetrize_fc3r=True) 198 | 199 | return ph3 200 | 201 | 202 | def calculate_conductivity( 203 | ph3: Phono3py, 204 | temperatures: np.ndarray = TEMPERATURES, 205 | log: str | Path | bool | None = None, 206 | **kwargs: Any, 207 | ) -> tuple[Phono3py, dict[str, np.ndarray]]: 208 | if not log: 209 | ph3._log_level = 0 210 | elif log is not None: 211 | ph3._log_level = 1 212 | 213 | ph3.init_phph_interaction(symmetrize_fc3q=False) 214 | 215 | ph3.run_thermal_conductivity( 216 | temperatures=temperatures, 217 | is_isotope=True, 218 | conductivity_type="wigner", 219 | boundary_mfp=1e6, 220 | **kwargs, 221 | ) 222 | 223 | cond = ph3.thermal_conductivity 224 | 225 | kappa_dict = {} 226 | 227 | try: 228 | kappa_dict["kappa_TOT_RTA"] = deepcopy(cond.kappa_TOT_RTA[0]) 229 | kappa_dict["kappa_P_RTA"] = deepcopy(cond.kappa_P_RTA[0]) 230 | kappa_dict["kappa_C"] = deepcopy(cond.kappa_C[0]) 231 | kappa_dict["weights"] = deepcopy(cond.grid_weights) 232 | kappa_dict["q_points"] = deepcopy(cond.qpoints) 233 | # kappa_dict["frequencies"] = deepcopy(cond.frequencies) 234 | except AttributeError as exc: 235 | warnings.warn(f"Phono3py conductivity does not have attribute: {exc}") 236 | 237 | try: 238 | mode_kappa_TOT = calculate_mode_kappa_TOT( 239 | deepcopy(cond.mode_kappa_P_RTA[0]), 240 | deepcopy(cond.mode_kappa_C[0]), 241 | deepcopy(cond.mode_heat_capacities), 242 | ) 243 | except AttributeError as exc: 244 | warnings.warn( 245 | f"Calculate mode kappa tot failed in {get_chemical_formula(ph3)}: {exc}" 246 | ) 247 | 248 | kappa_dict["mode_kappa_TOT"] = mode_kappa_TOT 249 | 250 | sum_mode_kappa_TOT = mode_kappa_TOT.sum( 251 | axis=tuple(range(1, mode_kappa_TOT.ndim - 1)) 252 | ) / np.sum(kappa_dict["weights"]) 253 | 254 | if np.all((sum_mode_kappa_TOT - kappa_dict["kappa_P_RTA"]) <= MODE_KAPPA_THRESHOLD): 255 | warnings.warn( 256 | f"Total mode kappa does not sum to total kappa. mode_kappa_TOT sum : {sum_mode_kappa_TOT}, kappa_TOT_RTA : {kappa_dict['kappa_P_RTA']}" 257 | ) 258 | 259 | return ph3, kappa_dict 260 | -------------------------------------------------------------------------------- /k_srme/data.py: -------------------------------------------------------------------------------- 1 | import os 2 | from collections.abc import Callable 3 | from glob import glob 4 | from pathlib import Path 5 | from typing import Any 6 | 7 | import pandas as pd 8 | import requests 9 | from tqdm import tqdm 10 | 11 | 12 | class FileDownloadError(Exception): 13 | """Custom exception raised when a file cannot be downloaded.""" 14 | 15 | 16 | DEFAULT_CACHE_DIR = os.getenv( 17 | "K_SRME_CACHE_DIR", str(Path.home() / ".cache" / "autoWTE") 18 | ) 19 | 20 | 21 | def glob2df( 22 | file_pattern: str, 23 | data_loader: Callable[[Any], pd.DataFrame] = None, 24 | pbar: bool = True, 25 | max_files=None, 26 | **load_options: Any, 27 | ) -> pd.DataFrame: 28 | """Merge multiple data files matching a glob pattern into a single dataframe. 29 | 30 | Args: 31 | file_pattern (str): Glob pattern for file matching (e.g., '*.csv'). 32 | data_loader (Callable[[Any], pd.DataFrame], optional): Function for loading 33 | individual files. Defaults to pd.read_csv for CSVs, otherwise pd.read_json. 34 | show_progress (bool, optional): Show progress bar during file loading. Defaults to True. 35 | **load_options: Additional options passed to the data loader (like pd.read_csv or pd.read_json). 36 | 37 | Returns: 38 | pd.DataFrame: A single DataFrame combining the data from all matching files. 39 | 40 | Raises: 41 | FileNotFoundError: If no files match the given glob pattern. 42 | """ 43 | # Choose the appropriate data loading function based on file extension if not provided 44 | if data_loader is None: 45 | if ".csv" in file_pattern.lower(): 46 | data_loader = pd.read_csv 47 | else: 48 | data_loader = pd.read_json 49 | 50 | # Find all files matching the given pattern 51 | matched_files = glob(file_pattern) 52 | if not matched_files: 53 | raise FileNotFoundError(f"No files matched the pattern: {file_pattern}") 54 | 55 | if max_files is not None: 56 | max_index = min(len(matched_files), max_files) 57 | matched_files = matched_files[:max_index] 58 | 59 | # Load data from each file into a dataframe 60 | dataframes = [] 61 | for file_path in tqdm(matched_files, disable=not pbar): 62 | df = data_loader(file_path, **load_options) 63 | dataframes.append(df) 64 | 65 | # Combine all loaded dataframes into one 66 | combined_df = pd.concat(dataframes, ignore_index=True) 67 | 68 | return combined_df 69 | 70 | 71 | class Files: 72 | """A class to manage files with associated URLs and cache functionality.""" 73 | 74 | def __init__( 75 | self, file_name: str, url: str = None, cache_dir: str = DEFAULT_CACHE_DIR 76 | ): 77 | """Initialize a file object with a name, optional download URL, and cache directory. 78 | 79 | Args: 80 | file_name (str): Name of the file. 81 | url (str): URL to download the file from if not present. 82 | cache_dir (str): Directory to store cached files. Defaults to a global cache dir. 83 | """ 84 | self.file_name = file_name 85 | self.url = url 86 | self.cache_dir = cache_dir 87 | self.file_path = os.path.join(self.cache_dir, file_name) 88 | 89 | def ensure_exists(self): 90 | """Ensure the file exists locally, downloading if necessary.""" 91 | if not os.path.isfile(self.file_path): 92 | if self.url is None: 93 | raise FileDownloadError( 94 | f"No URL provided for {self.file_name}. Cannot download." 95 | ) 96 | self.download_file() 97 | 98 | def download_file(self): 99 | """Download the file from the provided URL.""" 100 | os.makedirs(self.cache_dir, exist_ok=True) 101 | try: 102 | print(f"Downloading {self.file_name} from {self.url}") 103 | response = requests.get(self.url, stream=True) 104 | response.raise_for_status() 105 | 106 | with open(self.file_path, "wb") as f: 107 | for chunk in tqdm(response.iter_content(chunk_size=8192)): 108 | if chunk: 109 | f.write(chunk) 110 | except requests.RequestException as e: 111 | raise FileDownloadError(f"Failed to download {self.file_name}: {e}") 112 | 113 | def get_path(self): 114 | """Return the local path of the file, downloading if necessary.""" 115 | self.ensure_exists() 116 | return self.file_path 117 | 118 | 119 | class DataFiles(Files): 120 | """A class specifically for data files with predefined URLs.""" 121 | 122 | a = "b" 123 | -------------------------------------------------------------------------------- /k_srme/phono3py_utils.py: -------------------------------------------------------------------------------- 1 | from ase import Atoms 2 | from phono3py.api_phono3py import Phono3py 3 | from phonopy.structure.atoms import PhonopyAtoms 4 | 5 | 6 | def aseatoms2phonoatoms(atoms): 7 | phonoatoms = PhonopyAtoms( 8 | atoms.symbols, cell=atoms.cell, positions=atoms.positions, pbc=True 9 | ) 10 | return phonoatoms 11 | 12 | 13 | def aseatoms2phono3py( 14 | atoms, fc2_supercell, fc3_supercell, primitive_matrix=None, **kwargs 15 | ) -> Phono3py: 16 | unitcell = aseatoms2phonoatoms(atoms) 17 | return Phono3py( 18 | unitcell=unitcell, 19 | supercell_matrix=fc3_supercell, 20 | phonon_supercell_matrix=fc2_supercell, 21 | primitive_matrix=primitive_matrix, 22 | **kwargs, 23 | ) 24 | 25 | 26 | def phono3py2aseatoms(ph3: Phono3py) -> Atoms: 27 | phonopy_atoms = ph3.unitcell 28 | atoms = Atoms( 29 | phonopy_atoms.symbols, 30 | cell=phonopy_atoms.cell, 31 | positions=phonopy_atoms.positions, 32 | pbc=True, 33 | ) 34 | 35 | if ph3.supercell_matrix is not None: 36 | atoms.info["fc3_supercell"] = ph3.supercell_matrix 37 | 38 | if ph3.phonon_supercell_matrix is not None: 39 | atoms.info["fc2_supercell"] = ph3.phonon_supercell_matrix 40 | 41 | if ph3.primitive_matrix is not None: 42 | atoms.info["primitive_matrix"] = ph3.primitive_matrix 43 | 44 | if ph3.mesh_numbers is not None: 45 | atoms.info["q_mesh"] = ph3.mesh_numbers 46 | 47 | # TODO : Non-default values and BORN charges to be added 48 | 49 | return atoms 50 | 51 | 52 | def get_chemical_formula(ph3: Phono3py, mode="metal", **kwargs): 53 | unitcell = ph3.unitcell 54 | atoms = Atoms( 55 | unitcell.symbols, cell=unitcell.cell, positions=unitcell.positions, pbc=True 56 | ) 57 | return atoms.get_chemical_formula(mode=mode, **kwargs) 58 | -------------------------------------------------------------------------------- /k_srme/relax.py: -------------------------------------------------------------------------------- 1 | import os 2 | import warnings 3 | from pathlib import Path 4 | 5 | import ase 6 | from ase.calculators.calculator import Calculator 7 | from ase.constraints import FixSymmetry 8 | from ase.filters import FrechetCellFilter 9 | from ase.optimize import BFGS 10 | from ase.spacegroup import get_spacegroup 11 | 12 | from k_srme.utils import log_message, log_symmetry 13 | 14 | 15 | NO_TILT_MASK = [True, True, True, False, False, False] 16 | 17 | convert_ase_to_bar = 1.602176634e-19 / 1e-30 / 1e5 18 | 19 | 20 | def two_stage_relax( 21 | atoms, 22 | calculator: Calculator = None, 23 | fmax_stage1: float = 1e-4, 24 | fmax_stage2: float = 1e-4, 25 | steps_stage1: int = 300, 26 | steps_stage2: int = 300, 27 | enforce_symmetry: bool = True, 28 | symprec: float = 1e-5, 29 | allow_tilt: bool = False, 30 | Optimizer: type[ase.optimize.optimize.Optimizer] = BFGS, 31 | Filter: type[ase.filters.Filter] = FrechetCellFilter, 32 | filter_kwargs: dict | None = None, 33 | optim_kwargs: dict | None = None, 34 | log: str | Path | bool = True, # NOT WORKING FOR FILES FOR SYMMETRIES 35 | return_stage1: bool = False, 36 | symprec_tests: list[float] = [1e-5, 1e-4, 1e-3, 1e-1], 37 | ): 38 | if calculator is not None: 39 | atoms.calc = calculator 40 | elif atoms.calc is None: 41 | raise ValueError("Atoms object does not have a calculator assigned") 42 | 43 | if filter_kwargs is None: 44 | _filter_kwargs = {} 45 | else: 46 | _filter_kwargs = filter_kwargs 47 | 48 | if optim_kwargs is None: 49 | _optim_kwargs = {} 50 | else: 51 | _optim_kwargs = optim_kwargs 52 | 53 | if not log: 54 | ase_logfile = None 55 | elif log is True: 56 | ase_logfile = "-" 57 | else: 58 | ase_logfile = log 59 | 60 | if "name" in atoms.info: 61 | mat_name = atoms.info["name"] 62 | else: 63 | mat_name = f'{atoms.get_chemical_formula(mode="metal",empirical=True)}-{get_spacegroup(atoms,symprec=symprec).no}' 64 | 65 | tilt_mask = None 66 | if not allow_tilt: 67 | tilt_mask = NO_TILT_MASK 68 | 69 | input_cellpar = atoms.cell.cellpar().copy() 70 | 71 | log_message(f"\nRelaxing {mat_name}\n", output=log) 72 | log_message(f"Initial Energy {atoms.get_potential_energy()} ev", output=log) 73 | log_message( 74 | f"Initial Stress {atoms.get_stress()*convert_ase_to_bar} bar", output=log 75 | ) 76 | log_message("Initial symmetry:", output=log) 77 | sym_init = log_symmetry(atoms, symprec, output=log) 78 | 79 | atoms.set_constraint(FixSymmetry(atoms)) 80 | 81 | total_filter = Filter(atoms, mask=tilt_mask, **_filter_kwargs) 82 | dyn_stage1 = Optimizer(total_filter, **_optim_kwargs, logfile=ase_logfile) 83 | dyn_stage1.run(fmax=fmax_stage1, steps=steps_stage1) 84 | 85 | log_message( 86 | f"After keeping symmetry stage 1 relax, energy {atoms.get_potential_energy()} ev", 87 | output=log, 88 | ) 89 | log_message( 90 | f"After keeping symmetry stage 1 relax, stress {atoms.get_stress()*convert_ase_to_bar} bar", 91 | output=log, 92 | ) 93 | 94 | cell_diff = (atoms.cell.cellpar() / input_cellpar - 1.0) * 100 95 | log_message("Stage 1 Cell :", atoms.cell.cellpar(), output=log) 96 | log_message("Stage 1 Cell diff (%):", cell_diff, output=log) 97 | 98 | # We print out the initial symmetry groups 99 | log_message("After keeping symmetry stage 1 relax, symmetry:", output=log) 100 | sym_stage1 = log_symmetry(atoms, symprec, output=log) 101 | 102 | if sym_stage1["number"] != sym_init["number"]: 103 | warnings.warn( 104 | f"Symmetry is not kept during FixSymmetry relaxation of material {mat_name} in folder {os.getcwd()}" 105 | ) 106 | log_message( 107 | f"Symmetry is not kept during FixSymmetry relaxation of material {mat_name} in folder {os.getcwd()}", 108 | output=log, 109 | ) 110 | 111 | max_stress_stage1 = atoms.get_stress().reshape((2, 3), order="C").max(axis=1) 112 | 113 | atoms_stage1 = atoms.copy() 114 | atoms.constraints = None 115 | 116 | # Stage 2 117 | dyn_stage2 = Optimizer(total_filter, **_optim_kwargs, logfile=ase_logfile) 118 | dyn_stage2.run(fmax=fmax_stage2, steps=steps_stage2) 119 | 120 | log_message("Stage 2 Energy", atoms.get_potential_energy(), " ev", output=log) 121 | log_message( 122 | "Stage 2 Stress", atoms.get_stress() * convert_ase_to_bar, " bar", output=log 123 | ) 124 | log_message("Stage 2 Symmetry:", output=log) 125 | sym_stage2 = log_symmetry(atoms, symprec, output=log) 126 | cell_diff = (atoms.cell.cellpar() / input_cellpar - 1.0) * 100 127 | log_message(f"Stage 2 Cell : {atoms.cell.cellpar()}", output=log) 128 | log_message(f"Stage 2 Cell diff (%): {cell_diff}\n", output=log) 129 | 130 | # Test symmetries with various symprec if stage2 is different 131 | sym_tests = {} 132 | if sym_init.number != sym_stage2.number: 133 | for symprec_test in symprec_tests: 134 | log_message("Stage 2 Symmetry Test:", output=log) 135 | dataset_tests = log_symmetry(atoms, symprec_test, output=log) 136 | sym_tests[symprec_test] = dataset_tests.number 137 | 138 | max_stress_stage2 = atoms.get_stress().reshape((2, 3), order="C").max(axis=1) 139 | 140 | atoms_stage2 = atoms.copy() 141 | 142 | # compare symmetries and redirect to stage 1 143 | if sym_stage1.number != sym_stage2.number and enforce_symmetry: 144 | redirected_to_symm = True 145 | atoms = atoms_stage1 146 | max_stress = max_stress_stage1 147 | sym_final = sym_stage1 148 | warnings.warn( 149 | f"Symmetry is not kept after deleting FixSymmetry constraint, redirecting to structure with symmetry of material {mat_name}, in folder {os.getcwd()}" 150 | ) 151 | log_message( 152 | f"Symmetry is not kept after deleting FixSymmetry constraint, redirecting to structure with symmetry of material {mat_name}, in folder {os.getcwd()}", 153 | output=log, 154 | ) 155 | 156 | else: 157 | redirected_to_symm = False 158 | sym_final = sym_stage2 159 | max_stress = max_stress_stage2 160 | 161 | # maximum residual stress component in for xx,yy,zz and xy,yz,xz components separately 162 | # result is a array of 2 elements 163 | reached_max_steps = ( 164 | dyn_stage1.step == steps_stage1 or dyn_stage2.step == steps_stage2 165 | ) 166 | 167 | relax_dict = { 168 | "max_stress": max_stress, 169 | "reached_max_steps": reached_max_steps, 170 | "relaxed_space_group_number": sym_final.number, 171 | "broken_symmetry": sym_final.number != sym_init.number, 172 | "symprec_tests": sym_tests, 173 | "redirected_to_symm": redirected_to_symm, 174 | } 175 | 176 | if return_stage1: 177 | # first return is final, second is stage 1, third is stage 2 178 | return_atoms = [atoms, atoms_stage1, atoms_stage2] 179 | else: 180 | return_atoms = atoms 181 | 182 | return return_atoms, relax_dict 183 | -------------------------------------------------------------------------------- /k_srme/utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import io 3 | import sys 4 | import traceback 5 | import warnings 6 | from typing import Any, TextIO 7 | 8 | import numpy as np 9 | import pandas as pd 10 | from ase import Atoms 11 | from ase.io import read, write 12 | from ase.utils import atoms_to_spglib_cell 13 | from spglib import get_symmetry_dataset 14 | 15 | 16 | FREQUENCY_THRESHOLD = -1e-2 17 | MODE_KAPPA_THRESHOLD = 1e-6 18 | 19 | symm_name_map = {225: "rs", 186: "wz", 216: "zb"} 20 | 21 | 22 | def aseatoms2str(atoms: Atoms, format: str = "extxyz") -> str: 23 | buffer = io.StringIO() 24 | write( 25 | buffer, 26 | Atoms(atoms.symbols, positions=atoms.positions, cell=atoms.cell, pbc=True), 27 | format=format, 28 | ) # You can choose different formats (like 'cif', 'pdb', etc.) 29 | atoms_string = buffer.getvalue() 30 | return atoms_string 31 | 32 | 33 | def str2aseatoms(atoms_string: str, format: str = "extxyz") -> Atoms: 34 | buffer = io.StringIO(atoms_string) 35 | return read(buffer, format=format) 36 | 37 | 38 | def log_message( 39 | *messages: Any, 40 | output: bool | str | TextIO = True, 41 | sep: str = " ", 42 | **kwargs: Any, 43 | ) -> None: 44 | timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 45 | write_message = f"{timestamp} - {sep.join(str(m) for m in messages)}" 46 | 47 | if output is False: 48 | pass 49 | elif output is True: 50 | print(write_message, **kwargs) 51 | sys.stdout.flush() 52 | elif isinstance(output, str): 53 | # Write to the file specified by 'output' 54 | with open(output, "a") as file: 55 | file.write(write_message + "\n", **kwargs) 56 | elif hasattr(output, "write"): 57 | # Write to the file object 58 | output.write(write_message + "\n", **kwargs) 59 | output.flush() 60 | 61 | 62 | class ImaginaryFrequencyError(Exception): 63 | def __init__(self, value): 64 | self.value = value 65 | 66 | def __str__(self): 67 | return repr(self.value) 68 | 69 | 70 | def check_imaginary_freqs(frequencies: np.ndarray) -> bool: 71 | try: 72 | if np.all(pd.isna(frequencies)): 73 | return True 74 | 75 | if np.any(frequencies[0, 3:] < 0): 76 | return True 77 | 78 | if np.any(frequencies[0, :3] < FREQUENCY_THRESHOLD): 79 | return True 80 | 81 | if np.any(frequencies[1:] < 0): 82 | return True 83 | except Exception as e: 84 | warnings.warn(f"Failed to check imaginary frequencies: {e!r}") 85 | warnings.warn(traceback.format_exc()) 86 | 87 | return False 88 | 89 | 90 | def get_spacegroup_number(atoms: Atoms, symprec: float = 1e-5) -> int: 91 | dataset = get_symmetry_dataset(atoms_to_spglib_cell(atoms), symprec=symprec) 92 | return dataset.number 93 | 94 | 95 | def log_symmetry( 96 | atoms: Atoms, symprec: float, output: bool | str | TextIO = True 97 | ) -> Any: 98 | dataset = get_symmetry_dataset(atoms_to_spglib_cell(atoms), symprec=symprec) 99 | 100 | log_message( 101 | "Symmetry: prec", 102 | symprec, 103 | "got symmetry group number", 104 | dataset.number, 105 | ", international (Hermann-Mauguin)", 106 | dataset.international, 107 | ", Hall ", 108 | dataset.hall, 109 | output=output, 110 | ) 111 | 112 | return dataset 113 | -------------------------------------------------------------------------------- /models/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/.DS_Store -------------------------------------------------------------------------------- /models/CHGNet/1_test_srme.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | import warnings 4 | from typing import Literal, Any 5 | from collections.abc import Callable 6 | import traceback 7 | from copy import deepcopy 8 | from importlib.metadata import version 9 | import json 10 | 11 | import pandas as pd 12 | 13 | from tqdm import tqdm 14 | 15 | from ase.constraints import FixSymmetry 16 | from ase.filters import ExpCellFilter, FrechetCellFilter 17 | from ase.optimize import FIRE, LBFGS 18 | from ase.optimize.optimize import Optimizer 19 | from ase import Atoms 20 | from ase.io import read 21 | 22 | from k_srme import aseatoms2str, two_stage_relax, ID, STRUCTURES, NO_TILT_MASK 23 | from k_srme.utils import symm_name_map, get_spacegroup_number, check_imaginary_freqs 24 | from k_srme.conductivity import ( 25 | init_phono3py, 26 | get_fc2_and_freqs, 27 | get_fc3, 28 | calculate_conductivity, 29 | ) 30 | 31 | import torch 32 | from chgnet.model.dynamics import CHGNetCalculator 33 | 34 | 35 | warnings.filterwarnings("ignore", category=DeprecationWarning, module="spglib") 36 | 37 | 38 | # EDITABLE CONFIG 39 | model_name = "CHGNet" 40 | 41 | 42 | device = "cuda" if torch.cuda.is_available() else "cpu" 43 | calc = CHGNetCalculator(use_device=device) 44 | 45 | # Relaxation parameters 46 | ase_optimizer: Literal["FIRE", "LBFGS", "BFGS"] = "FIRE" 47 | ase_filter: Literal["frechet", "exp"] = "frechet" 48 | if_two_stage_relax = True # Use two-stage relaxation enforcing symmetries 49 | max_steps = 300 50 | force_max = 1e-4 # Run until the forces are smaller than this in eV/A 51 | 52 | # Symmetry parameters 53 | # symmetry precision for enforcing relaxation and conductivity calculation 54 | symprec = 1e-5 55 | # Enforce symmetry with during relaxation if broken 56 | enforce_relax_symm = True 57 | # Conductivity to be calculated if symmetry group changed during relaxation 58 | conductivity_broken_symm = False 59 | prog_bar = True 60 | save_forces = True # Save force sets to file 61 | 62 | 63 | slurm_array_task_count = int( 64 | os.getenv( 65 | "K_SRME_RESTART_ARRAY_TASK_COUNT", os.getenv("SLURM_ARRAY_TASK_COUNT", "1") 66 | ) 67 | ) 68 | slurm_array_task_id = int(os.getenv("SLURM_ARRAY_TASK_ID", "0")) 69 | slurm_array_job_id = os.getenv("SLURM_ARRAY_JOB_ID", os.getenv("SLURM_JOB_ID", "debug")) 70 | slurm_array_task_min = int( 71 | os.getenv("K_SRME_RESTART_ARRAY_TASK_MIN", os.getenv("SLURM_ARRAY_TASK_MIN", "0")) 72 | ) 73 | 74 | 75 | task_type = "LTC" # lattice thermal conductivity 76 | job_name = f"{model_name}-phononDB-{task_type}-{ase_optimizer}{'_2SR' if if_two_stage_relax else ''}_force{force_max}_sym{symprec}" 77 | module_dir = os.path.dirname(__file__) 78 | out_dir = os.getenv( 79 | "SBATCH_OUTPUT", 80 | f"{module_dir}/{datetime.datetime.now().strftime('%Y-%m-%d')}-{job_name}", 81 | ) 82 | os.makedirs(out_dir, exist_ok=True) 83 | 84 | out_path = ( 85 | f"{out_dir}/conductivity_{slurm_array_job_id}-{slurm_array_task_id:>03}.json.gz" 86 | ) 87 | 88 | 89 | timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 90 | struct_data_path = STRUCTURES 91 | print(f"\nJob {job_name} started {timestamp}") 92 | 93 | 94 | print(f"Read data from {struct_data_path}") 95 | atoms_list: list[Atoms] = read(struct_data_path, format="extxyz", index=":") 96 | 97 | run_params = { 98 | "timestamp": timestamp, 99 | "k_srme_version": version("k_srme"), 100 | "model_name": model_name, 101 | "device": device, 102 | "versions": {dep: version(dep) for dep in ("numpy", "torch")}, 103 | "ase_optimizer": ase_optimizer, 104 | "ase_filter": ase_filter, 105 | "if_two_stage_relax": if_two_stage_relax, 106 | "max_steps": max_steps, 107 | "force_max": force_max, 108 | "symprec": symprec, 109 | "enforce_relax_symm": enforce_relax_symm, 110 | "conductivity_broken_symm": conductivity_broken_symm, 111 | "slurm_array_task_count": slurm_array_task_count, 112 | "slurm_array_job_id": slurm_array_job_id, 113 | "task_type": task_type, 114 | "job_name": job_name, 115 | "struct_data_path": os.path.basename(struct_data_path), 116 | "n_structures": len(atoms_list), 117 | } 118 | 119 | if slurm_array_task_id == slurm_array_task_min: 120 | with open(f"{out_dir}/run_params.json", "w") as f: 121 | json.dump(run_params, f, indent=4) 122 | 123 | if slurm_array_job_id == "debug": 124 | atoms_list = atoms_list[:5] 125 | print("Running in DEBUG mode.") 126 | elif slurm_array_task_count > 1: 127 | # Split the atoms_list into slurm_array_task_count parts trying to make even runtime 128 | atoms_list = atoms_list[ 129 | slurm_array_task_id - slurm_array_task_min :: slurm_array_task_count 130 | ] 131 | 132 | 133 | # Set up the relaxation and force set calculation 134 | filter_cls: Callable[[Atoms], Atoms] = { 135 | "frechet": FrechetCellFilter, 136 | "exp": ExpCellFilter, 137 | }[ase_filter] 138 | optim_cls: Callable[..., Optimizer] = {"FIRE": FIRE, "LBFGS": LBFGS}[ase_optimizer] 139 | 140 | 141 | force_results: dict[str, dict[str, Any]] = {} 142 | kappa_results: dict[str, dict[str, Any]] = {} 143 | 144 | 145 | tqdm_bar = tqdm(atoms_list, desc="Conductivity calculation: ", disable=not prog_bar) 146 | 147 | for atoms in tqdm_bar: 148 | mat_id = atoms.info[ID] 149 | init_info = deepcopy(atoms.info) 150 | mat_name = atoms.info["name"] 151 | mat_desc = f"{mat_name}-{symm_name_map[atoms.info['symm.no']]}" 152 | info_dict = { 153 | "desc": mat_desc, 154 | "name": mat_name, 155 | "initial_space_group_number": atoms.info["symm.no"], 156 | "errors": [], 157 | "error_traceback": [], 158 | } 159 | 160 | tqdm_bar.set_postfix_str(mat_desc, refresh=True) 161 | 162 | # Relaxation 163 | try: 164 | atoms.calc = calc 165 | if max_steps > 0: 166 | if not if_two_stage_relax: 167 | if enforce_relax_symm: 168 | atoms.set_constraint(FixSymmetry(atoms)) 169 | filtered_atoms = filter_cls(atoms, mask=NO_TILT_MASK) 170 | else: 171 | filtered_atoms = filter_cls(atoms) 172 | 173 | optimizer = optim_cls( 174 | filtered_atoms, logfile=f"{out_dir}/relax_{slurm_array_task_id}.log" 175 | ) 176 | optimizer.run(fmax=force_max, steps=max_steps) 177 | 178 | reached_max_steps = False 179 | if optimizer.step == max_steps: 180 | reached_max_steps = True 181 | print( 182 | f"Material {mat_desc=}, {mat_id=} reached max step {max_steps=} during relaxation." 183 | ) 184 | 185 | # maximum residual stress component in for xx,yy,zz and xy,yz,xz components separately 186 | # result is a array of 2 elements 187 | max_stress = atoms.get_stress().reshape((2, 3), order="C").max(axis=1) 188 | 189 | atoms.calc = None 190 | atoms.constraints = None 191 | atoms.info = init_info | atoms.info 192 | 193 | symm_no = get_spacegroup_number(atoms, symprec=symprec) 194 | 195 | relax_dict = { 196 | "structure": aseatoms2str(atoms), 197 | "max_stress": max_stress, 198 | "reached_max_steps": reached_max_steps, 199 | "relaxed_space_group_number": symm_no, 200 | "broken_symmetry": symm_no 201 | != init_info["initial_space_group_number"], 202 | } 203 | 204 | else: 205 | atoms, relax_dict = two_stage_relax( 206 | atoms, 207 | fmax_stage1=force_max, 208 | fmax_stage2=force_max, 209 | steps_stage1=max_steps, 210 | steps_stage2=max_steps, 211 | Optimizer=optim_cls, 212 | Filter=filter_cls, 213 | allow_tilt=False, 214 | log=f"{out_dir}/relax_{slurm_array_task_id}.log", 215 | enforce_symmetry=enforce_relax_symm, 216 | ) 217 | 218 | atoms.calc = None 219 | 220 | except Exception as exc: 221 | warnings.warn(f"Failed to relax {mat_name=}, {mat_id=}: {exc!r}") 222 | traceback.print_exc() 223 | info_dict["errors"].append(f"RelaxError: {exc!r}") 224 | info_dict["error_traceback"].append(traceback.format_exc()) 225 | kappa_results[mat_id] = info_dict 226 | continue 227 | 228 | # Calculation of force sets 229 | try: 230 | ph3 = init_phono3py(atoms, log=False, symprec=symprec) 231 | 232 | ph3, fc2_set, freqs = get_fc2_and_freqs( 233 | ph3, 234 | calculator=calc, 235 | log=False, 236 | pbar_kwargs={"leave": False, "disable": not prog_bar}, 237 | ) 238 | 239 | imaginary_freqs = check_imaginary_freqs(freqs) 240 | freqs_dict = {"imaginary_freqs": imaginary_freqs, "frequencies": freqs} 241 | 242 | # if conductivity condition is met, calculate fc3 243 | ltc_condition = not imaginary_freqs and ( 244 | not relax_dict["broken_symmetry"] or conductivity_broken_symm 245 | ) 246 | 247 | if ltc_condition: 248 | ph3, fc3_set = get_fc3( 249 | ph3, 250 | calculator=calc, 251 | log=False, 252 | pbar_kwargs={"leave": False, "disable": not prog_bar}, 253 | ) 254 | 255 | else: 256 | fc3_set = [] 257 | 258 | if save_forces: 259 | force_results[mat_id] = {"fc2_set": fc2_set, "fc3_set": fc3_set} 260 | 261 | if not ltc_condition: 262 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict 263 | continue 264 | 265 | except Exception as exc: 266 | warnings.warn(f"Failed to calculate force sets {mat_id}: {exc!r}") 267 | traceback.print_exc() 268 | info_dict["errors"].append(f"ForceConstantError: {exc!r}") 269 | info_dict["error_traceback"].append(traceback.format_exc()) 270 | kappa_results[mat_id] = info_dict | relax_dict 271 | continue 272 | 273 | # Calculation of conductivity 274 | try: 275 | ph3, kappa_dict = calculate_conductivity(ph3, log=False) 276 | 277 | except Exception as exc: 278 | warnings.warn(f"Failed to calculate conductivity {mat_id}: {exc!r}") 279 | traceback.print_exc() 280 | info_dict["errors"].append(f"ConductivityError: {exc!r}") 281 | info_dict["error_traceback"].append(traceback.format_exc()) 282 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict 283 | continue 284 | 285 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict | kappa_dict 286 | 287 | 288 | df_kappa = pd.DataFrame(kappa_results).T 289 | df_kappa.index.name = ID 290 | df_kappa.reset_index().to_json(out_path) 291 | 292 | 293 | if save_forces: 294 | force_out_path = ( 295 | f"{out_dir}/force_sets_{slurm_array_job_id}-{slurm_array_task_id:>03}.json.gz" 296 | ) 297 | df_force = pd.DataFrame(force_results).T 298 | df_force = pd.concat([df_kappa, df_force], axis=1) 299 | df_force.index.name = ID 300 | df_force.reset_index().to_json(force_out_path) 301 | -------------------------------------------------------------------------------- /models/CHGNet/2024-11-09-CHGNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/CHGNet/2024-11-09-CHGNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/.DS_Store -------------------------------------------------------------------------------- /models/CHGNet/2024-11-09-CHGNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/CHGNet/2024-11-09-CHGNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz -------------------------------------------------------------------------------- /models/CHGNet/2024-11-09-CHGNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2024-11-09 13:23:11", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "CHGNet", 5 | "device": "cpu", 6 | "versions": { 7 | "numpy": "1.26.4", 8 | "torch": "2.4.1+cu124" 9 | }, 10 | "ase_optimizer": "FIRE", 11 | "ase_filter": "frechet", 12 | "if_two_stage_relax": true, 13 | "max_steps": 300, 14 | "force_max": 0.0001, 15 | "symprec": 1e-05, 16 | "enforce_relax_symm": true, 17 | "conductivity_broken_symm": false, 18 | "slurm_array_task_count": 34, 19 | "slurm_array_job_id": "1649654", 20 | "task_type": "LTC", 21 | "job_name": "CHGNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 22 | "struct_data_path": "phononDB-PBE-structures.extxyz", 23 | "n_structures": 103 24 | } -------------------------------------------------------------------------------- /models/CHGNet/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "CHGNet" 10 | 11 | json_file = "k_srme.json.gz" 12 | txt_path = "metrics.txt" 13 | in_file = "conductivity_*-???.json.gz" 14 | 15 | in_folder = f"2024-11-09-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 16 | 17 | 18 | # Comparing with the DFT results without 19 | # non-analytical correction term (NAC) 20 | DFT_RESULTS_FILE = DFT_NONAC_REF 21 | 22 | 23 | module_dir = os.path.dirname(__file__) 24 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 25 | out_path = f"{module_dir}/{in_folder}/{json_file}" 26 | 27 | 28 | # Read MLP results 29 | if not glob(in_pattern): 30 | if os.path.exists(out_path): 31 | df_mlp_results = pd.read_json(out_path).set_index(ID) 32 | else: 33 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 34 | 35 | 36 | # df_mlp_results.reset_index().to_json(out_path) 37 | 38 | # Read DFT results 39 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 40 | 41 | 42 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 43 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 44 | 45 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 46 | 47 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 48 | 49 | 50 | # Save results 51 | df_mlp_processed.round(5) 52 | df_mlp_processed.index.name = ID 53 | df_mlp_processed.reset_index().to_json(out_path) 54 | 55 | 56 | # Print 57 | pd.set_option("display.max_rows", None) 58 | pd.set_option("display.max_columns", None) 59 | df_mlp_print = df_mlp_filtered[ 60 | [ 61 | "desc", 62 | "SRME", 63 | "SRE", 64 | "kappa_TOT_ave", 65 | "DFT_kappa_TOT_ave", 66 | "imaginary_freqs", 67 | "errors", 68 | ] 69 | ] 70 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 71 | lambda x: x[0] if not pd.isna(x) else x 72 | ) 73 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 74 | lambda x: x[0] if not pd.isna(x) else x 75 | ) 76 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 77 | 78 | with open(txt_path, "w") as f: 79 | print(f"MODEL: {model_name}", file=f) 80 | print(f"\tmean SRME: {mSRME}", file=f) 81 | print(f"\tmean SRE: {mSRE}", file=f) 82 | 83 | print(df_mlp_print.round(4), file=f) 84 | 85 | 86 | df_mlp_print = df_mlp_print[ 87 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 88 | ] 89 | print(df_mlp_print.round(3)) 90 | 91 | print(f"MODEL: {model_name}") 92 | print(f"\tmean SRME: {mSRME}") 93 | print(f"\tmean SRE: {mSRE}") 94 | -------------------------------------------------------------------------------- /models/GRACE-1L-OAM/2025-02-05-GRACE-1L-OAM_2Feb25-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/GRACE-1L-OAM/2025-02-05-GRACE-1L-OAM_2Feb25-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz -------------------------------------------------------------------------------- /models/GRACE-1L-OAM/2025-02-05-GRACE-1L-OAM_2Feb25-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2025-02-05 14:59:03", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "GRACE-1L-OAM_2Feb25", 5 | "checkpoint": "2Feb2025", 6 | "device": "cuda", 7 | "dtype": "float64", 8 | "versions": { 9 | "numpy": "1.26.4", 10 | "tensorflow": "2.16.2", 11 | "tensorpotential": "0.4.5" 12 | }, 13 | "ase_optimizer": "FIRE", 14 | "ase_filter": "frechet", 15 | "if_two_stage_relax": true, 16 | "max_steps": 300, 17 | "force_max": 0.0001, 18 | "symprec": 1e-05, 19 | "enforce_relax_symm": true, 20 | "conductivity_broken_symm": false, 21 | "slurm_array_task_count": 16, 22 | "slurm_array_job_id": "production", 23 | "task_type": "LTC", 24 | "job_name": "GRACE-1L-OAM_2Feb25-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 25 | "struct_data_path": "phononDB-PBE-structures.extxyz", 26 | "n_structures": 103 27 | } -------------------------------------------------------------------------------- /models/GRACE-1L-OAM/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "GRACE-1L-OAM_2Feb25" 10 | 11 | 12 | json_file = "k_srme.json.gz" 13 | txt_path = "metrics.txt" 14 | in_file = "conductivity_*-???.json.gz" 15 | 16 | in_folder = f"2025-02-05-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 17 | 18 | # Comparing with the DFT results without 19 | # non-analytical correction term (NAC), which is in closer agreement with to MLP results 20 | DFT_RESULTS_FILE = DFT_NONAC_REF 21 | 22 | 23 | module_dir = os.path.dirname(__file__) 24 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 25 | out_path = f"{module_dir}/{in_folder}/{json_file}" 26 | 27 | print(f"{in_pattern=}") 28 | 29 | # Read MLP results 30 | if not glob(in_pattern): 31 | if os.path.exists(out_path): 32 | df_mlp_results = pd.read_json(out_path).set_index(ID) 33 | else: 34 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 35 | 36 | 37 | # df_mlp_results.reset_index().to_json(out_path) 38 | 39 | # Read DFT results 40 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 41 | 42 | 43 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 44 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 45 | 46 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 47 | 48 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 49 | 50 | 51 | # Save results 52 | df_mlp_processed.round(5) 53 | df_mlp_processed.index.name = ID 54 | df_mlp_processed.reset_index().to_json(out_path) 55 | 56 | 57 | # Print 58 | pd.set_option("display.max_rows", None) 59 | pd.set_option("display.max_columns", None) 60 | df_mlp_print = df_mlp_filtered[ 61 | [ 62 | "desc", 63 | "SRME", 64 | "SRE", 65 | "kappa_TOT_ave", 66 | "DFT_kappa_TOT_ave", 67 | "imaginary_freqs", 68 | "errors", 69 | ] 70 | ] 71 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 72 | lambda x: x[0] if not pd.isna(x) else x 73 | ) 74 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 75 | lambda x: x[0] if not pd.isna(x) else x 76 | ) 77 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 78 | 79 | with open(txt_path, "w") as f: 80 | print(f"MODEL: {model_name}", file=f) 81 | print(f"\tmean SRME: {mSRME}", file=f) 82 | print(f"\tmean SRE: {mSRE}", file=f) 83 | 84 | print(df_mlp_print.round(4), file=f) 85 | 86 | 87 | df_mlp_print = df_mlp_print[ 88 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 89 | ] 90 | print(df_mlp_print.round(3)) 91 | 92 | print(f"MODEL: {model_name}") 93 | print(f"\tmean SRME: {mSRME}") 94 | print(f"\tmean SRE: {mSRE}") 95 | -------------------------------------------------------------------------------- /models/GRACE-1L-OAM/run_eval_parallel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## this script is for imitation of SLURM tasks array on local machine with multiple GPUs 4 | 5 | THREADS=8 # number of threads for OMP, MKL, NUMEXPR etc. To share resources on single machine 6 | 7 | GPU_LIST=(-1 -1 -1 -1) # Define the list of GPUs to use 8 | NGPU=${#GPU_LIST[@]} # Set NGPU to the number of GPUs in GPU_LIST, automatically determined from GPU_LIST 9 | 10 | model_name="GRACE" # just for information, model name is hardcoded in 1_test_srme.py 11 | 12 | export MODEL_NAME="${model_name}" 13 | echo "MODEL_NAME=${MODEL_NAME}" 14 | export MKL_NUM_THREADS=$THREADS 15 | export NUMEXPR_NUM_THREADS=$THREADS 16 | export OMP_NUM_THREADS=$THREADS 17 | 18 | export SLURM_ARRAY_TASK_COUNT=16 # slurm_array_task_count 19 | export SLURM_ARRAY_JOB_ID="production" 20 | export SLURM_JOB_ID="production" 21 | 22 | 23 | # Function to kill all child processes when the script receives a termination signal 24 | cleanup() { 25 | echo "Terminating all child processes..." 26 | pkill -P $$ 27 | exit 1 28 | } 29 | 30 | # Set trap to catch signals and trigger the cleanup function 31 | trap cleanup SIGINT SIGTERM 32 | 33 | 34 | # skip1 and skip2 are options to control execution 35 | # of first and second stages of workflow 36 | 37 | # Check if "skip1" is in the arguments 38 | skip1=false 39 | for arg in "$@"; do 40 | if [ "$arg" = "skip1" ]; then 41 | skip1=true 42 | break 43 | fi 44 | done 45 | 46 | skip2=false 47 | for arg in "$@"; do 48 | if [ "$arg" = "skip2" ]; then 49 | skip2=true 50 | break 51 | fi 52 | done 53 | 54 | exec > "${MODEL_NAME}.out" 2>&1 55 | echo "MODEL_NAME=${MODEL_NAME}" 56 | echo "SLURM_ARRAY_TASK_COUNT=${SLURM_ARRAY_TASK_COUNT}" 57 | 58 | if [ "$skip1" = false ]; then 59 | echo "Running 1_test_srme.py" 60 | for task_id in $(seq 0 $((SLURM_ARRAY_TASK_COUNT-1))) 61 | do 62 | CUDA_VISIBLE_DEVICES=${GPU_LIST[$((task_id % NGPU))]} SLURM_ARRAY_TASK_ID=${task_id} python 1_test_srme.py "$MODEL_NAME" & 63 | done 64 | wait 65 | else 66 | echo "Skip 1_force_sets" 67 | fi 68 | 69 | if [ "$skip2" = false ]; then 70 | echo "Running 2_evaluate" 71 | for task_id in $(seq 0 $((SLURM_ARRAY_TASK_COUNT-1))) 72 | do 73 | SLURM_ARRAY_TASK_ID=${task_id} python 2_evaluate.py "$MODEL_NAME" & 74 | done 75 | wait 76 | else 77 | echo "Skip 2_evaluate" 78 | fi 79 | -------------------------------------------------------------------------------- /models/GRACE-2L-OAM/2025-02-05-GRACE_2L_OAM_28Jan25-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/GRACE-2L-OAM/2025-02-05-GRACE_2L_OAM_28Jan25-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz -------------------------------------------------------------------------------- /models/GRACE-2L-OAM/2025-02-05-GRACE_2L_OAM_28Jan25-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2025-02-05 14:51:47", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "GRACE_2L_OAM_28Jan25", 5 | "checkpoint": "28Jan2025", 6 | "device": "cuda", 7 | "dtype": "float64", 8 | "versions": { 9 | "numpy": "1.26.4", 10 | "tensorflow": "2.16.2", 11 | "tensorpotential": "0.4.5" 12 | }, 13 | "ase_optimizer": "FIRE", 14 | "ase_filter": "frechet", 15 | "if_two_stage_relax": true, 16 | "max_steps": 300, 17 | "force_max": 0.0001, 18 | "symprec": 1e-05, 19 | "enforce_relax_symm": true, 20 | "conductivity_broken_symm": false, 21 | "slurm_array_task_count": 16, 22 | "slurm_array_job_id": "production", 23 | "task_type": "LTC", 24 | "job_name": "GRACE_2L_OAM_28Jan25-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 25 | "struct_data_path": "phononDB-PBE-structures.extxyz", 26 | "n_structures": 103 27 | } -------------------------------------------------------------------------------- /models/GRACE-2L-OAM/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "GRACE_2L_OAM_28Jan25" 10 | 11 | 12 | json_file = "k_srme.json.gz" 13 | txt_path = "metrics.txt" 14 | in_file = "conductivity_*-???.json.gz" 15 | 16 | in_folder = f"2025-02-05-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 17 | 18 | # Comparing with the DFT results without 19 | # non-analytical correction term (NAC), which is in closer agreement with to MLP results 20 | DFT_RESULTS_FILE = DFT_NONAC_REF 21 | 22 | 23 | module_dir = os.path.dirname(__file__) 24 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 25 | out_path = f"{module_dir}/{in_folder}/{json_file}" 26 | 27 | 28 | # Read MLP results 29 | if not glob(in_pattern): 30 | if os.path.exists(out_path): 31 | df_mlp_results = pd.read_json(out_path).set_index(ID) 32 | else: 33 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 34 | 35 | 36 | # df_mlp_results.reset_index().to_json(out_path) 37 | 38 | # Read DFT results 39 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 40 | 41 | 42 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 43 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 44 | 45 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 46 | 47 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 48 | 49 | 50 | # Save results 51 | df_mlp_processed.round(5) 52 | df_mlp_processed.index.name = ID 53 | df_mlp_processed.reset_index().to_json(out_path) 54 | 55 | 56 | # Print 57 | pd.set_option("display.max_rows", None) 58 | pd.set_option("display.max_columns", None) 59 | df_mlp_print = df_mlp_filtered[ 60 | [ 61 | "desc", 62 | "SRME", 63 | "SRE", 64 | "kappa_TOT_ave", 65 | "DFT_kappa_TOT_ave", 66 | "imaginary_freqs", 67 | "errors", 68 | ] 69 | ] 70 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 71 | lambda x: x[0] if not pd.isna(x) else x 72 | ) 73 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 74 | lambda x: x[0] if not pd.isna(x) else x 75 | ) 76 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 77 | 78 | with open(txt_path, "w") as f: 79 | print(f"MODEL: {model_name}", file=f) 80 | print(f"\tmean SRME: {mSRME}", file=f) 81 | print(f"\tmean SRE: {mSRE}", file=f) 82 | 83 | print(df_mlp_print.round(4), file=f) 84 | 85 | 86 | df_mlp_print = df_mlp_print[ 87 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 88 | ] 89 | print(df_mlp_print.round(3)) 90 | 91 | print(f"MODEL: {model_name}") 92 | print(f"\tmean SRME: {mSRME}") 93 | print(f"\tmean SRE: {mSRE}") 94 | -------------------------------------------------------------------------------- /models/GRACE-2L-OAM/run_eval_parallel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## this script is for imitation of SLURM tasks array on local machine with multiple GPUs 4 | 5 | THREADS=8 # number of threads for OMP, MKL, NUMEXPR etc. To share resources on single machine 6 | 7 | GPU_LIST=(-1 -1 -1 -1) # Define the list of GPUs to use 8 | NGPU=${#GPU_LIST[@]} # Set NGPU to the number of GPUs in GPU_LIST, automatically determined from GPU_LIST 9 | 10 | model_name="GRACE" # just for information, model name is hardcoded in 1_test_srme.py 11 | 12 | export MODEL_NAME="${model_name}" 13 | echo "MODEL_NAME=${MODEL_NAME}" 14 | export MKL_NUM_THREADS=$THREADS 15 | export NUMEXPR_NUM_THREADS=$THREADS 16 | export OMP_NUM_THREADS=$THREADS 17 | 18 | export SLURM_ARRAY_TASK_COUNT=16 # slurm_array_task_count 19 | export SLURM_ARRAY_JOB_ID="production" 20 | export SLURM_JOB_ID="production" 21 | 22 | 23 | # Function to kill all child processes when the script receives a termination signal 24 | cleanup() { 25 | echo "Terminating all child processes..." 26 | pkill -P $$ 27 | exit 1 28 | } 29 | 30 | # Set trap to catch signals and trigger the cleanup function 31 | trap cleanup SIGINT SIGTERM 32 | 33 | 34 | # skip1 and skip2 are options to control execution 35 | # of first and second stages of workflow 36 | 37 | # Check if "skip1" is in the arguments 38 | skip1=false 39 | for arg in "$@"; do 40 | if [ "$arg" = "skip1" ]; then 41 | skip1=true 42 | break 43 | fi 44 | done 45 | 46 | skip2=false 47 | for arg in "$@"; do 48 | if [ "$arg" = "skip2" ]; then 49 | skip2=true 50 | break 51 | fi 52 | done 53 | 54 | exec > "${MODEL_NAME}.out" 2>&1 55 | echo "MODEL_NAME=${MODEL_NAME}" 56 | echo "SLURM_ARRAY_TASK_COUNT=${SLURM_ARRAY_TASK_COUNT}" 57 | 58 | if [ "$skip1" = false ]; then 59 | echo "Running 1_test_srme.py" 60 | for task_id in $(seq 0 $((SLURM_ARRAY_TASK_COUNT-1))) 61 | do 62 | CUDA_VISIBLE_DEVICES=${GPU_LIST[$((task_id % NGPU))]} SLURM_ARRAY_TASK_ID=${task_id} python 1_test_srme.py "$MODEL_NAME" & 63 | done 64 | wait 65 | else 66 | echo "Skip 1_force_sets" 67 | fi 68 | 69 | if [ "$skip2" = false ]; then 70 | echo "Running 2_evaluate" 71 | for task_id in $(seq 0 $((SLURM_ARRAY_TASK_COUNT-1))) 72 | do 73 | SLURM_ARRAY_TASK_ID=${task_id} python 2_evaluate.py "$MODEL_NAME" & 74 | done 75 | wait 76 | else 77 | echo "Skip 2_evaluate" 78 | fi 79 | -------------------------------------------------------------------------------- /models/GRACE-2L-r6-11Nov2024/2024-11-20-MP_GRACE_2L_r6_11Nov2024-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/GRACE-2L-r6-11Nov2024/2024-11-20-MP_GRACE_2L_r6_11Nov2024-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz -------------------------------------------------------------------------------- /models/GRACE-2L-r6-11Nov2024/2024-11-20-MP_GRACE_2L_r6_11Nov2024-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2024-11-20 16:05:17", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "MP_GRACE_2L_r6_11Nov2024", 5 | "checkpoint": "11Nov2024", 6 | "device": "cuda", 7 | "dtype": "float64", 8 | "versions": { 9 | "numpy": "1.26.4", 10 | "tensorflow": "2.16.2", 11 | "tensorpotential": "0.4.4" 12 | }, 13 | "ase_optimizer": "FIRE", 14 | "ase_filter": "frechet", 15 | "if_two_stage_relax": true, 16 | "max_steps": 300, 17 | "force_max": 0.0001, 18 | "symprec": 1e-05, 19 | "enforce_relax_symm": true, 20 | "conductivity_broken_symm": false, 21 | "slurm_array_task_count": 16, 22 | "slurm_array_job_id": "production", 23 | "task_type": "LTC", 24 | "job_name": "MP_GRACE_2L_r6_11Nov2024-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 25 | "struct_data_path": "phononDB-PBE-structures.extxyz", 26 | "n_structures": 103 27 | } -------------------------------------------------------------------------------- /models/GRACE-2L-r6-11Nov2024/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "MP_GRACE_2L_r6_11Nov2024" 10 | 11 | json_file = "k_srme.json.gz" 12 | txt_path = "metrics.txt" 13 | in_file = "conductivity_*-???.json.gz" 14 | 15 | in_folder = f"2024-11-20-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 16 | 17 | # Comparing with the DFT results without 18 | # non-analytical correction term (NAC), which is in closer agreement with to MLP results 19 | DFT_RESULTS_FILE = DFT_NONAC_REF 20 | 21 | 22 | module_dir = os.path.dirname(__file__) 23 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 24 | out_path = f"{module_dir}/{in_folder}/{json_file}" 25 | 26 | 27 | # Read MLP results 28 | if not glob(in_pattern): 29 | if os.path.exists(out_path): 30 | df_mlp_results = pd.read_json(out_path).set_index(ID) 31 | else: 32 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 33 | 34 | 35 | # df_mlp_results.reset_index().to_json(out_path) 36 | 37 | # Read DFT results 38 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 39 | 40 | 41 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 42 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 43 | 44 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 45 | 46 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 47 | 48 | 49 | # Save results 50 | df_mlp_processed.round(5) 51 | df_mlp_processed.index.name = ID 52 | df_mlp_processed.reset_index().to_json(out_path) 53 | 54 | 55 | # Print 56 | pd.set_option("display.max_rows", None) 57 | pd.set_option("display.max_columns", None) 58 | df_mlp_print = df_mlp_filtered[ 59 | [ 60 | "desc", 61 | "SRME", 62 | "SRE", 63 | "kappa_TOT_ave", 64 | "DFT_kappa_TOT_ave", 65 | "imaginary_freqs", 66 | "errors", 67 | ] 68 | ] 69 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 70 | lambda x: x[0] if not pd.isna(x) else x 71 | ) 72 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 73 | lambda x: x[0] if not pd.isna(x) else x 74 | ) 75 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 76 | 77 | with open(txt_path, "w") as f: 78 | print(f"MODEL: {model_name}", file=f) 79 | print(f"\tmean SRME: {mSRME}", file=f) 80 | print(f"\tmean SRE: {mSRE}", file=f) 81 | 82 | print(df_mlp_print.round(4), file=f) 83 | 84 | 85 | df_mlp_print = df_mlp_print[ 86 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 87 | ] 88 | print(df_mlp_print.round(3)) 89 | 90 | print(f"MODEL: {model_name}") 91 | print(f"\tmean SRME: {mSRME}") 92 | print(f"\tmean SRE: {mSRE}") 93 | -------------------------------------------------------------------------------- /models/GRACE-2L-r6-11Nov2024/run_eval_parallel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | ## this script is for imitation of SLURM tasks array on local machine with multiple GPUs 5 | 6 | THREADS=8 # number of threads for OMP, MKL, NUMEXPR etc. To share resources on single machine 7 | 8 | export NGPU=4 # Set the total number of GPUs here 9 | 10 | model_name="MP_GRACE_2L_r6_11Nov2024" # just for information, model name is hardcoded in 1_test_srme.py 11 | 12 | export MODEL_NAME="${model_name}" 13 | echo "MODEL_NAME=${MODEL_NAME}" 14 | export MKL_NUM_THREADS=$THREADS 15 | export NUMEXPR_NUM_THREADS=$THREADS 16 | export OMP_NUM_THREADS=$THREADS 17 | 18 | export SLURM_ARRAY_TASK_COUNT=16 # slurm_array_task_count 19 | export SLURM_ARRAY_JOB_ID="production" 20 | export SLURM_JOB_ID="production" 21 | 22 | 23 | # Function to kill all child processes when the script receives a termination signal 24 | cleanup() { 25 | echo "Terminating all child processes..." 26 | pkill -P $$ 27 | exit 1 28 | } 29 | 30 | # Set trap to catch signals and trigger the cleanup function 31 | trap cleanup SIGINT SIGTERM 32 | 33 | 34 | # skip1 and skip2 are options to control execution 35 | # of first and second stages of workflow 36 | 37 | # Check if "skip1" is in the arguments 38 | skip1=false 39 | for arg in "$@"; do 40 | if [ "$arg" = "skip1" ]; then 41 | skip1=true 42 | break 43 | fi 44 | done 45 | 46 | skip2=false 47 | for arg in "$@"; do 48 | if [ "$arg" = "skip2" ]; then 49 | skip2=true 50 | break 51 | fi 52 | done 53 | 54 | exec > "${MODEL_NAME}.out" 2>&1 55 | echo "MODEL_NAME=${MODEL_NAME}" 56 | echo "SLURM_ARRAY_TASK_COUNT=${SLURM_ARRAY_TASK_COUNT}" 57 | 58 | if [ "$skip1" = false ]; then 59 | echo "Running 1_test_srme.py" 60 | for task_id in $(seq 0 $((SLURM_ARRAY_TASK_COUNT-1))) 61 | do 62 | CUDA_VISIBLE_DEVICES=$((task_id % NGPU)) SLURM_ARRAY_TASK_ID=${task_id} python 1_test_srme.py "$MODEL_NAME" & 63 | done 64 | wait 65 | else 66 | echo "Skip 1_force_sets" 67 | fi 68 | 69 | if [ "$skip2" = false ]; then 70 | echo "Running 2_evaluate" 71 | for task_id in $(seq 0 $((SLURM_ARRAY_TASK_COUNT-1))) 72 | do 73 | SLURM_ARRAY_TASK_ID=${task_id} python 2_evaluate.py "$MODEL_NAME" & 74 | done 75 | wait 76 | else 77 | echo "Skip 2_evaluate" 78 | fi 79 | -------------------------------------------------------------------------------- /models/M3GNet/1_test_srme.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | import warnings 4 | from typing import Literal, Any 5 | from collections.abc import Callable 6 | import traceback 7 | from copy import deepcopy 8 | from importlib.metadata import version 9 | import json 10 | 11 | import pandas as pd 12 | 13 | from tqdm import tqdm 14 | 15 | from ase.constraints import FixSymmetry 16 | from ase.filters import ExpCellFilter, FrechetCellFilter 17 | from ase.optimize import FIRE, LBFGS 18 | from ase.optimize.optimize import Optimizer 19 | from ase import Atoms 20 | from ase.io import read 21 | 22 | from k_srme import aseatoms2str, two_stage_relax, ID, STRUCTURES, NO_TILT_MASK 23 | from k_srme.utils import symm_name_map, get_spacegroup_number, check_imaginary_freqs 24 | from k_srme.conductivity import ( 25 | init_phono3py, 26 | get_fc2_and_freqs, 27 | get_fc3, 28 | calculate_conductivity, 29 | ) 30 | 31 | from matgl.ext.ase import M3GNetCalculator 32 | from matgl import load_model 33 | 34 | 35 | warnings.filterwarnings("ignore", category=DeprecationWarning, module="spglib") 36 | 37 | 38 | # EDITABLE CONFIG 39 | model_name = "M3GNet" 40 | checkpoint = "M3GNet-MP-2021.2.8-PES" 41 | 42 | 43 | pot = load_model(checkpoint) 44 | calc = M3GNetCalculator(potential=pot) 45 | 46 | 47 | # Relaxation parameters 48 | ase_optimizer: Literal["FIRE", "LBFGS", "BFGS"] = "FIRE" 49 | ase_filter: Literal["frechet", "exp"] = "frechet" 50 | if_two_stage_relax = True # Use two-stage relaxation enforcing symmetries 51 | max_steps = 300 52 | force_max = 1e-4 # Run until the forces are smaller than this in eV/A 53 | 54 | # Symmetry parameters 55 | # symmetry precision for enforcing relaxation and conductivity calculation 56 | symprec = 1e-5 57 | # Enforce symmetry with during relaxation if broken 58 | enforce_relax_symm = True 59 | # Conductivity to be calculated if symmetry group changed during relaxation 60 | conductivity_broken_symm = False 61 | prog_bar = True 62 | save_forces = True # Save force sets to file 63 | 64 | 65 | slurm_array_task_count = int( 66 | os.getenv( 67 | "K_SRME_RESTART_ARRAY_TASK_COUNT", os.getenv("SLURM_ARRAY_TASK_COUNT", "1") 68 | ) 69 | ) 70 | slurm_array_task_id = int(os.getenv("SLURM_ARRAY_TASK_ID", "0")) 71 | slurm_array_job_id = os.getenv("SLURM_ARRAY_JOB_ID", os.getenv("SLURM_JOB_ID", "debug")) 72 | slurm_array_task_min = int( 73 | os.getenv("K_SRME_RESTART_ARRAY_TASK_MIN", os.getenv("SLURM_ARRAY_TASK_MIN", "0")) 74 | ) 75 | 76 | 77 | task_type = "LTC" # lattice thermal conductivity 78 | job_name = f"{model_name}-phononDB-{task_type}-{ase_optimizer}{'_2SR' if if_two_stage_relax else ''}_force{force_max}_sym{symprec}" 79 | module_dir = os.path.dirname(__file__) 80 | out_dir = os.getenv( 81 | "SBATCH_OUTPUT", 82 | f"{module_dir}/{datetime.datetime.now().strftime('%Y-%m-%d')}-{job_name}", 83 | ) 84 | os.makedirs(out_dir, exist_ok=True) 85 | 86 | out_path = ( 87 | f"{out_dir}/conductivity_{slurm_array_job_id}-{slurm_array_task_id:>03}.json.gz" 88 | ) 89 | 90 | 91 | timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 92 | struct_data_path = STRUCTURES 93 | print(f"\nJob {job_name} started {timestamp}") 94 | 95 | 96 | print(f"Read data from {struct_data_path}") 97 | atoms_list: list[Atoms] = read(struct_data_path, format="extxyz", index=":") 98 | 99 | run_params = { 100 | "timestamp": timestamp, 101 | "k_srme_version": version("k_srme"), 102 | "model_name": model_name, 103 | "checkpoint": checkpoint, 104 | "versions": {dep: version(dep) for dep in ("numpy", "torch")}, 105 | "ase_optimizer": ase_optimizer, 106 | "ase_filter": ase_filter, 107 | "if_two_stage_relax": if_two_stage_relax, 108 | "max_steps": max_steps, 109 | "force_max": force_max, 110 | "symprec": symprec, 111 | "enforce_relax_symm": enforce_relax_symm, 112 | "conductivity_broken_symm": conductivity_broken_symm, 113 | "slurm_array_task_count": slurm_array_task_count, 114 | "slurm_array_job_id": slurm_array_job_id, 115 | "task_type": task_type, 116 | "job_name": job_name, 117 | "struct_data_path": os.path.basename(struct_data_path), 118 | "n_structures": len(atoms_list), 119 | } 120 | 121 | if slurm_array_task_id == slurm_array_task_min: 122 | with open(f"{out_dir}/run_params.json", "w") as f: 123 | json.dump(run_params, f, indent=4) 124 | 125 | if slurm_array_job_id == "debug": 126 | atoms_list = atoms_list[:5] 127 | print("Running in DEBUG mode.") 128 | elif slurm_array_task_count > 1: 129 | # Split the atoms_list into slurm_array_task_count parts trying to make even runtime 130 | atoms_list = atoms_list[ 131 | slurm_array_task_id - slurm_array_task_min :: slurm_array_task_count 132 | ] 133 | 134 | 135 | # Set up the relaxation and force set calculation 136 | filter_cls: Callable[[Atoms], Atoms] = { 137 | "frechet": FrechetCellFilter, 138 | "exp": ExpCellFilter, 139 | }[ase_filter] 140 | optim_cls: Callable[..., Optimizer] = {"FIRE": FIRE, "LBFGS": LBFGS}[ase_optimizer] 141 | 142 | 143 | force_results: dict[str, dict[str, Any]] = {} 144 | kappa_results: dict[str, dict[str, Any]] = {} 145 | 146 | 147 | tqdm_bar = tqdm(atoms_list, desc="Conductivity calculation: ", disable=not prog_bar) 148 | 149 | for atoms in tqdm_bar: 150 | mat_id = atoms.info[ID] 151 | init_info = deepcopy(atoms.info) 152 | mat_name = atoms.info["name"] 153 | mat_desc = f"{mat_name}-{symm_name_map[atoms.info['symm.no']]}" 154 | info_dict = { 155 | "desc": mat_desc, 156 | "name": mat_name, 157 | "initial_space_group_number": atoms.info["symm.no"], 158 | "errors": [], 159 | "error_traceback": [], 160 | } 161 | 162 | tqdm_bar.set_postfix_str(mat_desc, refresh=True) 163 | 164 | # Relaxation 165 | try: 166 | atoms.calc = calc 167 | if max_steps > 0: 168 | if not if_two_stage_relax: 169 | if enforce_relax_symm: 170 | atoms.set_constraint(FixSymmetry(atoms)) 171 | filtered_atoms = filter_cls(atoms, mask=NO_TILT_MASK) 172 | else: 173 | filtered_atoms = filter_cls(atoms) 174 | 175 | optimizer = optim_cls( 176 | filtered_atoms, logfile=f"{out_dir}/relax_{slurm_array_task_id}.log" 177 | ) 178 | optimizer.run(fmax=force_max, steps=max_steps) 179 | 180 | reached_max_steps = False 181 | if optimizer.step == max_steps: 182 | reached_max_steps = True 183 | print( 184 | f"Material {mat_desc=}, {mat_id=} reached max step {max_steps=} during relaxation." 185 | ) 186 | 187 | # maximum residual stress component in for xx,yy,zz and xy,yz,xz components separately 188 | # result is a array of 2 elements 189 | max_stress = atoms.get_stress().reshape((2, 3), order="C").max(axis=1) 190 | 191 | atoms.calc = None 192 | atoms.constraints = None 193 | atoms.info = init_info | atoms.info 194 | 195 | symm_no = get_spacegroup_number(atoms, symprec=symprec) 196 | 197 | relax_dict = { 198 | "structure": aseatoms2str(atoms), 199 | "max_stress": max_stress, 200 | "reached_max_steps": reached_max_steps, 201 | "relaxed_space_group_number": symm_no, 202 | "broken_symmetry": symm_no 203 | != init_info["initial_space_group_number"], 204 | } 205 | 206 | else: 207 | atoms, relax_dict = two_stage_relax( 208 | atoms, 209 | fmax_stage1=force_max, 210 | fmax_stage2=force_max, 211 | steps_stage1=max_steps, 212 | steps_stage2=max_steps, 213 | Optimizer=optim_cls, 214 | Filter=filter_cls, 215 | allow_tilt=False, 216 | log=f"{out_dir}/relax_{slurm_array_task_id}.log", 217 | enforce_symmetry=enforce_relax_symm, 218 | ) 219 | 220 | atoms.calc = None 221 | 222 | except Exception as exc: 223 | warnings.warn(f"Failed to relax {mat_name=}, {mat_id=}: {exc!r}") 224 | traceback.print_exc() 225 | info_dict["errors"].append(f"RelaxError: {exc!r}") 226 | info_dict["error_traceback"].append(traceback.format_exc()) 227 | kappa_results[mat_id] = info_dict 228 | continue 229 | 230 | # Calculation of force sets 231 | try: 232 | ph3 = init_phono3py(atoms, log=False, symprec=symprec) 233 | 234 | ph3, fc2_set, freqs = get_fc2_and_freqs( 235 | ph3, 236 | calculator=calc, 237 | log=False, 238 | pbar_kwargs={"leave": False, "disable": not prog_bar}, 239 | ) 240 | 241 | imaginary_freqs = check_imaginary_freqs(freqs) 242 | freqs_dict = {"imaginary_freqs": imaginary_freqs, "frequencies": freqs} 243 | 244 | # if conductivity condition is met, calculate fc3 245 | ltc_condition = not imaginary_freqs and ( 246 | not relax_dict["broken_symmetry"] or conductivity_broken_symm 247 | ) 248 | 249 | if ltc_condition: 250 | ph3, fc3_set = get_fc3( 251 | ph3, 252 | calculator=calc, 253 | log=False, 254 | pbar_kwargs={"leave": False, "disable": not prog_bar}, 255 | ) 256 | 257 | else: 258 | fc3_set = [] 259 | 260 | if save_forces: 261 | force_results[mat_id] = {"fc2_set": fc2_set, "fc3_set": fc3_set} 262 | 263 | if not ltc_condition: 264 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict 265 | continue 266 | 267 | except Exception as exc: 268 | warnings.warn(f"Failed to calculate force sets {mat_id}: {exc!r}") 269 | traceback.print_exc() 270 | info_dict["errors"].append(f"ForceConstantError: {exc!r}") 271 | info_dict["error_traceback"].append(traceback.format_exc()) 272 | kappa_results[mat_id] = info_dict | relax_dict 273 | continue 274 | 275 | # Calculation of conductivity 276 | try: 277 | ph3, kappa_dict = calculate_conductivity(ph3, log=False) 278 | 279 | except Exception as exc: 280 | warnings.warn(f"Failed to calculate conductivity {mat_id}: {exc!r}") 281 | traceback.print_exc() 282 | info_dict["errors"].append(f"ConductivityError: {exc!r}") 283 | info_dict["error_traceback"].append(traceback.format_exc()) 284 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict 285 | continue 286 | 287 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict | kappa_dict 288 | 289 | 290 | df_kappa = pd.DataFrame(kappa_results).T 291 | df_kappa.index.name = ID 292 | df_kappa.reset_index().to_json(out_path) 293 | 294 | 295 | if save_forces: 296 | force_out_path = ( 297 | f"{out_dir}/force_sets_{slurm_array_job_id}-{slurm_array_task_id:>03}.json.gz" 298 | ) 299 | df_force = pd.DataFrame(force_results).T 300 | df_force = pd.concat([df_kappa, df_force], axis=1) 301 | df_force.index.name = ID 302 | df_force.reset_index().to_json(force_out_path) 303 | -------------------------------------------------------------------------------- /models/M3GNet/2024-11-09-M3GNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/M3GNet/2024-11-09-M3GNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz -------------------------------------------------------------------------------- /models/M3GNet/2024-11-09-M3GNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2024-11-09 13:49:05", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "M3GNet", 5 | "checkpoint": "M3GNet-MP-2021.2.8-PES", 6 | "versions": { 7 | "numpy": "1.26.4", 8 | "torch": "2.4.0" 9 | }, 10 | "ase_optimizer": "FIRE", 11 | "ase_filter": "frechet", 12 | "if_two_stage_relax": true, 13 | "max_steps": 300, 14 | "force_max": 0.0001, 15 | "symprec": 1e-05, 16 | "enforce_relax_symm": true, 17 | "conductivity_broken_symm": false, 18 | "slurm_array_task_count": 34, 19 | "slurm_array_job_id": "1649660", 20 | "task_type": "LTC", 21 | "job_name": "M3GNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 22 | "struct_data_path": "phononDB-PBE-structures.extxyz", 23 | "n_structures": 103 24 | } -------------------------------------------------------------------------------- /models/M3GNet/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "M3GNet" 10 | 11 | json_file = "k_srme.json.gz" 12 | txt_path = "metrics.txt" 13 | in_file = "conductivity_*-???.json.gz" 14 | 15 | in_folder = f"2024-11-09-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 16 | 17 | # Comparing with the DFT results without 18 | # non-analytical correction term (NAC) 19 | DFT_RESULTS_FILE = DFT_NONAC_REF 20 | 21 | 22 | module_dir = os.path.dirname(__file__) 23 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 24 | out_path = f"{module_dir}/{in_folder}/{json_file}" 25 | 26 | 27 | # Read MLP results 28 | if not glob(in_pattern): 29 | if os.path.exists(out_path): 30 | df_mlp_results = pd.read_json(out_path).set_index(ID) 31 | else: 32 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 33 | 34 | 35 | # df_mlp_results.reset_index().to_json(out_path) 36 | 37 | # Read DFT results 38 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 39 | 40 | 41 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 42 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 43 | 44 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 45 | 46 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 47 | 48 | 49 | # Save results 50 | df_mlp_processed.round(5) 51 | df_mlp_processed.index.name = ID 52 | df_mlp_processed.reset_index().to_json(out_path) 53 | 54 | 55 | # Print 56 | pd.set_option("display.max_rows", None) 57 | pd.set_option("display.max_columns", None) 58 | df_mlp_print = df_mlp_filtered[ 59 | [ 60 | "desc", 61 | "SRME", 62 | "SRE", 63 | "kappa_TOT_ave", 64 | "DFT_kappa_TOT_ave", 65 | "imaginary_freqs", 66 | "errors", 67 | ] 68 | ] 69 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 70 | lambda x: x[0] if not pd.isna(x) else x 71 | ) 72 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 73 | lambda x: x[0] if not pd.isna(x) else x 74 | ) 75 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 76 | 77 | with open(txt_path, "w") as f: 78 | print(f"MODEL: {model_name}", file=f) 79 | print(f"\tmean SRME: {mSRME}", file=f) 80 | print(f"\tmean SRE: {mSRE}", file=f) 81 | 82 | print(df_mlp_print.round(4), file=f) 83 | 84 | 85 | df_mlp_print = df_mlp_print[ 86 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 87 | ] 88 | print(df_mlp_print.round(3)) 89 | 90 | print(f"MODEL: {model_name}") 91 | print(f"\tmean SRME: {mSRME}") 92 | print(f"\tmean SRE: {mSRE}") 93 | -------------------------------------------------------------------------------- /models/MACE-L/1_test_srme.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | import warnings 4 | from typing import Literal, Any 5 | from collections.abc import Callable 6 | import traceback 7 | from copy import deepcopy 8 | from importlib.metadata import version 9 | import json 10 | 11 | import pandas as pd 12 | 13 | from tqdm import tqdm 14 | 15 | from ase.constraints import FixSymmetry 16 | from ase.filters import ExpCellFilter, FrechetCellFilter 17 | from ase.optimize import FIRE, LBFGS 18 | from ase.optimize.optimize import Optimizer 19 | from ase import Atoms 20 | from ase.io import read 21 | 22 | from k_srme import aseatoms2str, two_stage_relax, ID, STRUCTURES, NO_TILT_MASK 23 | from k_srme.utils import symm_name_map, get_spacegroup_number, check_imaginary_freqs 24 | from k_srme.conductivity import ( 25 | init_phono3py, 26 | get_fc2_and_freqs, 27 | get_fc3, 28 | calculate_conductivity, 29 | ) 30 | 31 | from mace.calculators import mace_mp 32 | import torch 33 | 34 | warnings.filterwarnings("ignore", category=DeprecationWarning, module="spglib") 35 | 36 | 37 | # EDITABLE CONFIG 38 | model_name = "MACE" 39 | checkpoint = "large" 40 | 41 | device = "cuda" if torch.cuda.is_available() else "cpu" 42 | dtype = "float64" 43 | calc = mace_mp(model=checkpoint, device=device, default_dtype=dtype) 44 | 45 | # Relaxation parameters 46 | ase_optimizer: Literal["FIRE", "LBFGS", "BFGS"] = "FIRE" 47 | ase_filter: Literal["frechet", "exp"] = "frechet" 48 | if_two_stage_relax = True # Use two-stage relaxation enforcing symmetries 49 | max_steps = 300 50 | force_max = 1e-4 # Run until the forces are smaller than this in eV/A 51 | 52 | # Symmetry parameters 53 | # symmetry precision for enforcing relaxation and conductivity calculation 54 | symprec = 1e-5 55 | # Enforce symmetry with during relaxation if broken 56 | enforce_relax_symm = True 57 | # Conductivity to be calculated if symmetry group changed during relaxation 58 | conductivity_broken_symm = False 59 | prog_bar = True 60 | save_forces = True # Save force sets to file 61 | 62 | 63 | slurm_array_task_count = int( 64 | os.getenv( 65 | "K_SRME_RESTART_ARRAY_TASK_COUNT", os.getenv("SLURM_ARRAY_TASK_COUNT", "1") 66 | ) 67 | ) 68 | slurm_array_task_id = int(os.getenv("SLURM_ARRAY_TASK_ID", "0")) 69 | slurm_array_job_id = os.getenv("SLURM_ARRAY_JOB_ID", os.getenv("SLURM_JOB_ID", "debug")) 70 | slurm_array_task_min = int( 71 | os.getenv("K_SRME_RESTART_ARRAY_TASK_MIN", os.getenv("SLURM_ARRAY_TASK_MIN", "0")) 72 | ) 73 | 74 | 75 | task_type = "LTC" # lattice thermal conductivity 76 | job_name = f"{model_name}-phononDB-{task_type}-{ase_optimizer}{'_2SR' if if_two_stage_relax else ''}_force{force_max}_sym{symprec}" 77 | module_dir = os.path.dirname(__file__) 78 | out_dir = os.getenv( 79 | "SBATCH_OUTPUT", 80 | f"{module_dir}/{datetime.datetime.now().strftime('%Y-%m-%d')}-{job_name}", 81 | ) 82 | os.makedirs(out_dir, exist_ok=True) 83 | 84 | out_path = ( 85 | f"{out_dir}/conductivity_{slurm_array_job_id}-{slurm_array_task_id:>03}.json.gz" 86 | ) 87 | 88 | 89 | timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 90 | struct_data_path = STRUCTURES 91 | print(f"\nJob {job_name} started {timestamp}") 92 | 93 | 94 | print(f"Read data from {struct_data_path}") 95 | atoms_list: list[Atoms] = read(struct_data_path, format="extxyz", index=":") 96 | 97 | run_params = { 98 | "timestamp": timestamp, 99 | "k_srme_version": version("k_srme"), 100 | "model_name": model_name, 101 | "checkpoint": checkpoint, 102 | "device": device, 103 | "dtype": dtype, 104 | "versions": {dep: version(dep) for dep in ("numpy", "torch")}, 105 | "ase_optimizer": ase_optimizer, 106 | "ase_filter": ase_filter, 107 | "if_two_stage_relax": if_two_stage_relax, 108 | "max_steps": max_steps, 109 | "force_max": force_max, 110 | "symprec": symprec, 111 | "enforce_relax_symm": enforce_relax_symm, 112 | "conductivity_broken_symm": conductivity_broken_symm, 113 | "slurm_array_task_count": slurm_array_task_count, 114 | "slurm_array_job_id": slurm_array_job_id, 115 | "task_type": task_type, 116 | "job_name": job_name, 117 | "struct_data_path": os.path.basename(struct_data_path), 118 | "n_structures": len(atoms_list), 119 | } 120 | 121 | if slurm_array_task_id == slurm_array_task_min: 122 | with open(f"{out_dir}/run_params.json", "w") as f: 123 | json.dump(run_params, f, indent=4) 124 | 125 | if slurm_array_job_id == "debug": 126 | atoms_list = atoms_list[:5] 127 | print("Running in DEBUG mode.") 128 | elif slurm_array_task_count > 1: 129 | # Split the atoms_list into slurm_array_task_count parts trying to make even runtime 130 | atoms_list = atoms_list[ 131 | slurm_array_task_id - slurm_array_task_min :: slurm_array_task_count 132 | ] 133 | 134 | 135 | # Set up the relaxation and force set calculation 136 | filter_cls: Callable[[Atoms], Atoms] = { 137 | "frechet": FrechetCellFilter, 138 | "exp": ExpCellFilter, 139 | }[ase_filter] 140 | optim_cls: Callable[..., Optimizer] = {"FIRE": FIRE, "LBFGS": LBFGS}[ase_optimizer] 141 | 142 | 143 | force_results: dict[str, dict[str, Any]] = {} 144 | kappa_results: dict[str, dict[str, Any]] = {} 145 | 146 | 147 | tqdm_bar = tqdm(atoms_list, desc="Conductivity calculation: ", disable=not prog_bar) 148 | 149 | for atoms in tqdm_bar: 150 | mat_id = atoms.info[ID] 151 | init_info = deepcopy(atoms.info) 152 | mat_name = atoms.info["name"] 153 | mat_desc = f"{mat_name}-{symm_name_map[atoms.info['symm.no']]}" 154 | info_dict = { 155 | "desc": mat_desc, 156 | "name": mat_name, 157 | "initial_space_group_number": atoms.info["symm.no"], 158 | "errors": [], 159 | "error_traceback": [], 160 | } 161 | 162 | tqdm_bar.set_postfix_str(mat_desc, refresh=True) 163 | 164 | # Relaxation 165 | try: 166 | atoms.calc = calc 167 | if max_steps > 0: 168 | if not if_two_stage_relax: 169 | if enforce_relax_symm: 170 | atoms.set_constraint(FixSymmetry(atoms)) 171 | filtered_atoms = filter_cls(atoms, mask=NO_TILT_MASK) 172 | else: 173 | filtered_atoms = filter_cls(atoms) 174 | 175 | optimizer = optim_cls( 176 | filtered_atoms, logfile=f"{out_dir}/relax_{slurm_array_task_id}.log" 177 | ) 178 | optimizer.run(fmax=force_max, steps=max_steps) 179 | 180 | reached_max_steps = False 181 | if optimizer.step == max_steps: 182 | reached_max_steps = True 183 | print( 184 | f"Material {mat_desc=}, {mat_id=} reached max step {max_steps=} during relaxation." 185 | ) 186 | 187 | # maximum residual stress component in for xx,yy,zz and xy,yz,xz components separately 188 | # result is a array of 2 elements 189 | max_stress = atoms.get_stress().reshape((2, 3), order="C").max(axis=1) 190 | 191 | atoms.calc = None 192 | atoms.constraints = None 193 | atoms.info = init_info | atoms.info 194 | 195 | symm_no = get_spacegroup_number(atoms, symprec=symprec) 196 | 197 | relax_dict = { 198 | "structure": aseatoms2str(atoms), 199 | "max_stress": max_stress, 200 | "reached_max_steps": reached_max_steps, 201 | "relaxed_space_group_number": symm_no, 202 | "broken_symmetry": symm_no 203 | != init_info["initial_space_group_number"], 204 | } 205 | 206 | else: 207 | atoms, relax_dict = two_stage_relax( 208 | atoms, 209 | fmax_stage1=force_max, 210 | fmax_stage2=force_max, 211 | steps_stage1=max_steps, 212 | steps_stage2=max_steps, 213 | Optimizer=optim_cls, 214 | Filter=filter_cls, 215 | allow_tilt=False, 216 | log=f"{out_dir}/relax_{slurm_array_task_id}.log", 217 | enforce_symmetry=enforce_relax_symm, 218 | ) 219 | 220 | atoms.calc = None 221 | 222 | except Exception as exc: 223 | warnings.warn(f"Failed to relax {mat_name=}, {mat_id=}: {exc!r}") 224 | traceback.print_exc() 225 | info_dict["errors"].append(f"RelaxError: {exc!r}") 226 | info_dict["error_traceback"].append(traceback.format_exc()) 227 | kappa_results[mat_id] = info_dict 228 | continue 229 | 230 | # Calculation of force sets 231 | try: 232 | ph3 = init_phono3py(atoms, log=False, symprec=symprec) 233 | 234 | ph3, fc2_set, freqs = get_fc2_and_freqs( 235 | ph3, 236 | calculator=calc, 237 | log=False, 238 | pbar_kwargs={"leave": False, "disable": not prog_bar}, 239 | ) 240 | 241 | imaginary_freqs = check_imaginary_freqs(freqs) 242 | freqs_dict = {"imaginary_freqs": imaginary_freqs, "frequencies": freqs} 243 | 244 | # if conductivity condition is met, calculate fc3 245 | ltc_condition = not imaginary_freqs and ( 246 | not relax_dict["broken_symmetry"] or conductivity_broken_symm 247 | ) 248 | 249 | if ltc_condition: 250 | ph3, fc3_set = get_fc3( 251 | ph3, 252 | calculator=calc, 253 | log=False, 254 | pbar_kwargs={"leave": False, "disable": not prog_bar}, 255 | ) 256 | 257 | else: 258 | fc3_set = [] 259 | 260 | if save_forces: 261 | force_results[mat_id] = {"fc2_set": fc2_set, "fc3_set": fc3_set} 262 | 263 | if not ltc_condition: 264 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict 265 | continue 266 | 267 | except Exception as exc: 268 | warnings.warn(f"Failed to calculate force sets {mat_id}: {exc!r}") 269 | traceback.print_exc() 270 | info_dict["errors"].append(f"ForceConstantError: {exc!r}") 271 | info_dict["error_traceback"].append(traceback.format_exc()) 272 | kappa_results[mat_id] = info_dict | relax_dict 273 | continue 274 | 275 | # Calculation of conductivity 276 | try: 277 | ph3, kappa_dict = calculate_conductivity(ph3, log=False) 278 | 279 | except Exception as exc: 280 | warnings.warn(f"Failed to calculate conductivity {mat_id}: {exc!r}") 281 | traceback.print_exc() 282 | info_dict["errors"].append(f"ConductivityError: {exc!r}") 283 | info_dict["error_traceback"].append(traceback.format_exc()) 284 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict 285 | continue 286 | 287 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict | kappa_dict 288 | 289 | 290 | df_kappa = pd.DataFrame(kappa_results).T 291 | df_kappa.index.name = ID 292 | df_kappa.reset_index().to_json(out_path) 293 | 294 | 295 | if save_forces: 296 | force_out_path = ( 297 | f"{out_dir}/force_sets_{slurm_array_job_id}-{slurm_array_task_id:>03}.json.gz" 298 | ) 299 | df_force = pd.DataFrame(force_results).T 300 | df_force = pd.concat([df_kappa, df_force], axis=1) 301 | df_force.index.name = ID 302 | df_force.reset_index().to_json(force_out_path) 303 | -------------------------------------------------------------------------------- /models/MACE-L/2024-11-09-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-L/2024-11-09-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz -------------------------------------------------------------------------------- /models/MACE-L/2024-11-09-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2024-11-09 13:52:42", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "MACE", 5 | "checkpoint": "large", 6 | "device": "cpu", 7 | "dtype": "float64", 8 | "versions": { 9 | "numpy": "1.26.4", 10 | "torch": "2.4.1+cu124" 11 | }, 12 | "ase_optimizer": "FIRE", 13 | "ase_filter": "frechet", 14 | "if_two_stage_relax": true, 15 | "max_steps": 300, 16 | "force_max": 0.0001, 17 | "symprec": 1e-05, 18 | "enforce_relax_symm": true, 19 | "conductivity_broken_symm": false, 20 | "slurm_array_task_count": 34, 21 | "slurm_array_job_id": "1649661", 22 | "task_type": "LTC", 23 | "job_name": "MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 24 | "struct_data_path": "phononDB-PBE-structures.extxyz", 25 | "n_structures": 103 26 | } -------------------------------------------------------------------------------- /models/MACE-L/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "MACE" 10 | 11 | json_file = "k_srme.json.gz" 12 | txt_path = "metrics.txt" 13 | in_file = "conductivity_*-???.json.gz" 14 | 15 | in_folder = f"2024-11-09-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 16 | 17 | # Comparing with the DFT results without 18 | # non-analytical correction term (NAC) 19 | DFT_RESULTS_FILE = DFT_NONAC_REF 20 | 21 | 22 | module_dir = os.path.dirname(__file__) 23 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 24 | out_path = f"{module_dir}/{in_folder}/{json_file}" 25 | 26 | 27 | # Read MLP results 28 | if not glob(in_pattern): 29 | if os.path.exists(out_path): 30 | df_mlp_results = pd.read_json(out_path).set_index(ID) 31 | else: 32 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 33 | 34 | 35 | # df_mlp_results.reset_index().to_json(out_path) 36 | 37 | # Read DFT results 38 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 39 | 40 | 41 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 42 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 43 | 44 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 45 | 46 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 47 | 48 | 49 | # Save results 50 | df_mlp_processed.round(5) 51 | df_mlp_processed.index.name = ID 52 | df_mlp_processed.reset_index().to_json(out_path) 53 | 54 | 55 | # Print 56 | pd.set_option("display.max_rows", None) 57 | pd.set_option("display.max_columns", None) 58 | df_mlp_print = df_mlp_filtered[ 59 | [ 60 | "desc", 61 | "SRME", 62 | "SRE", 63 | "kappa_TOT_ave", 64 | "DFT_kappa_TOT_ave", 65 | "imaginary_freqs", 66 | "errors", 67 | ] 68 | ] 69 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 70 | lambda x: x[0] if not pd.isna(x) else x 71 | ) 72 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 73 | lambda x: x[0] if not pd.isna(x) else x 74 | ) 75 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 76 | 77 | with open(txt_path, "w") as f: 78 | print(f"MODEL: {model_name}", file=f) 79 | print(f"\tmean SRME: {mSRME}", file=f) 80 | print(f"\tmean SRE: {mSRE}", file=f) 81 | 82 | print(df_mlp_print.round(4), file=f) 83 | 84 | 85 | df_mlp_print = df_mlp_print[ 86 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 87 | ] 88 | print(df_mlp_print.round(3)) 89 | 90 | print(f"MODEL: {model_name}") 91 | print(f"\tmean SRME: {mSRME}") 92 | print(f"\tmean SRE: {mSRE}") 93 | -------------------------------------------------------------------------------- /models/MACE-L2/2024-11-09-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-L2/2024-11-09-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz -------------------------------------------------------------------------------- /models/MACE-L2/2024-11-09-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2024-11-09 13:56:54", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "MACE", 5 | "checkpoint": "https://github.com/ACEsuit/mace-mp/releases/download/mace_mp_0/2024-01-07-mace-128-L2_epoch-199.model", 6 | "device": "cpu", 7 | "dtype": "float64", 8 | "versions": { 9 | "numpy": "1.26.4", 10 | "torch": "2.4.1+cu124" 11 | }, 12 | "ase_optimizer": "FIRE", 13 | "ase_filter": "frechet", 14 | "if_two_stage_relax": true, 15 | "max_steps": 300, 16 | "force_max": 0.0001, 17 | "symprec": 1e-05, 18 | "enforce_relax_symm": true, 19 | "conductivity_broken_symm": false, 20 | "slurm_array_task_count": 34, 21 | "slurm_array_job_id": "1649662", 22 | "task_type": "LTC", 23 | "job_name": "MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 24 | "struct_data_path": "phononDB-PBE-structures.extxyz", 25 | "n_structures": 103 26 | } -------------------------------------------------------------------------------- /models/MACE-L2/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "MACE" 10 | 11 | json_file = "k_srme.json.gz" 12 | txt_path = "metrics.txt" 13 | in_file = "conductivity_*-???.json.gz" 14 | 15 | in_folder = f"2024-11-09-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 16 | 17 | # Comparing with the DFT results without 18 | # non-analytical correction term (NAC) 19 | DFT_RESULTS_FILE = DFT_NONAC_REF 20 | 21 | 22 | module_dir = os.path.dirname(__file__) 23 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 24 | out_path = f"{module_dir}/{in_folder}/{json_file}" 25 | 26 | 27 | # Read MLP results 28 | if not glob(in_pattern): 29 | if os.path.exists(out_path): 30 | df_mlp_results = pd.read_json(out_path).set_index(ID) 31 | else: 32 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 33 | 34 | 35 | # df_mlp_results.reset_index().to_json(out_path) 36 | 37 | # Read DFT results 38 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 39 | 40 | 41 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 42 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 43 | 44 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 45 | 46 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 47 | 48 | 49 | # Save results 50 | df_mlp_processed.round(5) 51 | df_mlp_processed.index.name = ID 52 | df_mlp_processed.reset_index().to_json(out_path) 53 | 54 | 55 | # Print 56 | pd.set_option("display.max_rows", None) 57 | pd.set_option("display.max_columns", None) 58 | df_mlp_print = df_mlp_filtered[ 59 | [ 60 | "desc", 61 | "SRME", 62 | "SRE", 63 | "kappa_TOT_ave", 64 | "DFT_kappa_TOT_ave", 65 | "imaginary_freqs", 66 | "errors", 67 | ] 68 | ] 69 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 70 | lambda x: x[0] if not pd.isna(x) else x 71 | ) 72 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 73 | lambda x: x[0] if not pd.isna(x) else x 74 | ) 75 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 76 | 77 | with open(txt_path, "w") as f: 78 | print(f"MODEL: {model_name}", file=f) 79 | print(f"\tmean SRME: {mSRME}", file=f) 80 | print(f"\tmean SRE: {mSRE}", file=f) 81 | 82 | print(df_mlp_print.round(4), file=f) 83 | 84 | 85 | df_mlp_print = df_mlp_print[ 86 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 87 | ] 88 | print(df_mlp_print.round(3)) 89 | 90 | print(f"MODEL: {model_name}") 91 | print(f"\tmean SRME: {mSRME}") 92 | print(f"\tmean SRE: {mSRE}") 93 | -------------------------------------------------------------------------------- /models/MACE-M/1_test_srme.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | import warnings 4 | from typing import Literal, Any 5 | from collections.abc import Callable 6 | import traceback 7 | from copy import deepcopy 8 | from importlib.metadata import version 9 | import json 10 | 11 | import pandas as pd 12 | 13 | from tqdm import tqdm 14 | 15 | from ase.constraints import FixSymmetry 16 | from ase.filters import ExpCellFilter, FrechetCellFilter 17 | from ase.optimize import FIRE, LBFGS 18 | from ase.optimize.optimize import Optimizer 19 | from ase import Atoms 20 | from ase.io import read 21 | 22 | from k_srme import aseatoms2str, two_stage_relax, ID, STRUCTURES, NO_TILT_MASK 23 | from k_srme.utils import symm_name_map, get_spacegroup_number, check_imaginary_freqs 24 | from k_srme.conductivity import ( 25 | init_phono3py, 26 | get_fc2_and_freqs, 27 | get_fc3, 28 | calculate_conductivity, 29 | ) 30 | 31 | from mace.calculators import mace_mp 32 | import torch 33 | 34 | warnings.filterwarnings("ignore", category=DeprecationWarning, module="spglib") 35 | 36 | 37 | # EDITABLE CONFIG 38 | model_name = "MACE" 39 | checkpoint = "medium" 40 | 41 | device = "cuda" if torch.cuda.is_available() else "cpu" 42 | dtype = "float64" 43 | calc = mace_mp(model=checkpoint, device=device, default_dtype=dtype) 44 | 45 | # Relaxation parameters 46 | ase_optimizer: Literal["FIRE", "LBFGS", "BFGS"] = "FIRE" 47 | ase_filter: Literal["frechet", "exp"] = "frechet" 48 | if_two_stage_relax = True # Use two-stage relaxation enforcing symmetries 49 | max_steps = 300 50 | force_max = 1e-4 # Run until the forces are smaller than this in eV/A 51 | 52 | # Symmetry parameters 53 | # symmetry precision for enforcing relaxation and conductivity calculation 54 | symprec = 1e-5 55 | # Enforce symmetry with during relaxation if broken 56 | enforce_relax_symm = True 57 | # Conductivity to be calculated if symmetry group changed during relaxation 58 | conductivity_broken_symm = False 59 | prog_bar = True 60 | save_forces = True # Save force sets to file 61 | 62 | 63 | slurm_array_task_count = int( 64 | os.getenv( 65 | "K_SRME_RESTART_ARRAY_TASK_COUNT", os.getenv("SLURM_ARRAY_TASK_COUNT", "1") 66 | ) 67 | ) 68 | slurm_array_task_id = int(os.getenv("SLURM_ARRAY_TASK_ID", "0")) 69 | slurm_array_job_id = os.getenv("SLURM_ARRAY_JOB_ID", os.getenv("SLURM_JOB_ID", "debug")) 70 | slurm_array_task_min = int( 71 | os.getenv("K_SRME_RESTART_ARRAY_TASK_MIN", os.getenv("SLURM_ARRAY_TASK_MIN", "0")) 72 | ) 73 | 74 | 75 | task_type = "LTC" # lattice thermal conductivity 76 | job_name = f"{model_name}-phononDB-{task_type}-{ase_optimizer}{'_2SR' if if_two_stage_relax else ''}_force{force_max}_sym{symprec}" 77 | module_dir = os.path.dirname(__file__) 78 | out_dir = os.getenv( 79 | "SBATCH_OUTPUT", 80 | f"{module_dir}/{datetime.datetime.now().strftime('%Y-%m-%d')}-{job_name}", 81 | ) 82 | os.makedirs(out_dir, exist_ok=True) 83 | 84 | out_path = ( 85 | f"{out_dir}/conductivity_{slurm_array_job_id}-{slurm_array_task_id:>03}.json.gz" 86 | ) 87 | 88 | 89 | timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 90 | struct_data_path = STRUCTURES 91 | print(f"\nJob {job_name} started {timestamp}") 92 | 93 | 94 | print(f"Read data from {struct_data_path}") 95 | atoms_list: list[Atoms] = read(struct_data_path, format="extxyz", index=":") 96 | 97 | run_params = { 98 | "timestamp": timestamp, 99 | "k_srme_version": version("k_srme"), 100 | "model_name": model_name, 101 | "checkpoint": checkpoint, 102 | "device": device, 103 | "dtype": dtype, 104 | "versions": {dep: version(dep) for dep in ("numpy", "torch")}, 105 | "ase_optimizer": ase_optimizer, 106 | "ase_filter": ase_filter, 107 | "if_two_stage_relax": if_two_stage_relax, 108 | "max_steps": max_steps, 109 | "force_max": force_max, 110 | "symprec": symprec, 111 | "enforce_relax_symm": enforce_relax_symm, 112 | "conductivity_broken_symm": conductivity_broken_symm, 113 | "slurm_array_task_count": slurm_array_task_count, 114 | "slurm_array_job_id": slurm_array_job_id, 115 | "task_type": task_type, 116 | "job_name": job_name, 117 | "struct_data_path": os.path.basename(struct_data_path), 118 | "n_structures": len(atoms_list), 119 | } 120 | 121 | if slurm_array_task_id == slurm_array_task_min: 122 | with open(f"{out_dir}/run_params.json", "w") as f: 123 | json.dump(run_params, f, indent=4) 124 | 125 | if slurm_array_job_id == "debug": 126 | atoms_list = atoms_list[:5] 127 | print("Running in DEBUG mode.") 128 | elif slurm_array_task_count > 1: 129 | # Split the atoms_list into slurm_array_task_count parts trying to make even runtime 130 | atoms_list = atoms_list[ 131 | slurm_array_task_id - slurm_array_task_min :: slurm_array_task_count 132 | ] 133 | 134 | 135 | # Set up the relaxation and force set calculation 136 | filter_cls: Callable[[Atoms], Atoms] = { 137 | "frechet": FrechetCellFilter, 138 | "exp": ExpCellFilter, 139 | }[ase_filter] 140 | optim_cls: Callable[..., Optimizer] = {"FIRE": FIRE, "LBFGS": LBFGS}[ase_optimizer] 141 | 142 | 143 | force_results: dict[str, dict[str, Any]] = {} 144 | kappa_results: dict[str, dict[str, Any]] = {} 145 | 146 | 147 | tqdm_bar = tqdm(atoms_list, desc="Conductivity calculation: ", disable=not prog_bar) 148 | 149 | for atoms in tqdm_bar: 150 | mat_id = atoms.info[ID] 151 | init_info = deepcopy(atoms.info) 152 | mat_name = atoms.info["name"] 153 | mat_desc = f"{mat_name}-{symm_name_map[atoms.info['symm.no']]}" 154 | info_dict = { 155 | "desc": mat_desc, 156 | "name": mat_name, 157 | "initial_space_group_number": atoms.info["symm.no"], 158 | "errors": [], 159 | "error_traceback": [], 160 | } 161 | 162 | tqdm_bar.set_postfix_str(mat_desc, refresh=True) 163 | 164 | # Relaxation 165 | try: 166 | atoms.calc = calc 167 | if max_steps > 0: 168 | if not if_two_stage_relax: 169 | if enforce_relax_symm: 170 | atoms.set_constraint(FixSymmetry(atoms)) 171 | filtered_atoms = filter_cls(atoms, mask=NO_TILT_MASK) 172 | else: 173 | filtered_atoms = filter_cls(atoms) 174 | 175 | optimizer = optim_cls( 176 | filtered_atoms, logfile=f"{out_dir}/relax_{slurm_array_task_id}.log" 177 | ) 178 | optimizer.run(fmax=force_max, steps=max_steps) 179 | 180 | reached_max_steps = False 181 | if optimizer.step == max_steps: 182 | reached_max_steps = True 183 | print( 184 | f"Material {mat_desc=}, {mat_id=} reached max step {max_steps=} during relaxation." 185 | ) 186 | 187 | # maximum residual stress component in for xx,yy,zz and xy,yz,xz components separately 188 | # result is a array of 2 elements 189 | max_stress = atoms.get_stress().reshape((2, 3), order="C").max(axis=1) 190 | 191 | atoms.calc = None 192 | atoms.constraints = None 193 | atoms.info = init_info | atoms.info 194 | 195 | symm_no = get_spacegroup_number(atoms, symprec=symprec) 196 | 197 | relax_dict = { 198 | "structure": aseatoms2str(atoms), 199 | "max_stress": max_stress, 200 | "reached_max_steps": reached_max_steps, 201 | "relaxed_space_group_number": symm_no, 202 | "broken_symmetry": symm_no 203 | != init_info["initial_space_group_number"], 204 | } 205 | 206 | else: 207 | atoms, relax_dict = two_stage_relax( 208 | atoms, 209 | fmax_stage1=force_max, 210 | fmax_stage2=force_max, 211 | steps_stage1=max_steps, 212 | steps_stage2=max_steps, 213 | Optimizer=optim_cls, 214 | Filter=filter_cls, 215 | allow_tilt=False, 216 | log=f"{out_dir}/relax_{slurm_array_task_id}.log", 217 | enforce_symmetry=enforce_relax_symm, 218 | ) 219 | 220 | atoms.calc = None 221 | 222 | except Exception as exc: 223 | warnings.warn(f"Failed to relax {mat_name=}, {mat_id=}: {exc!r}") 224 | traceback.print_exc() 225 | info_dict["errors"].append(f"RelaxError: {exc!r}") 226 | info_dict["error_traceback"].append(traceback.format_exc()) 227 | kappa_results[mat_id] = info_dict 228 | continue 229 | 230 | # Calculation of force sets 231 | try: 232 | ph3 = init_phono3py(atoms, log=False, symprec=symprec) 233 | 234 | ph3, fc2_set, freqs = get_fc2_and_freqs( 235 | ph3, 236 | calculator=calc, 237 | log=False, 238 | pbar_kwargs={"leave": False, "disable": not prog_bar}, 239 | ) 240 | 241 | imaginary_freqs = check_imaginary_freqs(freqs) 242 | freqs_dict = {"imaginary_freqs": imaginary_freqs, "frequencies": freqs} 243 | 244 | # if conductivity condition is met, calculate fc3 245 | ltc_condition = not imaginary_freqs and ( 246 | not relax_dict["broken_symmetry"] or conductivity_broken_symm 247 | ) 248 | 249 | if ltc_condition: 250 | ph3, fc3_set = get_fc3( 251 | ph3, 252 | calculator=calc, 253 | log=False, 254 | pbar_kwargs={"leave": False, "disable": not prog_bar}, 255 | ) 256 | 257 | else: 258 | fc3_set = [] 259 | 260 | if save_forces: 261 | force_results[mat_id] = {"fc2_set": fc2_set, "fc3_set": fc3_set} 262 | 263 | if not ltc_condition: 264 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict 265 | continue 266 | 267 | except Exception as exc: 268 | warnings.warn(f"Failed to calculate force sets {mat_id}: {exc!r}") 269 | traceback.print_exc() 270 | info_dict["errors"].append(f"ForceConstantError: {exc!r}") 271 | info_dict["error_traceback"].append(traceback.format_exc()) 272 | kappa_results[mat_id] = info_dict | relax_dict 273 | continue 274 | 275 | # Calculation of conductivity 276 | try: 277 | ph3, kappa_dict = calculate_conductivity(ph3, log=False) 278 | 279 | except Exception as exc: 280 | warnings.warn(f"Failed to calculate conductivity {mat_id}: {exc!r}") 281 | traceback.print_exc() 282 | info_dict["errors"].append(f"ConductivityError: {exc!r}") 283 | info_dict["error_traceback"].append(traceback.format_exc()) 284 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict 285 | continue 286 | 287 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict | kappa_dict 288 | 289 | 290 | df_kappa = pd.DataFrame(kappa_results).T 291 | df_kappa.index.name = ID 292 | df_kappa.reset_index().to_json(out_path) 293 | 294 | 295 | if save_forces: 296 | force_out_path = ( 297 | f"{out_dir}/force_sets_{slurm_array_job_id}-{slurm_array_task_id:>03}.json.gz" 298 | ) 299 | df_force = pd.DataFrame(force_results).T 300 | df_force = pd.concat([df_kappa, df_force], axis=1) 301 | df_force.index.name = ID 302 | df_force.reset_index().to_json(force_out_path) 303 | -------------------------------------------------------------------------------- /models/MACE-M/2024-11-09-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-M/2024-11-09-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz -------------------------------------------------------------------------------- /models/MACE-M/2024-11-09-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2024-11-09 13:30:28", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "MACE", 5 | "checkpoint": "medium", 6 | "device": "cpu", 7 | "dtype": "float64", 8 | "versions": { 9 | "numpy": "1.26.4", 10 | "torch": "2.4.1+cu124" 11 | }, 12 | "ase_optimizer": "FIRE", 13 | "ase_filter": "frechet", 14 | "if_two_stage_relax": true, 15 | "max_steps": 300, 16 | "force_max": 0.0001, 17 | "symprec": 1e-05, 18 | "enforce_relax_symm": true, 19 | "conductivity_broken_symm": false, 20 | "slurm_array_task_count": 34, 21 | "slurm_array_job_id": "1649656", 22 | "task_type": "LTC", 23 | "job_name": "MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 24 | "struct_data_path": "phononDB-PBE-structures.extxyz", 25 | "n_structures": 103 26 | } -------------------------------------------------------------------------------- /models/MACE-M/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "MACE" 10 | 11 | json_file = "k_srme.json.gz" 12 | txt_path = "metrics.txt" 13 | in_file = "conductivity_*-???.json.gz" 14 | 15 | in_folder = f"2024-11-09-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 16 | 17 | # Comparing with the DFT results without 18 | # non-analytical correction term (NAC) 19 | DFT_RESULTS_FILE = DFT_NONAC_REF 20 | 21 | 22 | module_dir = os.path.dirname(__file__) 23 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 24 | out_path = f"{module_dir}/{in_folder}/{json_file}" 25 | 26 | 27 | # Read MLP results 28 | if not glob(in_pattern): 29 | if os.path.exists(out_path): 30 | df_mlp_results = pd.read_json(out_path).set_index(ID) 31 | else: 32 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 33 | 34 | 35 | # df_mlp_results.reset_index().to_json(out_path) 36 | 37 | # Read DFT results 38 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 39 | 40 | 41 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 42 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 43 | 44 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 45 | 46 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 47 | 48 | 49 | # Save results 50 | df_mlp_processed.round(5) 51 | df_mlp_processed.index.name = ID 52 | df_mlp_processed.reset_index().to_json(out_path) 53 | 54 | 55 | # Print 56 | pd.set_option("display.max_rows", None) 57 | pd.set_option("display.max_columns", None) 58 | df_mlp_print = df_mlp_filtered[ 59 | [ 60 | "desc", 61 | "SRME", 62 | "SRE", 63 | "kappa_TOT_ave", 64 | "DFT_kappa_TOT_ave", 65 | "imaginary_freqs", 66 | "errors", 67 | ] 68 | ] 69 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 70 | lambda x: x[0] if not pd.isna(x) else x 71 | ) 72 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 73 | lambda x: x[0] if not pd.isna(x) else x 74 | ) 75 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 76 | 77 | with open(txt_path, "w") as f: 78 | print(f"MODEL: {model_name}", file=f) 79 | print(f"\tmean SRME: {mSRME}", file=f) 80 | print(f"\tmean SRE: {mSRE}", file=f) 81 | 82 | print(df_mlp_print.round(4), file=f) 83 | 84 | 85 | df_mlp_print = df_mlp_print[ 86 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 87 | ] 88 | print(df_mlp_print.round(3)) 89 | 90 | print(f"MODEL: {model_name}") 91 | print(f"\tmean SRME: {mSRME}") 92 | print(f"\tmean SRE: {mSRE}") 93 | -------------------------------------------------------------------------------- /models/MACE-MPA-0/2024-11-25-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-MPA-0/2024-11-25-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz -------------------------------------------------------------------------------- /models/MACE-MPA-0/2024-11-25-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2024-11-25 09:02:31", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "MACE", 5 | "checkpoint": "MACE-MPA-0.model", 6 | "device": "cpu", 7 | "dtype": "float64", 8 | "versions": { 9 | "numpy": "1.26.4", 10 | "torch": "2.4.1+cu124" 11 | }, 12 | "ase_optimizer": "FIRE", 13 | "ase_filter": "frechet", 14 | "if_two_stage_relax": true, 15 | "max_steps": 300, 16 | "force_max": 0.0001, 17 | "symprec": 1e-05, 18 | "enforce_relax_symm": true, 19 | "conductivity_broken_symm": false, 20 | "slurm_array_task_count": 34, 21 | "slurm_array_job_id": "1803244", 22 | "task_type": "LTC", 23 | "job_name": "MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 24 | "struct_data_path": "phononDB-PBE-structures.extxyz", 25 | "n_structures": 103 26 | } -------------------------------------------------------------------------------- /models/MACE-MPA-0/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import ( 7 | get_metrics, 8 | process_benchmark_descriptors, 9 | get_success_metrics, 10 | ) 11 | 12 | 13 | model_name = "MACE" 14 | 15 | json_file = "k_srme.json.gz" 16 | txt_path = "metrics.txt" 17 | in_file = "conductivity_*-???.json.gz" 18 | 19 | in_folder = f"2024-11-25-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 20 | 21 | DFT_RESULTS_FILE = DFT_NONAC_REF 22 | 23 | 24 | module_dir = os.path.dirname(__file__) 25 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 26 | out_path = f"{module_dir}/{in_folder}/{json_file}" 27 | 28 | 29 | # Read MLP results 30 | if not glob(in_pattern): 31 | if os.path.exists(out_path): 32 | df_mlp_results = pd.read_json(out_path).set_index(ID) 33 | else: 34 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 35 | 36 | 37 | # df_mlp_results.reset_index().to_json(out_path) 38 | 39 | # Read DFT results 40 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 41 | 42 | 43 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 44 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 45 | 46 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 47 | 48 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 49 | 50 | 51 | # Save results 52 | df_mlp_processed.round(5) 53 | df_mlp_processed.index.name = ID 54 | df_mlp_processed.reset_index().to_json(out_path) 55 | 56 | 57 | # Print 58 | pd.set_option("display.max_rows", None) 59 | pd.set_option("display.max_columns", None) 60 | df_mlp_print = df_mlp_filtered[ 61 | [ 62 | "desc", 63 | "SRME", 64 | "SRE", 65 | "kappa_TOT_ave", 66 | "DFT_kappa_TOT_ave", 67 | "imaginary_freqs", 68 | "errors", 69 | ] 70 | ] 71 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 72 | lambda x: x[0] if not pd.isna(x) else x 73 | ) 74 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 75 | lambda x: x[0] if not pd.isna(x) else x 76 | ) 77 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 78 | 79 | with open(txt_path, "w") as f: 80 | print(f"MODEL: {model_name}", file=f) 81 | print(f"\tmean SRME: {mSRME}", file=f) 82 | print(f"\tmean SRE: {mSRE}", file=f) 83 | 84 | print(df_mlp_print.round(4), file=f) 85 | 86 | 87 | df_mlp_print = df_mlp_print[ 88 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 89 | ] 90 | print(df_mlp_print.round(3)) 91 | 92 | print(f"MODEL: {model_name}") 93 | print(f"\tmean SRME: {mSRME}") 94 | print(f"\tmean SRE: {mSRE}") 95 | 96 | mSRE_success, mSRME_success = get_success_metrics(df_mlp_filtered) 97 | 98 | print(f"\tmean SRME for successful runs: {mSRME_success}") 99 | print(f"\tmean SRE for successful runs: {mSRE_success}") 100 | -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/.DS_Store -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-000.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-000.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-001.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-001.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-002.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-002.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-003.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-003.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-004.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-004.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-005.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-005.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-006.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-006.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-007.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-007.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-008.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-008.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-009.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-009.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-010.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-010.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-011.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-011.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-012.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-012.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-013.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-013.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-014.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-014.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-015.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-015.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-016.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-016.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-017.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-017.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-018.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-018.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-019.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-019.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-020.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-020.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-021.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-021.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-022.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-022.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-023.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-023.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-024.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-024.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-025.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-025.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-026.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-026.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-027.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-027.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-028.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-028.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-029.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-029.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-030.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-030.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-031.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-031.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-032.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-032.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-033.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/force_sets_1653939-033.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2024-11-10-MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2024-11-10 21:39:54", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "MACE", 5 | "checkpoint": "https://github.com/ACEsuit/mace-mp/releases/download/mace_mp_0b/mace_agnesi_medium.model", 6 | "device": "cpu", 7 | "dtype": "float64", 8 | "versions": { 9 | "numpy": "1.26.4", 10 | "torch": "2.4.1+cu124" 11 | }, 12 | "ase_optimizer": "FIRE", 13 | "ase_filter": "frechet", 14 | "if_two_stage_relax": true, 15 | "max_steps": 300, 16 | "force_max": 0.0001, 17 | "symprec": 1e-05, 18 | "enforce_relax_symm": true, 19 | "conductivity_broken_symm": false, 20 | "slurm_array_task_count": 34, 21 | "slurm_array_job_id": "1653939", 22 | "task_type": "LTC", 23 | "job_name": "MACE-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 24 | "struct_data_path": "phononDB-PBE-structures.extxyz", 25 | "n_structures": 103 26 | } -------------------------------------------------------------------------------- /models/MACE-agnesi-medium/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "MACE" 10 | 11 | json_file = "k_srme.json.gz" 12 | txt_path = "metrics.txt" 13 | in_file = "conductivity_*-???.json.gz" 14 | 15 | in_folder = f"2024-11-10-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 16 | 17 | # Comparing with the DFT results without 18 | # non-analytical correction term (NAC) 19 | DFT_RESULTS_FILE = DFT_NONAC_REF 20 | 21 | 22 | module_dir = os.path.dirname(__file__) 23 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 24 | out_path = f"{module_dir}/{in_folder}/{json_file}" 25 | 26 | 27 | # Read MLP results 28 | if not glob(in_pattern): 29 | if os.path.exists(out_path): 30 | df_mlp_results = pd.read_json(out_path).set_index(ID) 31 | else: 32 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 33 | 34 | 35 | # df_mlp_results.reset_index().to_json(out_path) 36 | 37 | # Read DFT results 38 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 39 | 40 | 41 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 42 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 43 | 44 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 45 | 46 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 47 | 48 | 49 | # Save results 50 | df_mlp_processed.round(5) 51 | df_mlp_processed.index.name = ID 52 | df_mlp_processed.reset_index().to_json(out_path) 53 | 54 | 55 | # Print 56 | pd.set_option("display.max_rows", None) 57 | pd.set_option("display.max_columns", None) 58 | df_mlp_print = df_mlp_filtered[ 59 | [ 60 | "desc", 61 | "SRME", 62 | "SRE", 63 | "kappa_TOT_ave", 64 | "DFT_kappa_TOT_ave", 65 | "imaginary_freqs", 66 | "errors", 67 | ] 68 | ] 69 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 70 | lambda x: x[0] if not pd.isna(x) else x 71 | ) 72 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 73 | lambda x: x[0] if not pd.isna(x) else x 74 | ) 75 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 76 | 77 | with open(txt_path, "w") as f: 78 | print(f"MODEL: {model_name}", file=f) 79 | print(f"\tmean SRME: {mSRME}", file=f) 80 | print(f"\tmean SRE: {mSRE}", file=f) 81 | 82 | print(df_mlp_print.round(4), file=f) 83 | 84 | 85 | df_mlp_print = df_mlp_print[ 86 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 87 | ] 88 | print(df_mlp_print.round(3)) 89 | 90 | print(f"MODEL: {model_name}") 91 | print(f"\tmean SRME: {mSRME}") 92 | print(f"\tmean SRE: {mSRE}") 93 | -------------------------------------------------------------------------------- /models/MatterSim-V1/1_test_srme.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | import warnings 4 | from typing import Literal, Any 5 | from collections.abc import Callable 6 | import traceback 7 | from copy import deepcopy 8 | from importlib.metadata import version 9 | import json 10 | 11 | import pandas as pd 12 | 13 | from tqdm import tqdm 14 | 15 | from ase.constraints import FixSymmetry 16 | from ase.filters import ExpCellFilter, FrechetCellFilter 17 | from ase.optimize import FIRE, LBFGS 18 | from ase.optimize.optimize import Optimizer 19 | from ase import Atoms 20 | from ase.io import read 21 | 22 | from k_srme import aseatoms2str, two_stage_relax, ID, STRUCTURES, NO_TILT_MASK 23 | from k_srme.utils import symm_name_map, get_spacegroup_number, check_imaginary_freqs 24 | from k_srme.conductivity import ( 25 | init_phono3py, 26 | get_fc2_and_freqs, 27 | get_fc3, 28 | calculate_conductivity, 29 | ) 30 | 31 | from mattersim.forcefield import MatterSimCalculator 32 | 33 | warnings.filterwarnings("ignore", category=DeprecationWarning, module="spglib") 34 | 35 | 36 | # EDITABLE CONFIG 37 | model_name = "MatterSim-V1" 38 | 39 | 40 | # load the default checkpoint: 1M 41 | calc = MatterSimCalculator(device="cuda") 42 | checkpoint = "mattersim-v1.0.0-1m" 43 | suffix = "1M" 44 | 45 | # load the checkpoint of 5M parameters 46 | # calc = MatterSimCalculator(device="cuda", load_path="mattersim-v1.0.0-5m") 47 | # checkpoint = "mattersim-v1.0.0-5m" 48 | # suffix="5M" 49 | 50 | 51 | # Relaxation parameters 52 | ase_optimizer: Literal["FIRE", "LBFGS", "BFGS"] = "FIRE" 53 | ase_filter: Literal["frechet", "exp"] = "frechet" 54 | if_two_stage_relax = True # Use two-stage relaxation enforcing symmetries 55 | max_steps = 300 56 | force_max = 1e-4 # Run until the forces are smaller than this in eV/A 57 | 58 | # Symmetry parameters 59 | # symmetry precision for enforcing relaxation and conductivity calculation 60 | symprec = 1e-5 61 | # Enforce symmetry with during relaxation if broken 62 | enforce_relax_symm = True 63 | # Conductivity to be calculated if symmetry group changed during relaxation 64 | conductivity_broken_symm = False 65 | prog_bar = True 66 | save_forces = True # Save force sets to file 67 | 68 | 69 | task_type = "LTC" # lattice thermal conductivity 70 | job_name = f"{model_name}-phononDB-{task_type}-{ase_optimizer}{'_2SR' if if_two_stage_relax else ''}_force{force_max}_sym{symprec}-{suffix}" 71 | module_dir = os.path.dirname(__file__) 72 | out_dir = f"{module_dir}/{datetime.datetime.now().strftime('%Y-%m-%d')}-{job_name}" 73 | os.makedirs(out_dir, exist_ok=True) 74 | 75 | out_path = f"{out_dir}/conductivity.json.gz" 76 | 77 | 78 | timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 79 | struct_data_path = STRUCTURES 80 | print(f"\nJob {job_name} started {timestamp}") 81 | 82 | 83 | print(f"Read data from {struct_data_path}") 84 | atoms_list: list[Atoms] = read(struct_data_path, format="extxyz", index=":") 85 | 86 | run_params = { 87 | "timestamp": timestamp, 88 | "k_srme_version": version("k_srme"), 89 | "model_name": model_name, 90 | # "checkpoint": checkpoint, 91 | "versions": {dep: version(dep) for dep in ("numpy", "torch")}, 92 | "ase_optimizer": ase_optimizer, 93 | "ase_filter": ase_filter, 94 | "if_two_stage_relax": if_two_stage_relax, 95 | "max_steps": max_steps, 96 | "force_max": force_max, 97 | "symprec": symprec, 98 | "enforce_relax_symm": enforce_relax_symm, 99 | "conductivity_broken_symm": conductivity_broken_symm, 100 | # "slurm_array_task_count": slurm_array_task_count, 101 | # "slurm_array_job_id": slurm_array_job_id, 102 | "task_type": task_type, 103 | "job_name": job_name, 104 | "struct_data_path": os.path.basename(struct_data_path), 105 | "n_structures": len(atoms_list), 106 | } 107 | 108 | # if slurm_array_task_id == slurm_array_task_min: 109 | if True: 110 | with open(f"{out_dir}/run_params.json", "w") as f: 111 | json.dump(run_params, f, indent=4) 112 | 113 | atoms_list = atoms_list[:5] 114 | 115 | # Set up the relaxation and force set calculation 116 | filter_cls: Callable[[Atoms], Atoms] = { 117 | "frechet": FrechetCellFilter, 118 | "exp": ExpCellFilter, 119 | }[ase_filter] 120 | optim_cls: Callable[..., Optimizer] = {"FIRE": FIRE, "LBFGS": LBFGS}[ase_optimizer] 121 | 122 | 123 | force_results: dict[str, dict[str, Any]] = {} 124 | kappa_results: dict[str, dict[str, Any]] = {} 125 | 126 | 127 | tqdm_bar = tqdm(atoms_list, desc="Conductivity calculation: ", disable=not prog_bar) 128 | 129 | for atoms in tqdm_bar: 130 | mat_id = atoms.info[ID] 131 | init_info = deepcopy(atoms.info) 132 | mat_name = atoms.info["name"] 133 | mat_desc = f"{mat_name}-{symm_name_map[atoms.info['symm.no']]}" 134 | info_dict = { 135 | "desc": mat_desc, 136 | "name": mat_name, 137 | "initial_space_group_number": atoms.info["symm.no"], 138 | "errors": [], 139 | "error_traceback": [], 140 | } 141 | 142 | tqdm_bar.set_postfix_str(mat_desc, refresh=True) 143 | 144 | # Relaxation 145 | try: 146 | atoms.calc = calc 147 | if max_steps > 0: 148 | if not if_two_stage_relax: 149 | if enforce_relax_symm: 150 | atoms.set_constraint(FixSymmetry(atoms)) 151 | filtered_atoms = filter_cls(atoms, mask=NO_TILT_MASK) 152 | else: 153 | filtered_atoms = filter_cls(atoms) 154 | 155 | optimizer = optim_cls(filtered_atoms, logfile=f"{out_dir}/relax.log") 156 | optimizer.run(fmax=force_max, steps=max_steps) 157 | 158 | reached_max_steps = False 159 | if optimizer.step == max_steps: 160 | reached_max_steps = True 161 | print( 162 | f"Material {mat_desc=}, {mat_id=} reached max step {max_steps=} during relaxation." 163 | ) 164 | 165 | # maximum residual stress component in for xx,yy,zz and xy,yz,xz components separately 166 | # result is a array of 2 elements 167 | max_stress = atoms.get_stress().reshape((2, 3), order="C").max(axis=1) 168 | 169 | atoms.calc = None 170 | atoms.constraints = None 171 | atoms.info = init_info | atoms.info 172 | 173 | symm_no = get_spacegroup_number(atoms, symprec=symprec) 174 | 175 | relax_dict = { 176 | "structure": aseatoms2str(atoms), 177 | "max_stress": max_stress, 178 | "reached_max_steps": reached_max_steps, 179 | "relaxed_space_group_number": symm_no, 180 | "broken_symmetry": symm_no 181 | != init_info["initial_space_group_number"], 182 | } 183 | 184 | else: 185 | atoms, relax_dict = two_stage_relax( 186 | atoms, 187 | fmax_stage1=force_max, 188 | fmax_stage2=force_max, 189 | steps_stage1=max_steps, 190 | steps_stage2=max_steps, 191 | Optimizer=optim_cls, 192 | Filter=filter_cls, 193 | allow_tilt=False, 194 | log=f"{out_dir}/relax.log", 195 | enforce_symmetry=enforce_relax_symm, 196 | ) 197 | 198 | atoms.calc = None 199 | 200 | except Exception as exc: 201 | warnings.warn(f"Failed to relax {mat_name=}, {mat_id=}: {exc!r}") 202 | traceback.print_exc() 203 | info_dict["errors"].append(f"RelaxError: {exc!r}") 204 | info_dict["error_traceback"].append(traceback.format_exc()) 205 | kappa_results[mat_id] = info_dict 206 | continue 207 | 208 | # Calculation of force sets 209 | try: 210 | ph3 = init_phono3py(atoms, log=False, symprec=symprec) 211 | 212 | ph3, fc2_set, freqs = get_fc2_and_freqs( 213 | ph3, 214 | calculator=calc, 215 | log=False, 216 | pbar_kwargs={"leave": False, "disable": not prog_bar}, 217 | ) 218 | 219 | imaginary_freqs = check_imaginary_freqs(freqs) 220 | freqs_dict = {"imaginary_freqs": imaginary_freqs, "frequencies": freqs} 221 | 222 | # if conductivity condition is met, calculate fc3 223 | ltc_condition = not imaginary_freqs and ( 224 | not relax_dict["broken_symmetry"] or conductivity_broken_symm 225 | ) 226 | 227 | if ltc_condition: 228 | ph3, fc3_set = get_fc3( 229 | ph3, 230 | calculator=calc, 231 | log=False, 232 | pbar_kwargs={"leave": False, "disable": not prog_bar}, 233 | ) 234 | 235 | else: 236 | fc3_set = [] 237 | 238 | if save_forces: 239 | force_results[mat_id] = {"fc2_set": fc2_set, "fc3_set": fc3_set} 240 | 241 | if not ltc_condition: 242 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict 243 | continue 244 | 245 | except Exception as exc: 246 | warnings.warn(f"Failed to calculate force sets {mat_id}: {exc!r}") 247 | traceback.print_exc() 248 | info_dict["errors"].append(f"ForceConstantError: {exc!r}") 249 | info_dict["error_traceback"].append(traceback.format_exc()) 250 | kappa_results[mat_id] = info_dict | relax_dict 251 | continue 252 | 253 | # Calculation of conductivity 254 | try: 255 | ph3, kappa_dict = calculate_conductivity(ph3, log=False) 256 | 257 | except Exception as exc: 258 | warnings.warn(f"Failed to calculate conductivity {mat_id}: {exc!r}") 259 | traceback.print_exc() 260 | info_dict["errors"].append(f"ConductivityError: {exc!r}") 261 | info_dict["error_traceback"].append(traceback.format_exc()) 262 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict 263 | continue 264 | 265 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict | kappa_dict 266 | 267 | 268 | df_kappa = pd.DataFrame(kappa_results).T 269 | df_kappa.index.name = ID 270 | df_kappa.reset_index().to_json(out_path) 271 | 272 | 273 | if save_forces: 274 | force_out_path = f"{out_dir}/force_sets.json.gz" 275 | df_force = pd.DataFrame(force_results).T 276 | df_force = pd.concat([df_kappa, df_force], axis=1) 277 | df_force.index.name = ID 278 | df_force.reset_index().to_json(force_out_path) 279 | -------------------------------------------------------------------------------- /models/MatterSim-V1/2024-12-09-MatterSim-V1-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05-1M/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MatterSim-V1/2024-12-09-MatterSim-V1-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05-1M/k_srme.json.gz -------------------------------------------------------------------------------- /models/MatterSim-V1/2024-12-09-MatterSim-V1-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05-1M/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2024-12-09 07:13:12", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "MatterSim-V1", 5 | "versions": { 6 | "numpy": "1.26.4", 7 | "torch": "2.2.0" 8 | }, 9 | "ase_optimizer": "FIRE", 10 | "ase_filter": "frechet", 11 | "if_two_stage_relax": true, 12 | "max_steps": 300, 13 | "force_max": 0.0001, 14 | "symprec": 1e-05, 15 | "enforce_relax_symm": true, 16 | "conductivity_broken_symm": false, 17 | "slurm_array_task_count": 1, 18 | "slurm_array_job_id": "prod", 19 | "task_type": "LTC", 20 | "job_name": "MatterSim-V1-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 21 | "struct_data_path": "phononDB-PBE-structures.extxyz", 22 | "n_structures": 103 23 | } -------------------------------------------------------------------------------- /models/MatterSim-V1/2024-12-09-MatterSim-V1-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05-5M/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/MatterSim-V1/2024-12-09-MatterSim-V1-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05-5M/k_srme.json.gz -------------------------------------------------------------------------------- /models/MatterSim-V1/2024-12-09-MatterSim-V1-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05-5M/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2024-12-09 13:23:55", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "MatterSim-V1", 5 | "versions": { 6 | "numpy": "1.26.4", 7 | "torch": "2.2.0" 8 | }, 9 | "ase_optimizer": "FIRE", 10 | "ase_filter": "frechet", 11 | "if_two_stage_relax": true, 12 | "max_steps": 300, 13 | "force_max": 0.0001, 14 | "symprec": 1e-05, 15 | "enforce_relax_symm": true, 16 | "conductivity_broken_symm": false, 17 | "slurm_array_task_count": 1, 18 | "slurm_array_job_id": "prod", 19 | "task_type": "LTC", 20 | "job_name": "MatterSim-V1-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 21 | "struct_data_path": "phononDB-PBE-structures.extxyz", 22 | "n_structures": 103 23 | } -------------------------------------------------------------------------------- /models/MatterSim-V1/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "MatterSim-V1" 10 | 11 | json_file = "k_srme.json.gz" 12 | txt_path = "metrics.txt" 13 | in_file = "conductivity*.json.gz" 14 | 15 | in_folder = "2024-12-09-MatterSim-V1-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05-1M" 16 | # in_folder = "2024-12-09-MatterSim-V1-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05-5M" 17 | 18 | # Comparing with the DFT results without 19 | # non-analytical correction term (NAC) 20 | DFT_RESULTS_FILE = DFT_NONAC_REF 21 | 22 | 23 | module_dir = os.path.dirname(__file__) 24 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 25 | out_path = f"{module_dir}/{in_folder}/{json_file}" 26 | 27 | 28 | # Read MLP results 29 | if not glob(in_pattern): 30 | if os.path.exists(out_path): 31 | df_mlp_results = pd.read_json(out_path).set_index(ID) 32 | else: 33 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 34 | 35 | 36 | # df_mlp_results.reset_index().to_json(out_path) 37 | 38 | # Read DFT results 39 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 40 | 41 | 42 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 43 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 44 | 45 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 46 | 47 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 48 | 49 | 50 | # Save results 51 | df_mlp_processed.round(5) 52 | df_mlp_processed.index.name = ID 53 | df_mlp_processed.reset_index().to_json(out_path) 54 | 55 | 56 | # Print 57 | pd.set_option("display.max_rows", None) 58 | pd.set_option("display.max_columns", None) 59 | df_mlp_print = df_mlp_filtered[ 60 | [ 61 | "desc", 62 | "SRME", 63 | "SRE", 64 | "kappa_TOT_ave", 65 | "DFT_kappa_TOT_ave", 66 | "imaginary_freqs", 67 | "errors", 68 | ] 69 | ] 70 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 71 | lambda x: x[0] if not pd.isna(x) else x 72 | ) 73 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 74 | lambda x: x[0] if not pd.isna(x) else x 75 | ) 76 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 77 | 78 | with open(txt_path, "w") as f: 79 | print(f"MODEL: {model_name}", file=f) 80 | print(f"\tmean SRME: {mSRME}", file=f) 81 | print(f"\tmean SRE: {mSRE}", file=f) 82 | 83 | print(df_mlp_print.round(4), file=f) 84 | 85 | 86 | df_mlp_print = df_mlp_print[ 87 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 88 | ] 89 | print(df_mlp_print.round(3)) 90 | 91 | print(f"MODEL: {model_name}") 92 | print(f"\tmean SRME: {mSRME}") 93 | print(f"\tmean SRE: {mSRE}") 94 | -------------------------------------------------------------------------------- /models/ORBv2-MPTraj/2024-11-09-ORB-v2-MPtraj-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/ORBv2-MPTraj/2024-11-09-ORB-v2-MPtraj-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz -------------------------------------------------------------------------------- /models/ORBv2-MPTraj/2024-11-09-ORB-v2-MPtraj-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2024-11-09 13:38:12", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "ORB-v2-MPtraj", 5 | "device": "cpu", 6 | "versions": { 7 | "numpy": "1.26.4", 8 | "torch": "2.2.0" 9 | }, 10 | "ase_optimizer": "FIRE", 11 | "ase_filter": "frechet", 12 | "if_two_stage_relax": true, 13 | "max_steps": 300, 14 | "force_max": 0.0001, 15 | "symprec": 1e-05, 16 | "enforce_relax_symm": true, 17 | "conductivity_broken_symm": false, 18 | "slurm_array_task_count": 34, 19 | "slurm_array_job_id": "1649658", 20 | "task_type": "LTC", 21 | "job_name": "ORB-v2-MPtraj-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 22 | "struct_data_path": "phononDB-PBE-structures.extxyz", 23 | "n_structures": 103 24 | } -------------------------------------------------------------------------------- /models/ORBv2-MPTraj/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "ORB-v2-MPtraj" 10 | 11 | json_file = "k_srme.json.gz" 12 | txt_path = "metrics.txt" 13 | in_file = "conductivity_*-???.json.gz" 14 | 15 | in_folder = f"2024-11-09-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 16 | 17 | # Comparing with the DFT results without 18 | # non-analytical correction term (NAC) 19 | DFT_RESULTS_FILE = DFT_NONAC_REF 20 | 21 | 22 | module_dir = os.path.dirname(__file__) 23 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 24 | out_path = f"{module_dir}/{in_folder}/{json_file}" 25 | 26 | 27 | # Read MLP results 28 | if not glob(in_pattern): 29 | if os.path.exists(out_path): 30 | df_mlp_results = pd.read_json(out_path).set_index(ID) 31 | else: 32 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 33 | 34 | 35 | # df_mlp_results.reset_index().to_json(out_path) 36 | 37 | # Read DFT results 38 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 39 | 40 | 41 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 42 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 43 | 44 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 45 | 46 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 47 | 48 | 49 | # Save results 50 | df_mlp_processed.round(5) 51 | df_mlp_processed.index.name = ID 52 | df_mlp_processed.reset_index().to_json(out_path) 53 | 54 | 55 | # Print 56 | pd.set_option("display.max_rows", None) 57 | pd.set_option("display.max_columns", None) 58 | df_mlp_print = df_mlp_filtered[ 59 | [ 60 | "desc", 61 | "SRME", 62 | "SRE", 63 | "kappa_TOT_ave", 64 | "DFT_kappa_TOT_ave", 65 | "imaginary_freqs", 66 | "errors", 67 | ] 68 | ] 69 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 70 | lambda x: x[0] if not pd.isna(x) else x 71 | ) 72 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 73 | lambda x: x[0] if not pd.isna(x) else x 74 | ) 75 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 76 | 77 | with open(txt_path, "w") as f: 78 | print(f"MODEL: {model_name}", file=f) 79 | print(f"\tmean SRME: {mSRME}", file=f) 80 | print(f"\tmean SRE: {mSRE}", file=f) 81 | 82 | print(df_mlp_print.round(4), file=f) 83 | 84 | 85 | df_mlp_print = df_mlp_print[ 86 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 87 | ] 88 | print(df_mlp_print.round(3)) 89 | 90 | print(f"MODEL: {model_name}") 91 | print(f"\tmean SRME: {mSRME}") 92 | print(f"\tmean SRE: {mSRE}") 93 | -------------------------------------------------------------------------------- /models/ORBv2/1_test_srme.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | import warnings 4 | from typing import Literal, Any 5 | from collections.abc import Callable 6 | import traceback 7 | from copy import deepcopy 8 | from importlib.metadata import version 9 | import json 10 | 11 | import pandas as pd 12 | 13 | from tqdm import tqdm 14 | 15 | from ase.constraints import FixSymmetry 16 | from ase.filters import ExpCellFilter, FrechetCellFilter 17 | from ase.optimize import FIRE, LBFGS 18 | from ase.optimize.optimize import Optimizer 19 | from ase import Atoms 20 | from ase.io import read 21 | 22 | from k_srme import aseatoms2str, two_stage_relax, ID, STRUCTURES, NO_TILT_MASK 23 | from k_srme.utils import symm_name_map, get_spacegroup_number, check_imaginary_freqs 24 | from k_srme.conductivity import ( 25 | init_phono3py, 26 | get_fc2_and_freqs, 27 | get_fc3, 28 | calculate_conductivity, 29 | ) 30 | 31 | import torch 32 | from orb_models.forcefield import pretrained 33 | from orb_models.forcefield.calculator import ORBCalculator 34 | 35 | 36 | warnings.filterwarnings("ignore", category=DeprecationWarning, module="spglib") 37 | 38 | 39 | # EDITABLE CONFIG 40 | model_name = "ORB-v2" 41 | 42 | device = "cuda" if torch.cuda.is_available() else "cpu" 43 | orbff = pretrained.orb_v2(device=device) 44 | calc = ORBCalculator(orbff, device=device) 45 | 46 | # Relaxation parameters 47 | ase_optimizer: Literal["FIRE", "LBFGS", "BFGS"] = "FIRE" 48 | ase_filter: Literal["frechet", "exp"] = "frechet" 49 | if_two_stage_relax = True # Use two-stage relaxation enforcing symmetries 50 | max_steps = 300 51 | force_max = 1e-4 # Run until the forces are smaller than this in eV/A 52 | 53 | # Symmetry parameters 54 | # symmetry precision for enforcing relaxation and conductivity calculation 55 | symprec = 1e-5 56 | # Enforce symmetry with during relaxation if broken 57 | enforce_relax_symm = True 58 | # Conductivity to be calculated if symmetry group changed during relaxation 59 | conductivity_broken_symm = False 60 | prog_bar = True 61 | save_forces = True # Save force sets to file 62 | 63 | 64 | slurm_array_task_count = int( 65 | os.getenv( 66 | "K_SRME_RESTART_ARRAY_TASK_COUNT", os.getenv("SLURM_ARRAY_TASK_COUNT", "1") 67 | ) 68 | ) 69 | slurm_array_task_id = int(os.getenv("SLURM_ARRAY_TASK_ID", "0")) 70 | slurm_array_job_id = os.getenv("SLURM_ARRAY_JOB_ID", os.getenv("SLURM_JOB_ID", "debug")) 71 | slurm_array_task_min = int( 72 | os.getenv("K_SRME_RESTART_ARRAY_TASK_MIN", os.getenv("SLURM_ARRAY_TASK_MIN", "0")) 73 | ) 74 | 75 | 76 | task_type = "LTC" # lattice thermal conductivity 77 | job_name = f"{model_name}-phononDB-{task_type}-{ase_optimizer}{'_2SR' if if_two_stage_relax else ''}_force{force_max}_sym{symprec}" 78 | module_dir = os.path.dirname(__file__) 79 | out_dir = os.getenv( 80 | "SBATCH_OUTPUT", 81 | f"{module_dir}/{datetime.datetime.now().strftime('%Y-%m-%d')}-{job_name}", 82 | ) 83 | os.makedirs(out_dir, exist_ok=True) 84 | 85 | out_path = ( 86 | f"{out_dir}/conductivity_{slurm_array_job_id}-{slurm_array_task_id:>03}.json.gz" 87 | ) 88 | 89 | 90 | timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 91 | struct_data_path = STRUCTURES 92 | print(f"\nJob {job_name} started {timestamp}") 93 | 94 | 95 | print(f"Read data from {struct_data_path}") 96 | atoms_list: list[Atoms] = read(struct_data_path, format="extxyz", index=":") 97 | 98 | run_params = { 99 | "timestamp": timestamp, 100 | "k_srme_version": version("k_srme"), 101 | "model_name": model_name, 102 | "device": device, 103 | "versions": {dep: version(dep) for dep in ("numpy", "torch")}, 104 | "ase_optimizer": ase_optimizer, 105 | "ase_filter": ase_filter, 106 | "if_two_stage_relax": if_two_stage_relax, 107 | "max_steps": max_steps, 108 | "force_max": force_max, 109 | "symprec": symprec, 110 | "enforce_relax_symm": enforce_relax_symm, 111 | "conductivity_broken_symm": conductivity_broken_symm, 112 | "slurm_array_task_count": slurm_array_task_count, 113 | "slurm_array_job_id": slurm_array_job_id, 114 | "task_type": task_type, 115 | "job_name": job_name, 116 | "struct_data_path": os.path.basename(struct_data_path), 117 | "n_structures": len(atoms_list), 118 | } 119 | 120 | if slurm_array_task_id == slurm_array_task_min: 121 | with open(f"{out_dir}/run_params.json", "w") as f: 122 | json.dump(run_params, f, indent=4) 123 | 124 | if slurm_array_job_id == "debug": 125 | atoms_list = atoms_list[:5] 126 | print("Running in DEBUG mode.") 127 | elif slurm_array_task_count > 1: 128 | # Split the atoms_list into slurm_array_task_count parts trying to make even runtime 129 | atoms_list = atoms_list[ 130 | slurm_array_task_id - slurm_array_task_min :: slurm_array_task_count 131 | ] 132 | 133 | 134 | # Set up the relaxation and force set calculation 135 | filter_cls: Callable[[Atoms], Atoms] = { 136 | "frechet": FrechetCellFilter, 137 | "exp": ExpCellFilter, 138 | }[ase_filter] 139 | optim_cls: Callable[..., Optimizer] = {"FIRE": FIRE, "LBFGS": LBFGS}[ase_optimizer] 140 | 141 | 142 | force_results: dict[str, dict[str, Any]] = {} 143 | kappa_results: dict[str, dict[str, Any]] = {} 144 | 145 | 146 | tqdm_bar = tqdm(atoms_list, desc="Conductivity calculation: ", disable=not prog_bar) 147 | 148 | for atoms in tqdm_bar: 149 | mat_id = atoms.info[ID] 150 | init_info = deepcopy(atoms.info) 151 | mat_name = atoms.info["name"] 152 | mat_desc = f"{mat_name}-{symm_name_map[atoms.info['symm.no']]}" 153 | info_dict = { 154 | "desc": mat_desc, 155 | "name": mat_name, 156 | "initial_space_group_number": atoms.info["symm.no"], 157 | "errors": [], 158 | "error_traceback": [], 159 | } 160 | 161 | tqdm_bar.set_postfix_str(mat_desc, refresh=True) 162 | 163 | # Relaxation 164 | try: 165 | atoms.calc = calc 166 | if max_steps > 0: 167 | if not if_two_stage_relax: 168 | if enforce_relax_symm: 169 | atoms.set_constraint(FixSymmetry(atoms)) 170 | filtered_atoms = filter_cls(atoms, mask=NO_TILT_MASK) 171 | else: 172 | filtered_atoms = filter_cls(atoms) 173 | 174 | optimizer = optim_cls( 175 | filtered_atoms, logfile=f"{out_dir}/relax_{slurm_array_task_id}.log" 176 | ) 177 | optimizer.run(fmax=force_max, steps=max_steps) 178 | 179 | reached_max_steps = False 180 | if optimizer.step == max_steps: 181 | reached_max_steps = True 182 | print( 183 | f"Material {mat_desc=}, {mat_id=} reached max step {max_steps=} during relaxation." 184 | ) 185 | 186 | # maximum residual stress component in for xx,yy,zz and xy,yz,xz components separately 187 | # result is a array of 2 elements 188 | max_stress = atoms.get_stress().reshape((2, 3), order="C").max(axis=1) 189 | 190 | atoms.calc = None 191 | atoms.constraints = None 192 | atoms.info = init_info | atoms.info 193 | 194 | symm_no = get_spacegroup_number(atoms, symprec=symprec) 195 | 196 | relax_dict = { 197 | "structure": aseatoms2str(atoms), 198 | "max_stress": max_stress, 199 | "reached_max_steps": reached_max_steps, 200 | "relaxed_space_group_number": symm_no, 201 | "broken_symmetry": symm_no 202 | != init_info["initial_space_group_number"], 203 | } 204 | 205 | else: 206 | atoms, relax_dict = two_stage_relax( 207 | atoms, 208 | fmax_stage1=force_max, 209 | fmax_stage2=force_max, 210 | steps_stage1=max_steps, 211 | steps_stage2=max_steps, 212 | Optimizer=optim_cls, 213 | Filter=filter_cls, 214 | allow_tilt=False, 215 | log=f"{out_dir}/relax_{slurm_array_task_id}.log", 216 | enforce_symmetry=enforce_relax_symm, 217 | ) 218 | 219 | atoms.calc = None 220 | 221 | except Exception as exc: 222 | warnings.warn(f"Failed to relax {mat_name=}, {mat_id=}: {exc!r}") 223 | traceback.print_exc() 224 | info_dict["errors"].append(f"RelaxError: {exc!r}") 225 | info_dict["error_traceback"].append(traceback.format_exc()) 226 | kappa_results[mat_id] = info_dict 227 | continue 228 | 229 | # Calculation of force sets 230 | try: 231 | ph3 = init_phono3py(atoms, log=False, symprec=symprec) 232 | 233 | ph3, fc2_set, freqs = get_fc2_and_freqs( 234 | ph3, 235 | calculator=calc, 236 | log=False, 237 | pbar_kwargs={"leave": False, "disable": not prog_bar}, 238 | ) 239 | 240 | imaginary_freqs = check_imaginary_freqs(freqs) 241 | freqs_dict = {"imaginary_freqs": imaginary_freqs, "frequencies": freqs} 242 | 243 | # if conductivity condition is met, calculate fc3 244 | ltc_condition = not imaginary_freqs and ( 245 | not relax_dict["broken_symmetry"] or conductivity_broken_symm 246 | ) 247 | 248 | if ltc_condition: 249 | ph3, fc3_set = get_fc3( 250 | ph3, 251 | calculator=calc, 252 | log=False, 253 | pbar_kwargs={"leave": False, "disable": not prog_bar}, 254 | ) 255 | 256 | else: 257 | fc3_set = [] 258 | 259 | if save_forces: 260 | force_results[mat_id] = {"fc2_set": fc2_set, "fc3_set": fc3_set} 261 | 262 | if not ltc_condition: 263 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict 264 | continue 265 | 266 | except Exception as exc: 267 | warnings.warn(f"Failed to calculate force sets {mat_id}: {exc!r}") 268 | traceback.print_exc() 269 | info_dict["errors"].append(f"ForceConstantError: {exc!r}") 270 | info_dict["error_traceback"].append(traceback.format_exc()) 271 | kappa_results[mat_id] = info_dict | relax_dict 272 | continue 273 | 274 | # Calculation of conductivity 275 | try: 276 | ph3, kappa_dict = calculate_conductivity(ph3, log=False) 277 | 278 | except Exception as exc: 279 | warnings.warn(f"Failed to calculate conductivity {mat_id}: {exc!r}") 280 | traceback.print_exc() 281 | info_dict["errors"].append(f"ConductivityError: {exc!r}") 282 | info_dict["error_traceback"].append(traceback.format_exc()) 283 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict 284 | continue 285 | 286 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict | kappa_dict 287 | 288 | 289 | df_kappa = pd.DataFrame(kappa_results).T 290 | df_kappa.index.name = ID 291 | df_kappa.reset_index().to_json(out_path) 292 | 293 | 294 | if save_forces: 295 | force_out_path = ( 296 | f"{out_dir}/force_sets_{slurm_array_job_id}-{slurm_array_task_id:>03}.json.gz" 297 | ) 298 | df_force = pd.DataFrame(force_results).T 299 | df_force = pd.concat([df_kappa, df_force], axis=1) 300 | df_force.index.name = ID 301 | df_force.reset_index().to_json(force_out_path) 302 | -------------------------------------------------------------------------------- /models/ORBv2/2024-11-09-ORB-v2-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/ORBv2/2024-11-09-ORB-v2-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz -------------------------------------------------------------------------------- /models/ORBv2/2024-11-09-ORB-v2-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2024-11-09 13:35:42", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "ORB-v2", 5 | "device": "cpu", 6 | "versions": { 7 | "numpy": "1.26.4", 8 | "torch": "2.2.0" 9 | }, 10 | "ase_optimizer": "FIRE", 11 | "ase_filter": "frechet", 12 | "if_two_stage_relax": true, 13 | "max_steps": 300, 14 | "force_max": 0.0001, 15 | "symprec": 1e-05, 16 | "enforce_relax_symm": true, 17 | "conductivity_broken_symm": false, 18 | "slurm_array_task_count": 34, 19 | "slurm_array_job_id": "1649657", 20 | "task_type": "LTC", 21 | "job_name": "ORB-v2-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 22 | "struct_data_path": "phononDB-PBE-structures.extxyz", 23 | "n_structures": 103 24 | } -------------------------------------------------------------------------------- /models/ORBv2/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "ORB-v2" 10 | 11 | json_file = "k_srme.json.gz" 12 | txt_path = "metrics.txt" 13 | in_file = "conductivity_*-???.json.gz" 14 | 15 | in_folder = f"2024-11-09-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 16 | 17 | # Comparing with the DFT results without 18 | # non-analytical correction term (NAC) 19 | DFT_RESULTS_FILE = DFT_NONAC_REF 20 | 21 | 22 | module_dir = os.path.dirname(__file__) 23 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 24 | out_path = f"{module_dir}/{in_folder}/{json_file}" 25 | 26 | 27 | # Read MLP results 28 | if not glob(in_pattern): 29 | if os.path.exists(out_path): 30 | df_mlp_results = pd.read_json(out_path).set_index(ID) 31 | else: 32 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 33 | 34 | 35 | # df_mlp_results.reset_index().to_json(out_path) 36 | 37 | # Read DFT results 38 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 39 | 40 | 41 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 42 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 43 | 44 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 45 | 46 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 47 | 48 | 49 | # Save results 50 | df_mlp_processed.round(5) 51 | df_mlp_processed.index.name = ID 52 | df_mlp_processed.reset_index().to_json(out_path) 53 | 54 | 55 | # Print 56 | pd.set_option("display.max_rows", None) 57 | pd.set_option("display.max_columns", None) 58 | df_mlp_print = df_mlp_filtered[ 59 | [ 60 | "desc", 61 | "SRME", 62 | "SRE", 63 | "kappa_TOT_ave", 64 | "DFT_kappa_TOT_ave", 65 | "imaginary_freqs", 66 | "errors", 67 | ] 68 | ] 69 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 70 | lambda x: x[0] if not pd.isna(x) else x 71 | ) 72 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 73 | lambda x: x[0] if not pd.isna(x) else x 74 | ) 75 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 76 | 77 | with open(txt_path, "w") as f: 78 | print(f"MODEL: {model_name}", file=f) 79 | print(f"\tmean SRME: {mSRME}", file=f) 80 | print(f"\tmean SRE: {mSRE}", file=f) 81 | 82 | print(df_mlp_print.round(4), file=f) 83 | 84 | 85 | df_mlp_print = df_mlp_print[ 86 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 87 | ] 88 | print(df_mlp_print.round(3)) 89 | 90 | print(f"MODEL: {model_name}") 91 | print(f"\tmean SRME: {mSRME}") 92 | print(f"\tmean SRE: {mSRE}") 93 | -------------------------------------------------------------------------------- /models/SevenNet/1_test_srme.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | import warnings 4 | from typing import Literal, Any 5 | from collections.abc import Callable 6 | import traceback 7 | from copy import deepcopy 8 | from importlib.metadata import version 9 | import json 10 | 11 | import pandas as pd 12 | 13 | from tqdm import tqdm 14 | 15 | from ase.constraints import FixSymmetry 16 | from ase.filters import ExpCellFilter, FrechetCellFilter 17 | from ase.optimize import FIRE, LBFGS 18 | from ase.optimize.optimize import Optimizer 19 | from ase import Atoms 20 | from ase.io import read 21 | 22 | from k_srme import aseatoms2str, two_stage_relax, ID, STRUCTURES, NO_TILT_MASK 23 | from k_srme.utils import symm_name_map, get_spacegroup_number, check_imaginary_freqs 24 | from k_srme.conductivity import ( 25 | init_phono3py, 26 | get_fc2_and_freqs, 27 | get_fc3, 28 | calculate_conductivity, 29 | ) 30 | 31 | import torch 32 | from sevenn.sevennet_calculator import SevenNetCalculator 33 | 34 | warnings.filterwarnings("ignore", category=DeprecationWarning, module="spglib") 35 | 36 | 37 | # EDITABLE CONFIG 38 | model_name = "SevenNet" 39 | checkpoint = "SevenNet-0" 40 | 41 | device = "cuda" if torch.cuda.is_available() else "cpu" 42 | calc = SevenNetCalculator(model=checkpoint, device=device) 43 | 44 | 45 | # Relaxation parameters 46 | ase_optimizer: Literal["FIRE", "LBFGS", "BFGS"] = "FIRE" 47 | ase_filter: Literal["frechet", "exp"] = "frechet" 48 | if_two_stage_relax = True # Use two-stage relaxation enforcing symmetries 49 | max_steps = 300 50 | force_max = 1e-4 # Run until the forces are smaller than this in eV/A 51 | 52 | # Symmetry parameters 53 | # symmetry precision for enforcing relaxation and conductivity calculation 54 | symprec = 1e-5 55 | # Enforce symmetry with during relaxation if broken 56 | enforce_relax_symm = True 57 | # Conductivity to be calculated if symmetry group changed during relaxation 58 | conductivity_broken_symm = False 59 | prog_bar = True 60 | save_forces = True # Save force sets to file 61 | 62 | 63 | slurm_array_task_count = int( 64 | os.getenv( 65 | "K_SRME_RESTART_ARRAY_TASK_COUNT", os.getenv("SLURM_ARRAY_TASK_COUNT", "1") 66 | ) 67 | ) 68 | slurm_array_task_id = int(os.getenv("SLURM_ARRAY_TASK_ID", "0")) 69 | slurm_array_job_id = os.getenv("SLURM_ARRAY_JOB_ID", os.getenv("SLURM_JOB_ID", "debug")) 70 | slurm_array_task_min = int( 71 | os.getenv("K_SRME_RESTART_ARRAY_TASK_MIN", os.getenv("SLURM_ARRAY_TASK_MIN", "0")) 72 | ) 73 | 74 | 75 | task_type = "LTC" # lattice thermal conductivity 76 | job_name = f"{model_name}-phononDB-{task_type}-{ase_optimizer}{'_2SR' if if_two_stage_relax else ''}_force{force_max}_sym{symprec}" 77 | module_dir = os.path.dirname(__file__) 78 | out_dir = os.getenv( 79 | "SBATCH_OUTPUT", 80 | f"{module_dir}/{datetime.datetime.now().strftime('%Y-%m-%d')}-{job_name}", 81 | ) 82 | os.makedirs(out_dir, exist_ok=True) 83 | 84 | out_path = ( 85 | f"{out_dir}/conductivity_{slurm_array_job_id}-{slurm_array_task_id:>03}.json.gz" 86 | ) 87 | 88 | 89 | timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 90 | struct_data_path = STRUCTURES 91 | print(f"\nJob {job_name} started {timestamp}") 92 | 93 | 94 | print(f"Read data from {struct_data_path}") 95 | atoms_list: list[Atoms] = read(struct_data_path, format="extxyz", index=":") 96 | 97 | run_params = { 98 | "timestamp": timestamp, 99 | "k_srme_version": version("k_srme"), 100 | "model_name": model_name, 101 | "checkpoint": checkpoint, 102 | "device": device, 103 | "versions": {dep: version(dep) for dep in ("numpy", "torch")}, 104 | "ase_optimizer": ase_optimizer, 105 | "ase_filter": ase_filter, 106 | "if_two_stage_relax": if_two_stage_relax, 107 | "max_steps": max_steps, 108 | "force_max": force_max, 109 | "symprec": symprec, 110 | "enforce_relax_symm": enforce_relax_symm, 111 | "conductivity_broken_symm": conductivity_broken_symm, 112 | "slurm_array_task_count": slurm_array_task_count, 113 | "slurm_array_job_id": slurm_array_job_id, 114 | "task_type": task_type, 115 | "job_name": job_name, 116 | "struct_data_path": os.path.basename(struct_data_path), 117 | "n_structures": len(atoms_list), 118 | } 119 | 120 | if slurm_array_task_id == slurm_array_task_min: 121 | with open(f"{out_dir}/run_params.json", "w") as f: 122 | json.dump(run_params, f, indent=4) 123 | 124 | if slurm_array_job_id == "debug": 125 | atoms_list = atoms_list[:5] 126 | print("Running in DEBUG mode.") 127 | elif slurm_array_task_count > 1: 128 | # Split the atoms_list into slurm_array_task_count parts trying to make even runtime 129 | atoms_list = atoms_list[ 130 | slurm_array_task_id - slurm_array_task_min :: slurm_array_task_count 131 | ] 132 | 133 | 134 | # Set up the relaxation and force set calculation 135 | filter_cls: Callable[[Atoms], Atoms] = { 136 | "frechet": FrechetCellFilter, 137 | "exp": ExpCellFilter, 138 | }[ase_filter] 139 | optim_cls: Callable[..., Optimizer] = {"FIRE": FIRE, "LBFGS": LBFGS}[ase_optimizer] 140 | 141 | 142 | force_results: dict[str, dict[str, Any]] = {} 143 | kappa_results: dict[str, dict[str, Any]] = {} 144 | 145 | 146 | tqdm_bar = tqdm(atoms_list, desc="Conductivity calculation: ", disable=not prog_bar) 147 | 148 | for atoms in tqdm_bar: 149 | mat_id = atoms.info[ID] 150 | init_info = deepcopy(atoms.info) 151 | mat_name = atoms.info["name"] 152 | mat_desc = f"{mat_name}-{symm_name_map[atoms.info['symm.no']]}" 153 | info_dict = { 154 | "desc": mat_desc, 155 | "name": mat_name, 156 | "initial_space_group_number": atoms.info["symm.no"], 157 | "errors": [], 158 | "error_traceback": [], 159 | } 160 | 161 | tqdm_bar.set_postfix_str(mat_desc, refresh=True) 162 | 163 | # Relaxation 164 | try: 165 | atoms.calc = calc 166 | if max_steps > 0: 167 | if not if_two_stage_relax: 168 | if enforce_relax_symm: 169 | atoms.set_constraint(FixSymmetry(atoms)) 170 | filtered_atoms = filter_cls(atoms, mask=NO_TILT_MASK) 171 | else: 172 | filtered_atoms = filter_cls(atoms) 173 | 174 | optimizer = optim_cls( 175 | filtered_atoms, logfile=f"{out_dir}/relax_{slurm_array_task_id}.log" 176 | ) 177 | optimizer.run(fmax=force_max, steps=max_steps) 178 | 179 | reached_max_steps = False 180 | if optimizer.step == max_steps: 181 | reached_max_steps = True 182 | print( 183 | f"Material {mat_desc=}, {mat_id=} reached max step {max_steps=} during relaxation." 184 | ) 185 | 186 | # maximum residual stress component in for xx,yy,zz and xy,yz,xz components separately 187 | # result is a array of 2 elements 188 | max_stress = atoms.get_stress().reshape((2, 3), order="C").max(axis=1) 189 | 190 | atoms.calc = None 191 | atoms.constraints = None 192 | atoms.info = init_info | atoms.info 193 | 194 | symm_no = get_spacegroup_number(atoms, symprec=symprec) 195 | 196 | relax_dict = { 197 | "structure": aseatoms2str(atoms), 198 | "max_stress": max_stress, 199 | "reached_max_steps": reached_max_steps, 200 | "relaxed_space_group_number": symm_no, 201 | "broken_symmetry": symm_no 202 | != init_info["initial_space_group_number"], 203 | } 204 | 205 | else: 206 | atoms, relax_dict = two_stage_relax( 207 | atoms, 208 | fmax_stage1=force_max, 209 | fmax_stage2=force_max, 210 | steps_stage1=max_steps, 211 | steps_stage2=max_steps, 212 | Optimizer=optim_cls, 213 | Filter=filter_cls, 214 | allow_tilt=False, 215 | log=f"{out_dir}/relax_{slurm_array_task_id}.log", 216 | enforce_symmetry=enforce_relax_symm, 217 | ) 218 | 219 | atoms.calc = None 220 | 221 | except Exception as exc: 222 | warnings.warn(f"Failed to relax {mat_name=}, {mat_id=}: {exc!r}") 223 | traceback.print_exc() 224 | info_dict["errors"].append(f"RelaxError: {exc!r}") 225 | info_dict["error_traceback"].append(traceback.format_exc()) 226 | kappa_results[mat_id] = info_dict 227 | continue 228 | 229 | # Calculation of force sets 230 | try: 231 | ph3 = init_phono3py(atoms, log=False, symprec=symprec) 232 | 233 | ph3, fc2_set, freqs = get_fc2_and_freqs( 234 | ph3, 235 | calculator=calc, 236 | log=False, 237 | pbar_kwargs={"leave": False, "disable": not prog_bar}, 238 | ) 239 | 240 | imaginary_freqs = check_imaginary_freqs(freqs) 241 | freqs_dict = {"imaginary_freqs": imaginary_freqs, "frequencies": freqs} 242 | 243 | # if conductivity condition is met, calculate fc3 244 | ltc_condition = not imaginary_freqs and ( 245 | not relax_dict["broken_symmetry"] or conductivity_broken_symm 246 | ) 247 | 248 | if ltc_condition: 249 | ph3, fc3_set = get_fc3( 250 | ph3, 251 | calculator=calc, 252 | log=False, 253 | pbar_kwargs={"leave": False, "disable": not prog_bar}, 254 | ) 255 | 256 | else: 257 | fc3_set = [] 258 | 259 | if save_forces: 260 | force_results[mat_id] = {"fc2_set": fc2_set, "fc3_set": fc3_set} 261 | 262 | if not ltc_condition: 263 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict 264 | continue 265 | 266 | except Exception as exc: 267 | warnings.warn(f"Failed to calculate force sets {mat_id}: {exc!r}") 268 | traceback.print_exc() 269 | info_dict["errors"].append(f"ForceConstantError: {exc!r}") 270 | info_dict["error_traceback"].append(traceback.format_exc()) 271 | kappa_results[mat_id] = info_dict | relax_dict 272 | continue 273 | 274 | # Calculation of conductivity 275 | try: 276 | ph3, kappa_dict = calculate_conductivity(ph3, log=False) 277 | 278 | except Exception as exc: 279 | warnings.warn(f"Failed to calculate conductivity {mat_id}: {exc!r}") 280 | traceback.print_exc() 281 | info_dict["errors"].append(f"ConductivityError: {exc!r}") 282 | info_dict["error_traceback"].append(traceback.format_exc()) 283 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict 284 | continue 285 | 286 | kappa_results[mat_id] = info_dict | relax_dict | freqs_dict | kappa_dict 287 | 288 | 289 | df_kappa = pd.DataFrame(kappa_results).T 290 | df_kappa.index.name = ID 291 | df_kappa.reset_index().to_json(out_path) 292 | 293 | 294 | if save_forces: 295 | force_out_path = ( 296 | f"{out_dir}/force_sets_{slurm_array_job_id}-{slurm_array_task_id:>03}.json.gz" 297 | ) 298 | df_force = pd.DataFrame(force_results).T 299 | df_force = pd.concat([df_kappa, df_force], axis=1) 300 | df_force.index.name = ID 301 | df_force.reset_index().to_json(force_out_path) 302 | -------------------------------------------------------------------------------- /models/SevenNet/2024-11-09-SevenNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/SevenNet/2024-11-09-SevenNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz -------------------------------------------------------------------------------- /models/SevenNet/2024-11-09-SevenNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2024-11-09 13:44:19", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "SevenNet", 5 | "checkpoint": "SevenNet-0", 6 | "device": "cpu", 7 | "versions": { 8 | "numpy": "1.26.4", 9 | "torch": "2.4.1+cu124" 10 | }, 11 | "ase_optimizer": "FIRE", 12 | "ase_filter": "frechet", 13 | "if_two_stage_relax": true, 14 | "max_steps": 300, 15 | "force_max": 0.0001, 16 | "symprec": 1e-05, 17 | "enforce_relax_symm": true, 18 | "conductivity_broken_symm": false, 19 | "slurm_array_task_count": 34, 20 | "slurm_array_job_id": "1649659", 21 | "task_type": "LTC", 22 | "job_name": "SevenNet-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 23 | "struct_data_path": "phononDB-PBE-structures.extxyz", 24 | "n_structures": 103 25 | } -------------------------------------------------------------------------------- /models/SevenNet/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "SevenNet" 10 | 11 | json_file = "k_srme.json.gz" 12 | txt_path = "metrics.txt" 13 | in_file = "conductivity_*-???.json.gz" 14 | 15 | in_folder = f"2024-11-09-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 16 | 17 | # Comparing with the DFT results without 18 | # non-analytical correction term (NAC) 19 | DFT_RESULTS_FILE = DFT_NONAC_REF 20 | 21 | 22 | module_dir = os.path.dirname(__file__) 23 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 24 | out_path = f"{module_dir}/{in_folder}/{json_file}" 25 | 26 | 27 | # Read MLP results 28 | if not glob(in_pattern): 29 | if os.path.exists(out_path): 30 | df_mlp_results = pd.read_json(out_path).set_index(ID) 31 | else: 32 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 33 | 34 | 35 | # df_mlp_results.reset_index().to_json(out_path) 36 | 37 | # Read DFT results 38 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 39 | 40 | 41 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 42 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 43 | 44 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 45 | 46 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 47 | 48 | 49 | # Save results 50 | df_mlp_processed.round(5) 51 | df_mlp_processed.index.name = ID 52 | df_mlp_processed.reset_index().to_json(out_path) 53 | 54 | 55 | # Print 56 | pd.set_option("display.max_rows", None) 57 | pd.set_option("display.max_columns", None) 58 | df_mlp_print = df_mlp_filtered[ 59 | [ 60 | "desc", 61 | "SRME", 62 | "SRE", 63 | "kappa_TOT_ave", 64 | "DFT_kappa_TOT_ave", 65 | "imaginary_freqs", 66 | "errors", 67 | ] 68 | ] 69 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 70 | lambda x: x[0] if not pd.isna(x) else x 71 | ) 72 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 73 | lambda x: x[0] if not pd.isna(x) else x 74 | ) 75 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 76 | 77 | with open(txt_path, "w") as f: 78 | print(f"MODEL: {model_name}", file=f) 79 | print(f"\tmean SRME: {mSRME}", file=f) 80 | print(f"\tmean SRE: {mSRE}", file=f) 81 | 82 | print(df_mlp_print.round(4), file=f) 83 | 84 | 85 | df_mlp_print = df_mlp_print[ 86 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 87 | ] 88 | print(df_mlp_print.round(3)) 89 | 90 | print(f"MODEL: {model_name}") 91 | print(f"\tmean SRME: {mSRME}") 92 | print(f"\tmean SRE: {mSRE}") 93 | -------------------------------------------------------------------------------- /models/eqV2-full-S/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/eqV2-full-S/.DS_Store -------------------------------------------------------------------------------- /models/eqV2-full-S/2024-11-09-eqV2-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MPA2suite/k_SRME/0269a946a1e4c53552b060a92b98df94e039fd80/models/eqV2-full-S/2024-11-09-eqV2-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/k_srme.json.gz -------------------------------------------------------------------------------- /models/eqV2-full-S/2024-11-09-eqV2-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/run_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2024-11-09 14:03:35", 3 | "k_srme_version": "1.0.0", 4 | "model_name": "eqV2", 5 | "checkpoint": "eqV2_31M_omat_mp_salex.pt", 6 | "versions": { 7 | "numpy": "1.26.4", 8 | "torch": "2.4.0+cu124" 9 | }, 10 | "device": "cpu", 11 | "ase_optimizer": "FIRE", 12 | "ase_filter": "frechet", 13 | "if_two_stage_relax": true, 14 | "max_steps": 300, 15 | "force_max": 0.0001, 16 | "symprec": 1e-05, 17 | "enforce_relax_symm": true, 18 | "conductivity_broken_symm": false, 19 | "slurm_array_task_count": 34, 20 | "slurm_array_job_id": "1649663", 21 | "task_type": "LTC", 22 | "job_name": "eqV2-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05", 23 | "struct_data_path": "phononDB-PBE-structures.extxyz", 24 | "n_structures": 103 25 | } -------------------------------------------------------------------------------- /models/eqV2-full-S/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "eqV2" 10 | 11 | json_file = "k_srme.json.gz" 12 | txt_path = "metrics.txt" 13 | in_file = "conductivity_*-???.json.gz" 14 | 15 | in_folder = f"2024-11-09-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 16 | 17 | # Comparing with the DFT results without 18 | # non-analytical correction term (NAC) 19 | DFT_RESULTS_FILE = DFT_NONAC_REF 20 | 21 | 22 | module_dir = os.path.dirname(__file__) 23 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 24 | out_path = f"{module_dir}/{in_folder}/{json_file}" 25 | 26 | 27 | # Read MLP results 28 | if not glob(in_pattern): 29 | if os.path.exists(out_path): 30 | df_mlp_results = pd.read_json(out_path).set_index(ID) 31 | else: 32 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 33 | 34 | 35 | # df_mlp_results.reset_index().to_json(out_path) 36 | 37 | # Read DFT results 38 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 39 | 40 | 41 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 42 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 43 | 44 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 45 | 46 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 47 | 48 | 49 | # Save results 50 | df_mlp_processed.round(5) 51 | df_mlp_processed.index.name = ID 52 | df_mlp_processed.reset_index().to_json(out_path) 53 | 54 | 55 | # Print 56 | pd.set_option("display.max_rows", None) 57 | pd.set_option("display.max_columns", None) 58 | df_mlp_print = df_mlp_filtered[ 59 | [ 60 | "desc", 61 | "SRME", 62 | "SRE", 63 | "kappa_TOT_ave", 64 | "DFT_kappa_TOT_ave", 65 | "imaginary_freqs", 66 | "errors", 67 | ] 68 | ] 69 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 70 | lambda x: x[0] if not pd.isna(x) else x 71 | ) 72 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 73 | lambda x: x[0] if not pd.isna(x) else x 74 | ) 75 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 76 | 77 | with open(txt_path, "w") as f: 78 | print(f"MODEL: {model_name}", file=f) 79 | print(f"\tmean SRME: {mSRME}", file=f) 80 | print(f"\tmean SRE: {mSRE}", file=f) 81 | 82 | print(df_mlp_print.round(4), file=f) 83 | 84 | 85 | df_mlp_print = df_mlp_print[ 86 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 87 | ] 88 | print(df_mlp_print.round(3)) 89 | 90 | print(f"MODEL: {model_name}") 91 | print(f"\tmean SRME: {mSRME}") 92 | print(f"\tmean SRE: {mSRE}") 93 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "k-srme" 3 | dynamic = ["version"] 4 | description = "Thermal conductivity test for foundation interatomic potentials" 5 | authors = [ 6 | { name = "Balázs Póta" }, 7 | { name = "Gábor Csányi" }, 8 | { name = "Michele Simoncelli", email = "ms2855@cam.ac.uk" }, 9 | { name = "Paramvir Ahlawat" }, 10 | ] 11 | readme = "README.md" 12 | license = { text = "GPL-3.0-only" } 13 | requires-python = ">=3.10" 14 | dependencies = [ 15 | "ase>=3.23.0", 16 | "h5py", 17 | "numpy", 18 | "pandas>=2.2.3", 19 | "spglib", 20 | "tqdm", 21 | ] 22 | classifiers = [ 23 | "Development Status :: 3 - Alpha", 24 | "Intended Audience :: Science/Research", 25 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 26 | "Programming Language :: Python :: 3.10", 27 | "Programming Language :: Python :: 3.11", 28 | "Programming Language :: Python :: 3.12", 29 | ] 30 | 31 | [project.optional-dependencies] 32 | dev = ["phono3py", "phonopy>=2.26.6"] 33 | 34 | [project.urls] 35 | homepage = "https://github.com/MPA2suite/k-srme" 36 | 37 | 38 | [tool.setuptools.packages.find] 39 | where = ["."] 40 | include = ["k_srme*"] 41 | 42 | [tool.setuptools.dynamic] 43 | version = { attr = "k_srme.__version__" } 44 | -------------------------------------------------------------------------------- /scripts/2_evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | import pandas as pd 4 | 5 | from k_srme import glob2df, ID, DFT_NONAC_REF 6 | from k_srme.benchmark import get_metrics, process_benchmark_descriptors 7 | 8 | 9 | model_name = "MACE" 10 | 11 | json_file = "k_srme.json.gz" 12 | txt_path = "metrics.txt" 13 | in_file = "conductivity_*-???.json.gz" 14 | 15 | in_folder = f"2024-11-09-{model_name}-phononDB-LTC-FIRE_2SR_force0.0001_sym1e-05/" 16 | 17 | # Comparing with the DFT results without 18 | # non-analytical correction term (NAC), which is in closer agreement with to MLP results 19 | DFT_RESULTS_FILE = DFT_NONAC_REF 20 | 21 | 22 | module_dir = os.path.dirname(__file__) 23 | in_pattern = f"{module_dir}/{in_folder}/{in_file}" 24 | out_path = f"{module_dir}/{in_folder}/{json_file}" 25 | 26 | 27 | # Read MLP results 28 | if not glob(in_pattern): 29 | if os.path.exists(out_path): 30 | df_mlp_results = pd.read_json(out_path).set_index(ID) 31 | else: 32 | df_mlp_results = glob2df(in_pattern, max_files=None).set_index(ID) 33 | 34 | 35 | # df_mlp_results.reset_index().to_json(out_path) 36 | 37 | # Read DFT results 38 | df_dft_results = pd.read_json(DFT_RESULTS_FILE).set_index(ID) 39 | 40 | 41 | df_mlp_filtered = df_mlp_results[df_mlp_results.index.isin(df_dft_results.index)] 42 | df_mlp_filtered = df_mlp_filtered.reindex(df_dft_results.index) 43 | 44 | df_mlp_processed = process_benchmark_descriptors(df_mlp_filtered, df_dft_results) 45 | 46 | mSRE, mSRME, rmseSRE, rmseSRME = get_metrics(df_mlp_filtered) 47 | 48 | 49 | # Save results 50 | df_mlp_processed.round(5) 51 | df_mlp_processed.index.name = ID 52 | df_mlp_processed.reset_index().to_json(out_path) 53 | 54 | 55 | # Print 56 | pd.set_option("display.max_rows", None) 57 | pd.set_option("display.max_columns", None) 58 | df_mlp_print = df_mlp_filtered[ 59 | [ 60 | "desc", 61 | "SRME", 62 | "SRE", 63 | "kappa_TOT_ave", 64 | "DFT_kappa_TOT_ave", 65 | "imaginary_freqs", 66 | "errors", 67 | ] 68 | ] 69 | df_mlp_print["DFT_kappa_TOT_ave"] = df_mlp_print["DFT_kappa_TOT_ave"].apply( 70 | lambda x: x[0] if not pd.isna(x) else x 71 | ) 72 | df_mlp_print["kappa_TOT_ave"] = df_mlp_print["kappa_TOT_ave"].apply( 73 | lambda x: x[0] if not pd.isna(x) else x 74 | ) 75 | df_mlp_print["SRME_failed"] = df_mlp_print["SRME"].apply(lambda x: x == 2) 76 | 77 | with open(txt_path, "w") as f: 78 | print(f"MODEL: {model_name}", file=f) 79 | print(f"\tmean SRME: {mSRME}", file=f) 80 | print(f"\tmean SRE: {mSRE}", file=f) 81 | 82 | print(df_mlp_print.round(4), file=f) 83 | 84 | 85 | df_mlp_print = df_mlp_print[ 86 | ["desc", "SRME", "SRE", "kappa_TOT_ave", "DFT_kappa_TOT_ave"] 87 | ] 88 | print(df_mlp_print.round(3)) 89 | 90 | print(f"MODEL: {model_name}") 91 | print(f"\tmean SRME: {mSRME}") 92 | print(f"\tmean SRE: {mSRE}") 93 | --------------------------------------------------------------------------------