├── README.md ├── examples └── SFE_AIM_Fe │ ├── Niessen_Li_Werner_Lu_Vitos_Villa_Somers_2023.pdf │ ├── atoms │ ├── fcc │ └── hcp │ ├── job_SFE_AIM_V_fixed.py │ ├── job_SFE_AIM_Vcoa_fixed.py │ ├── job_SFE_AIM_coa_fixed.py │ └── job_SFE_AIM_optim.py ├── operations ├── cell_relaxation_isif2.json ├── cell_relaxation_isif2_coa.json ├── encut_optimization.json ├── fit_eos_isif2.json ├── fit_eos_isif2_coa.json ├── kpoint_optimization.json └── sfe_aim.json ├── results └── SFE_AIM_Vcoa_fixed │ └── 0_1684837818_log.output ├── settings ├── hostnames.json ├── vasp_default.json ├── vasp_high_accuracy.json ├── vasp_high_accuracy_largeSC.json ├── vasp_largeSC.json └── vasp_largeSC_interstitial.json ├── spec-file.txt ├── vasplib.py ├── workflow_engine.py └── workflows ├── SFE_AIM_PCs_V_fixed.json ├── SFE_AIM_PCs_Vcoa_fixed.json ├── SFE_AIM_PCs_coa_fixed.json ├── SFE_AIM_PCs_optim.json └── SFE_AIM_largeSCs_Vcoa_fixed.json /README.md: -------------------------------------------------------------------------------- 1 | # DFT automatization using VASP and ASE 2 | This repo contains a library that aims at automatizing some Density Functional Theory (DFT) workflows in [**VASP**](https://vasp.at/) by using the [**ASE**](https://wiki.fysik.dtu.dk/ase/index.html) toolkit. The workflows are losely based on the wonderful ressources provided by [Prof. John Kitchin](http://kitchingroup.cheme.cmu.edu/dft-book/dft.html). 3 | I only implement what I need for my own research and I share it here in the hope that it might benefit others. 4 | 5 | *This repo has been entirely revamped on 11/05/2023. You can find the legacy version of ASE_VASP_automation [here](https://github.com/frankNiessen/ASE_VASP_automation/releases/tag/v0.9).* 6 | 7 | ## Installation 8 | - To install the ASE_VASP_automation software, paste this folder wherever it is convenient for you on your computer and add **export PYTHONPATH=:$PYTHONPATH** to your **~/.bashrc** file. 9 | - You might want to use the [spec file](https://github.com/frankNiessen/ASE_VASP_automation/blob/master/spec-file.txt) to install all required python packages into a virtual conda environment by running **conda create --name env_vasp_ase_automation --file spec-file.txt**. 10 | - You need to have **ASE** [properly installed](https://wiki.fysik.dtu.dk/ase/ase/calculators/vasp.html) and set up to communicate with VASP. 11 | - You need to have a properly working and licensed **VASP** installation on your local computer or a high performance cluster. 12 | - The scripts can be run from anywhere, meaning that you could cut and paste the example folder to wherever you store your code. 13 | 14 | ## Program structure 15 | The program is build to run simple tasks that are defined within *.json* files. Within the example folder, few scripts are provided in which workflows are defined. Workflows comprise a certain order of tasks that are conducted on defined atomic structures given a set of parameters. The folder *settings* contains settings files that can be used in conjunction with different workflows. 16 | 17 | ## Examples 18 | The currently only provided example is on stacking fault energy determination using the axial interaction model (see our [recent paper](https://github.com/frankNiessen/ASE_VASP_automation/blob/master/examples/SFE_AIM_Fe/Niessen_Li_Werner_Lu_Vitos_Villa_Somers_2023.pdf) on its application to Fe-C, Fe-N and austenitic stainless steel). Here 4 different relaxation modes are offered: volume relaxation, relaxation of the c/a ratio, volume relaxation and relaxation of the c/a ratio, and no relaxation at all. Finally, the SFE value is determined using the axial interaction model. 19 | 20 | ## Parallelization 21 | The scripts can be run as-is on a personal computer, by default it will do so with *kpar = 1* and *ncore = 1*. If you have **VASP** and **ASE** installed on a High Performance Cluster (*HPC*), or if you have a parallelized version of **VASP** compiled on your personal computer, you can supply the arguments *kpar* and *ncore* on the command line. As an example, you can run the calculations with *kpar = 2* and *ncore = 16* by executing: 22 | *job.py 2 16* 23 | 24 | If you want to run **VASP** in a parallel environment, you will have to set the **ASE** environment variable *ASE_VASP_COMMAND* to *"mpirun vasp_std"*. 25 | 26 | ## Contribute 27 | If you find any errors, feel free to submit a pull request or to adress them in the "Issues" tab. If you want to contribute to this code, please get into contact with me. Also feel tree to fork the code and develop your own alterations. 28 | -------------------------------------------------------------------------------- /examples/SFE_AIM_Fe/Niessen_Li_Werner_Lu_Vitos_Villa_Somers_2023.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frankNiessen/ASE_VASP_automation/a8f0fdf5f4c81085e77d0fbe942f793ec3c346c6/examples/SFE_AIM_Fe/Niessen_Li_Werner_Lu_Vitos_Villa_Somers_2023.pdf -------------------------------------------------------------------------------- /examples/SFE_AIM_Fe/atoms/fcc: -------------------------------------------------------------------------------- 1 | Fe fcc 2 | 1 3 | 0.5 0.5 0.0 4 | 0.0 0.5 0.5 5 | 0.5 0.0 0.5 6 | Fe 7 | 1 8 | cartesian 9 | 0 0 0 10 | -------------------------------------------------------------------------------- /examples/SFE_AIM_Fe/atoms/hcp: -------------------------------------------------------------------------------- 1 | Fe2 hcp_ideal 2 | 1 # a/sqrt(2) 3 | 1.00000000 0.00000000 0.00000000 4 | -0.50000000 0.86602540 0.00000000 5 | 0.00000000 0.00000000 1.63299316 6 | Fe 7 | 2 8 | cartesian 9 | 0.00000000 0.00000000 0.00000000 Fe 10 | 0.00000000 0.57735027 0.81649658 Fe 11 | -------------------------------------------------------------------------------- /examples/SFE_AIM_Fe/job_SFE_AIM_V_fixed.py: -------------------------------------------------------------------------------- 1 | # %% 2 | import sys 3 | import os 4 | import json 5 | import os 6 | import numpy as np 7 | from ase.calculators.vasp import Vasp 8 | from ase.io import read 9 | import numpy as np 10 | import vasplib 11 | 12 | 13 | def workflow_etot(settings): 14 | vasplib.prnt_header( 15 | 'Running E_tot workflow: ' + settings['workflow_name'] + ' on cell ' + settings['atoms']) 16 | atoms = read(settings['job_dir'] + '/' + settings['atoms_dir'] + '/' + 17 | settings['atoms'], format=settings['software']) 18 | 19 | # Initialize VASP calculation 20 | vasp_settings = vasplib.libdir('/settings/' + settings["software_settings"]) 21 | calc = vasplib.ini_vasp_calculation(vasp_settings,atoms, settings["vasp_settings"]) 22 | 23 | # Set volume 24 | calc.atoms.set_cell( 25 | calc.get_atoms().cell * settings["lattice_param"], scale_atoms=True) 26 | initial_volume = calc.get_atoms().get_volume() 27 | 28 | # Execute tasks 29 | for t in settings["tasks"]: 30 | with open(vasplib.libdir('/tasks/' + t + '.json'), 'r') as openfile: 31 | # Load task 32 | task = json.load(openfile) 33 | # Attach additional information 34 | task.update(settings['task_input']) 35 | task["outdir"] = settings['job_dir'] + '/' + settings['result_dir'] + '/' + \ 36 | settings['atoms'] + '/' + t 37 | 38 | # Execute task 39 | calc, result, unit = getattr(vasplib, task["name"])(calc, task) 40 | # Write and plot results 41 | vasplib.result2json(task, settings, result, unit) 42 | if task["makeplot"]: 43 | vasplib.makeplot( 44 | task['value'], result["etot"], task['param'], task['unit']) 45 | vasplib.saveplot( 46 | task["outdir"], settings["job_id"] + "_" + task['name'] + '_etot.png') 47 | 48 | return result, unit 49 | 50 | 51 | def workflow_post(settings): 52 | vasplib.prnt_header( 53 | 'Running Post workflow: ' + settings['workflow_name']) 54 | 55 | # Execute tasks 56 | for t in settings["tasks"]: 57 | with open(vasplib.libdir('/tasks/' + t + '.json'), 'r') as openfile: 58 | # Load task 59 | task = json.load(openfile) 60 | # Attach additional information 61 | task.update(settings['task_input']) 62 | task["outdir"] = settings['job_dir'] + '/' + settings['result_dir'] + '/' + \ 63 | settings['workflow_name'] + '/' + t 64 | # Execute task 65 | result, unit = getattr(vasplib, task["name"])(task) 66 | # Write and plot results 67 | vasplib.result2json(task, settings, result, unit) 68 | if task["makeplot"]: 69 | vasplib.makeplot( 70 | task['value'], result["etot"], task['param'], task['unit']) 71 | vasplib.saveplot( 72 | task["outdir"], settings["job_id"] + "_" + task['name'] + '_etot.png') 73 | 74 | return result, unit 75 | 76 | 77 | if __name__ == '__main__': 78 | 79 | # Global settings 80 | settings = {'atoms_dir': 'atoms', 81 | 'job_dir': os.path.dirname(__file__), 82 | 'software_settings': 'vasp_default', 83 | 'result_dir': 'results/'+os.path.basename(sys.argv[0]).split('.')[0], 84 | 'software': 'vasp', 85 | 'vasp_settings': vasplib.get_par_settings(sys.argv) 86 | } 87 | settings["job_id"] = vasplib.make_jobid(0) 88 | 89 | # Log command line output 90 | sys.stdout = vasplib.Logger( 91 | settings['job_dir'] + '/' + settings["result_dir"] + "/" + settings["job_id"] + "_log.output") 92 | print(' ///// AIM fcc - hcp calculations - ' + settings["job_id"]) 93 | 94 | # FCC workflow settings 95 | settings_fcc = { 96 | 'workflow_name': 'Relax fcc', 97 | 'atoms': 'fcc', 98 | 'lattice_param': 3.573, 99 | 'task_input': {'job_id': settings["job_id"]}, 100 | 'tasks': ['kpoint_optimization']} 101 | settings_fcc.update(settings) 102 | 103 | # FCC workflow 104 | result, unit = workflow_etot(settings_fcc) 105 | 106 | # HCP workflow settings 107 | settings_hcp = { 108 | 'workflow_name': 'Relax hcp', 109 | 'atoms': 'hcp', 110 | 'lattice_param': (result["v0"][-1] * 4)**(1 / 3) / np.sqrt(2), 111 | 'task_input': {'job_id': settings["job_id"]}, 112 | 'tasks': ['kpoint_optimization', 'cell_relaxation_isif2_coa', 'fit_eos_isif2_coa']} 113 | settings_hcp.update(settings) 114 | 115 | # HCP workflow 116 | result, unit = workflow_etot(settings_hcp) 117 | 118 | # SFE AIM workflow settings 119 | settings_sfe_aim = { 120 | 'workflow_name': 'SFE AIM', 121 | 'tasks': ['sfe_aim'], 122 | 'task_input': { 123 | 'software': settings['software'], 124 | 'atoms_dir': settings['job_dir'] + "/" + settings['atoms_dir'], 125 | 'result_dir': settings['job_dir'] + "/" + settings["result_dir"], 126 | 'job_id': settings["job_id"], 127 | # parent, child - order matters! 128 | 'atoms': ['fcc', 'hcp'], 129 | 'etot_source': ['kpoint_optimization', 'fit_eos_isif2_coa'], 130 | 'etot_job': [settings["job_id"], settings["job_id"]], 131 | 'etot_index': [-1, -1] 132 | } 133 | } 134 | settings_sfe_aim.update(settings) 135 | 136 | # SFE AIM workflow 137 | result, unit = workflow_post(settings_sfe_aim) 138 | 139 | # %% 140 | -------------------------------------------------------------------------------- /examples/SFE_AIM_Fe/job_SFE_AIM_Vcoa_fixed.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import os 4 | import numpy as np 5 | from ase.calculators.vasp import Vasp 6 | from ase.io import read 7 | import numpy as np 8 | import vasplib 9 | 10 | 11 | def workflow_etot(settings): 12 | vasplib.prnt_header( 13 | 'Running E_tot workflow: ' + settings['workflow_name'] + ' on cell ' + settings['atoms']) 14 | atoms = read(settings['job_dir'] + '/' + settings['atoms_dir'] + '/' + 15 | settings['atoms'], format=settings['software']) 16 | 17 | # Initialize VASP calculation 18 | vasp_settings = vasplib.libdir('/settings/' + settings["software_settings"]) 19 | calc = vasplib.ini_vasp_calculation(vasp_settings,atoms, settings["vasp_settings"]) 20 | 21 | # Set volume 22 | calc.atoms.set_cell( 23 | calc.get_atoms().cell * settings["lattice_param"], scale_atoms=True) 24 | initial_volume = calc.get_atoms().get_volume() 25 | 26 | # Execute tasks 27 | for t in settings["tasks"]: 28 | with open(vasplib.libdir('/tasks/' + t + '.json'), 'r') as openfile: 29 | # Load task 30 | task = json.load(openfile) 31 | # Attach additional information 32 | task.update(settings['task_input']) 33 | task["outdir"] = settings['job_dir'] + '/' + settings['result_dir'] + '/' + \ 34 | settings['atoms'] + '/' + t 35 | 36 | # Execute task 37 | calc, result, unit = getattr(vasplib, task["name"])(calc, task) 38 | # Write and plot results 39 | vasplib.result2json(task, settings, result, unit) 40 | if task["makeplot"]: 41 | vasplib.makeplot( 42 | task['value'], result["etot"], task['param'], task['unit']) 43 | vasplib.saveplot( 44 | task["outdir"], settings["job_id"] + "_" + task['name'] + '_etot.png') 45 | 46 | return result, unit 47 | 48 | 49 | def workflow_post(settings): 50 | vasplib.prnt_header( 51 | 'Running Post workflow: ' + settings['workflow_name']) 52 | 53 | # Execute tasks 54 | for t in settings["tasks"]: 55 | with open(vasplib.libdir('/tasks/' + t + '.json'), 'r') as openfile: 56 | # Load task 57 | task = json.load(openfile) 58 | # Attach additional information 59 | task.update(settings['task_input']) 60 | task["outdir"] = settings['job_dir'] + '/' + settings['result_dir'] + '/' + \ 61 | settings['workflow_name'] + '/' + t 62 | # Execute task 63 | result, unit = getattr(vasplib, task["name"])(task) 64 | # Write and plot results 65 | vasplib.result2json(task, settings, result, unit) 66 | if task["makeplot"]: 67 | vasplib.makeplot( 68 | task['value'], result["etot"], task['param'], task['unit']) 69 | vasplib.saveplot( 70 | task["outdir"], settings["job_id"] + "_" + task['name'] + '_etot.png') 71 | 72 | return result, unit 73 | 74 | 75 | if __name__ == '__main__': 76 | 77 | # Global settings 78 | settings = {'atoms_dir': 'atoms', 79 | 'job_dir': os.path.dirname(__file__), 80 | 'software_settings': 'vasp_default', 81 | 'result_dir': 'results/'+os.path.basename(sys.argv[0]).split('.')[0], 82 | 'software': 'vasp', 83 | 'vasp_settings': vasplib.get_par_settings(sys.argv) 84 | } 85 | 86 | settings["job_id"] = vasplib.make_jobid(0) 87 | 88 | # Log command line output 89 | sys.stdout = vasplib.Logger( 90 | settings['job_dir'] + '/' + settings["result_dir"] + "/" + settings["job_id"] + "_log.output") 91 | print(' ///// AIM fcc - hcp calculations - ' + settings["job_id"]) 92 | 93 | # FCC workflow settings 94 | settings_fcc = { 95 | 'workflow_name': 'Relax fcc', 96 | 'atoms': 'fcc', 97 | 'lattice_param': 3.573, 98 | 'task_input': {'job_id': settings["job_id"]}, 99 | 'tasks': ['kpoint_optimization']} 100 | settings_fcc.update(settings) 101 | 102 | # FCC workflow 103 | result, unit = workflow_etot(settings_fcc) 104 | 105 | # HCP workflow settings 106 | settings_hcp = { 107 | 'workflow_name': 'Relax hcp', 108 | 'atoms': 'hcp', 109 | 'lattice_param': (result["v0"][-1] * 4)**(1 / 3) / np.sqrt(2), 110 | 'task_input': {'job_id': settings["job_id"]}, 111 | 'tasks': ['kpoint_optimization']} 112 | settings_hcp.update(settings) 113 | 114 | # HCP workflow 115 | result, unit = workflow_etot(settings_hcp) 116 | 117 | # SFE AIM workflow settings 118 | settings_sfe_aim = { 119 | 'workflow_name': 'SFE AIM', 120 | 'tasks': ['sfe_aim'], 121 | 'task_input': { 122 | 'software': settings['software'], 123 | 'atoms_dir': settings['job_dir'] + "/" + settings['atoms_dir'], 124 | 'result_dir': settings['job_dir'] + "/" + settings["result_dir"], 125 | 'job_id': settings["job_id"], 126 | # parent, child - order matters! 127 | 'atoms': ['fcc', 'hcp'], 128 | 'etot_source': ['fit_eos_isif2', 'kpoint_optimization'], 129 | 'etot_job': [settings["job_id"], settings["job_id"]], 130 | 'etot_index': [-1, -1] 131 | } 132 | } 133 | settings_sfe_aim.update(settings) 134 | 135 | # SFE AIM workflow 136 | result, unit = workflow_post(settings_sfe_aim) 137 | -------------------------------------------------------------------------------- /examples/SFE_AIM_Fe/job_SFE_AIM_coa_fixed.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import json 4 | import numpy as np 5 | from ase.io import read 6 | import numpy as np 7 | import vasplib 8 | 9 | # Workflow functions 10 | def workflow_etot(settings): 11 | vasplib.prnt_header( 12 | 'Running E_tot workflow: ' + settings['workflow_name'] + ' on cell ' + settings['atoms']) 13 | atoms = read(settings['job_dir'] + '/' + settings['atoms_dir'] + '/' + 14 | settings['atoms'], format=settings['software']) 15 | 16 | # Initialize VASP calculation 17 | vasp_settings = vasplib.libdir('/settings/' + settings["software_settings"]) 18 | calc = vasplib.ini_vasp_calculation(vasp_settings,atoms, settings["vasp_settings"]) 19 | 20 | # Set volume 21 | calc.atoms.set_cell( 22 | calc.get_atoms().cell * settings["lattice_param"], scale_atoms=True) 23 | initial_volume = calc.get_atoms().get_volume() 24 | 25 | # Execute tasks 26 | for t in settings["tasks"]: 27 | with open(vasplib.libdir('/tasks/' + t + '.json'), 'r') as openfile: 28 | # Load task 29 | task = json.load(openfile) 30 | # Attach additional information 31 | task.update(settings['task_input']) 32 | task["outdir"] = settings['job_dir'] + '/' + settings['result_dir'] + '/' + \ 33 | settings['atoms'] + '/' + t 34 | 35 | # Execute task 36 | calc, result, unit = getattr(vasplib, task["name"])(calc, task) 37 | # Write and plot results 38 | vasplib.result2json(task, settings, result, unit) 39 | if task["makeplot"]: 40 | vasplib.makeplot( 41 | task['value'], result["etot"], task['param'], task['unit']) 42 | vasplib.saveplot( 43 | task["outdir"], settings["job_id"] + "_" + task['name'] + '_etot.png') 44 | 45 | return result, unit 46 | 47 | 48 | def workflow_post(settings): 49 | vasplib.prnt_header( 50 | 'Running Post workflow: ' + settings['workflow_name']) 51 | 52 | # Execute tasks 53 | for t in settings["tasks"]: 54 | with open(vasplib.libdir('/tasks/' + t + '.json'), 'r') as openfile: 55 | # Load task 56 | task = json.load(openfile) 57 | # Attach additional information 58 | task.update(settings['task_input']) 59 | task["outdir"] = settings['job_dir'] + '/' + settings['result_dir'] + '/' + \ 60 | settings['workflow_name'] + '/' + t 61 | # Execute task 62 | result, unit = getattr(vasplib, task["name"])(task) 63 | # Write and plot results 64 | vasplib.result2json(task, settings, result, unit) 65 | if task["makeplot"]: 66 | vasplib.makeplot( 67 | task['value'], result["etot"], task['param'], task['unit']) 68 | vasplib.saveplot( 69 | task["outdir"], settings["job_id"] + "_" + task['name'] + '_etot.png') 70 | 71 | return result, unit 72 | 73 | #### Main Section ### 74 | if __name__ == '__main__': 75 | 76 | # Global settings 77 | settings = {'atoms_dir': 'atoms', 78 | 'software_settings': 'vasp_default', 79 | 'job_dir': os.path.dirname(__file__), 80 | 'result_dir': 'results/'+os.path.basename(sys.argv[0]).split('.')[0], 81 | 'software': 'vasp', 82 | 'vasp_settings': vasplib.get_par_settings(sys.argv) 83 | } 84 | 85 | settings["job_id"] = vasplib.make_jobid(0) 86 | 87 | 88 | # Log command line output 89 | sys.stdout = vasplib.Logger( 90 | settings['job_dir'] + '/' + settings["result_dir"] + "/" + settings["job_id"] + "_log.output") 91 | print(' ///// AIM fcc - hcp calculations - ' + settings["job_id"]) 92 | 93 | # FCC workflow settings 94 | settings_fcc = { 95 | 'workflow_name': 'Relax fcc', 96 | 'atoms': 'fcc', 97 | 'lattice_param': 3.573, 98 | 'task_input': {'job_id': settings["job_id"]}, 99 | 'tasks': ['kpoint_optimization', 'cell_relaxation_isif2', 'fit_eos_isif2']} 100 | settings_fcc.update(settings) 101 | 102 | # FCC workflow 103 | result, unit = workflow_etot(settings_fcc) 104 | 105 | # HCP workflow settings 106 | settings_hcp = { 107 | 'workflow_name': 'Relax hcp', 108 | 'atoms': 'hcp', 109 | 'lattice_param': (result["v0"][-1] * 4)**(1 / 3) / np.sqrt(2), 110 | 'task_input': {'job_id': settings["job_id"]}, 111 | 'tasks': ['kpoint_optimization']} 112 | settings_hcp.update(settings) 113 | 114 | # HCP workflow 115 | result, unit = workflow_etot(settings_hcp) 116 | 117 | # SFE AIM workflow settings 118 | settings_sfe_aim = { 119 | 'workflow_name': 'SFE AIM', 120 | 'tasks': ['sfe_aim'], 121 | 'task_input': { 122 | 'software': settings['software'], 123 | 'atoms_dir': settings['job_dir'] + "/" + settings['atoms_dir'], 124 | 'result_dir': settings['job_dir'] + "/" + settings["result_dir"], 125 | 'job_id': settings["job_id"], 126 | # parent, child - order matters! 127 | 'atoms': ['fcc', 'hcp'], 128 | 'etot_source': ['fit_eos_isif2', 'kpoint_optimization'], 129 | 'etot_job': [settings["job_id"], settings["job_id"]], 130 | 'etot_index': [-1, -1] 131 | } 132 | } 133 | settings_sfe_aim.update(settings) 134 | 135 | # SFE AIM workflow 136 | result, unit = workflow_post(settings_sfe_aim) 137 | -------------------------------------------------------------------------------- /examples/SFE_AIM_Fe/job_SFE_AIM_optim.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import os 4 | import numpy as np 5 | from ase.calculators.vasp import Vasp 6 | from ase.io import read 7 | import numpy as np 8 | import vasplib 9 | 10 | 11 | def workflow_etot(settings): 12 | vasplib.prnt_header( 13 | 'Running E_tot workflow: ' + settings['workflow_name'] + ' on cell ' + settings['atoms']) 14 | atoms = read(settings['job_dir'] + '/' + settings['atoms_dir'] + '/' + 15 | settings['atoms'], format=settings['software']) 16 | 17 | # Initialize VASP calculation 18 | vasp_settings = vasplib.libdir('/settings/' + settings["software_settings"]) 19 | calc = vasplib.ini_vasp_calculation(vasp_settings,atoms, settings["vasp_settings"]) 20 | 21 | # Set volume 22 | calc.atoms.set_cell( 23 | calc.get_atoms().cell * settings["lattice_param"], scale_atoms=True) 24 | initial_volume = calc.get_atoms().get_volume() 25 | 26 | # Execute tasks 27 | for t in settings["tasks"]: 28 | with open(vasplib.libdir('/tasks/' + t + '.json'), 'r') as openfile: 29 | # Load task 30 | task = json.load(openfile) 31 | # Attach additional information 32 | task.update(settings['task_input']) 33 | task["outdir"] = settings['job_dir'] + '/' + settings['result_dir'] + '/' + \ 34 | settings['atoms'] + '/' + t 35 | 36 | # Execute task 37 | calc, result, unit = getattr(vasplib, task["name"])(calc, task) 38 | # Write and plot results 39 | vasplib.result2json(task, settings, result, unit) 40 | if task["makeplot"]: 41 | vasplib.makeplot( 42 | task['value'], result["etot"], task['param'], task['unit']) 43 | vasplib.saveplot( 44 | task["outdir"], settings["job_id"] + "_" + task['name'] + '_etot.png') 45 | 46 | return result, unit 47 | 48 | 49 | def workflow_post(settings): 50 | vasplib.prnt_header( 51 | 'Running Post workflow: ' + settings['workflow_name']) 52 | 53 | # Execute tasks 54 | for t in settings["tasks"]: 55 | with open(vasplib.libdir('/tasks/' + t + '.json'), 'r') as openfile: 56 | # Load task 57 | task = json.load(openfile) 58 | # Attach additional information 59 | task.update(settings['task_input']) 60 | task["outdir"] = settings['job_dir'] + '/' + settings['result_dir'] + '/' + \ 61 | settings['workflow_name'] + '/' + t 62 | # Execute task 63 | result, unit = getattr(vasplib, task["name"])(task) 64 | # Write and plot results 65 | vasplib.result2json(task, settings, result, unit) 66 | if task["makeplot"]: 67 | vasplib.makeplot( 68 | task['value'], result["etot"], task['param'], task['unit']) 69 | vasplib.saveplot( 70 | task["outdir"], settings["job_id"] + "_" + task['name'] + '_etot.png') 71 | 72 | return result, unit 73 | 74 | 75 | if __name__ == '__main__': 76 | 77 | # Global settings 78 | settings = {'atoms_dir': 'atoms', 79 | 'job_dir': os.path.dirname(__file__), 80 | 'software_settings': 'vasp_default', 81 | 'result_dir': 'results/'+os.path.basename(sys.argv[0]).split('.')[0], 82 | 'software': 'vasp', 83 | 'vasp_settings': vasplib.get_par_settings(sys.argv) 84 | } 85 | settings["job_id"] = vasplib.make_jobid(0) 86 | 87 | # Log command line output 88 | sys.stdout = vasplib.Logger( 89 | settings['job_dir'] + '/' + settings["result_dir"] + "/" + settings["job_id"] + "_log.output") 90 | print(' ///// AIM fcc - hcp calculations - ' + settings["job_id"]) 91 | 92 | # FCC workflow settings 93 | settings_fcc = { 94 | 'workflow_name': 'Relax fcc', 95 | 'atoms': 'fcc', 96 | 'lattice_param': 3.573, 97 | 'task_input': {'job_id': settings["job_id"]}, 98 | 'tasks': ['kpoint_optimization', 'cell_relaxation_isif2', 'fit_eos_isif2']} 99 | settings_fcc.update(settings) 100 | 101 | # FCC workflow 102 | result, unit = workflow_etot(settings_fcc) 103 | 104 | # HCP workflow settings 105 | settings_hcp = { 106 | 'workflow_name': 'Relax hcp', 107 | 'atoms': 'hcp', 108 | 'lattice_param': (result["v0"][-1] * 4)**(1 / 3) / np.sqrt(2), 109 | 'task_input': {'job_id': settings["job_id"]}, 110 | 'tasks': ['kpoint_optimization', 'cell_relaxation_isif2_coa', 'fit_eos_isif2_coa']} 111 | settings_hcp.update(settings) 112 | 113 | # HCP workflow 114 | result, unit = workflow_etot(settings_hcp) 115 | 116 | # SFE AIM workflow settings 117 | settings_sfe_aim = { 118 | 'workflow_name': 'SFE AIM', 119 | 'tasks': ['sfe_aim'], 120 | 'task_input': { 121 | 'software': settings['software'], 122 | 'atoms_dir': settings['job_dir'] + "/" + settings['atoms_dir'], 123 | 'result_dir': settings['job_dir'] + "/" + settings["result_dir"], 124 | 'job_id': settings["job_id"], 125 | # parent, child - order matters! 126 | 'atoms': ['fcc', 'hcp'], 127 | 'etot_source': ['fit_eos_isif2', 'kpoint_optimization'], 128 | 'etot_job': [settings["job_id"], settings["job_id"]], 129 | 'etot_index': [-1, -1] 130 | } 131 | } 132 | settings_sfe_aim.update(settings) 133 | 134 | # SFE AIM workflow 135 | result, unit = workflow_post(settings_sfe_aim) 136 | -------------------------------------------------------------------------------- /operations/cell_relaxation_isif2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cell_relaxation", 3 | "method": "isif2 - volume relaxation", 4 | "isif": 2, 5 | "param": "volume_expansion", 6 | "unit": "1", 7 | "makeplot": 1 8 | } -------------------------------------------------------------------------------- /operations/cell_relaxation_isif2_coa.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cell_relaxation_hcp", 3 | "method": "constantV", 4 | "isif": 2, 5 | "param": "coa", 6 | "unit": "1", 7 | "makeplot": 1 8 | } -------------------------------------------------------------------------------- /operations/encut_optimization.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "param_convergence", 3 | "param": "encut", 4 | "unit": "1", 5 | "epsilon_value": 5e-4, 6 | "epsilon_unit": "eV", 7 | "stop_when_converged": true, 8 | "value": [250,260,270,280,290,300,310,320,330,340,350,360,370,380,390,400,410,420,430,440,450,460,470,480,490,500], 9 | "makeplot": 1 10 | } -------------------------------------------------------------------------------- /operations/fit_eos_isif2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fit_eos", 3 | "method": "sj", 4 | "x": "v0", 5 | "y": "etot", 6 | "silent": 0, 7 | "makeplot": 0 8 | } -------------------------------------------------------------------------------- /operations/fit_eos_isif2_coa.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fit_eos", 3 | "input_dataset": "cell_relaxation_isif2_coa", 4 | "method": "sj", 5 | "x": "coa", 6 | "y": "etot", 7 | "silent": 0, 8 | "makeplot": 0 9 | } -------------------------------------------------------------------------------- /operations/kpoint_optimization.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kpoint_optimization", 3 | "param": "kpts", 4 | "unit": "1", 5 | "epsilon_value": 5e-4, 6 | "epsilon_unit": "eV", 7 | "stop_when_converged": true, 8 | "makeplot": 1 9 | } -------------------------------------------------------------------------------- /operations/sfe_aim.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sfe_aim", 3 | "makeplot": 0 4 | } -------------------------------------------------------------------------------- /results/SFE_AIM_Vcoa_fixed/0_1684837818_log.output: -------------------------------------------------------------------------------- 1 | ///// SFE AIM Primitive Cells - 0_1684837818 ///// 2 | Start time: 12:30:18 hh:mm:ss 3 | --------------------------- 4 | Running Task: relax_fcc 5 | --------------------------- 6 | -------------------------------------------------------------------------------- /settings/hostnames.json: -------------------------------------------------------------------------------- 1 | { 2 | "Local": ["MEK-425-026-01"], 3 | "HPC": ["svol.fysik.dtu.dk"] 4 | } -------------------------------------------------------------------------------- /settings/vasp_default.json: -------------------------------------------------------------------------------- 1 | { 2 | "ase_version": "3.21.1", 3 | "vasp_version": null, 4 | "inputs": { 5 | "encut": 300, 6 | "sigma": 0.2, 7 | "ediff": 1e-06, 8 | "gga": "PE", 9 | "prec": "normal", 10 | "ibrion": -1, 11 | "icharg": 1, 12 | "ismear": 1, 13 | "nsw": 0, 14 | "xc": "PBE", 15 | "pp": "PBE", 16 | "setups": "recommended", 17 | "txt": "vasp.out", 18 | "gamma": false, 19 | "reciprocal": false, 20 | "ignore_constraints": false, 21 | "lreal": "Auto", 22 | "custom": {}, 23 | "lcharg": ".TRUE." 24 | } 25 | } -------------------------------------------------------------------------------- /settings/vasp_high_accuracy.json: -------------------------------------------------------------------------------- 1 | { 2 | "ase_version": "3.21.1", 3 | "vasp_version": null, 4 | "inputs": { 5 | "prec": "accurate", 6 | "ediff": 1e-6, 7 | "encut": 320, 8 | "ismear": 1, 9 | "sigma": 0.2, 10 | "lreal": "Auto" 11 | } 12 | } -------------------------------------------------------------------------------- /settings/vasp_high_accuracy_largeSC.json: -------------------------------------------------------------------------------- 1 | { 2 | "ase_version": "3.21.1", 3 | "vasp_version": null, 4 | "inputs": { 5 | "prec": "accurate", 6 | "ediff": 1e-6, 7 | "encut": 320, 8 | "ismear": 1, 9 | "sigma": 0.2, 10 | "symprec": 1e-4, 11 | "lreal": "Auto" 12 | } 13 | } -------------------------------------------------------------------------------- /settings/vasp_largeSC.json: -------------------------------------------------------------------------------- 1 | { 2 | "ase_version": "3.21.1", 3 | "vasp_version": null, 4 | "inputs": { 5 | "encut": 300, 6 | "sigma": 0.2, 7 | "ediff": 1e-06, 8 | "gga": "PE", 9 | "prec": "normal", 10 | "ibrion": -1, 11 | "icharg": 1, 12 | "ismear": 1, 13 | "nsw": 0, 14 | "xc": "PBE", 15 | "pp": "PBE", 16 | "setups": "recommended", 17 | "txt": "vasp.out", 18 | "gamma": false, 19 | "reciprocal": false, 20 | "ignore_constraints": false, 21 | "lreal": "Auto", 22 | "custom": {}, 23 | "lcharg": ".TRUE.", 24 | "lplane": ".TRUE." 25 | } 26 | } -------------------------------------------------------------------------------- /settings/vasp_largeSC_interstitial.json: -------------------------------------------------------------------------------- 1 | { 2 | "ase_version": "3.21.1", 3 | "vasp_version": null, 4 | "inputs": { 5 | "encut": 300, 6 | "sigma": 0.2, 7 | "ediff": 1e-06, 8 | "gga": "PE", 9 | "prec": "normal", 10 | "ibrion": -1, 11 | "icharg": 1, 12 | "ismear": 1, 13 | "nsw": 0, 14 | "xc": "PBE", 15 | "pp": "PBE", 16 | "setups": "recommended", 17 | "txt": "vasp.out", 18 | "gamma": false, 19 | "reciprocal": false, 20 | "ignore_constraints": false, 21 | "lreal": "Auto", 22 | "custom": {}, 23 | "lcharg": ".TRUE.", 24 | "lplane": ".TRUE.", 25 | "symprec": 1e-4 26 | } 27 | } -------------------------------------------------------------------------------- /spec-file.txt: -------------------------------------------------------------------------------- 1 | # This file may be used to create an environment using: 2 | # $ conda create --name --file 3 | # platform: linux-64 4 | @EXPLICIT 5 | https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 6 | https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2023.5.7-hbcca054_0.conda 7 | https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda 8 | https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2 9 | https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2 10 | https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.11-3_cp311.conda 11 | https://conda.anaconda.org/conda-forge/noarch/tzdata-2023c-h71feb2d_0.conda 12 | https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.2.0-h69a702a_19.tar.bz2 13 | https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.2.0-h65d4601_19.tar.bz2 14 | https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 15 | https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.2.0-h65d4601_19.tar.bz2 16 | https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2 17 | https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2 18 | https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2 19 | https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.18-h0b41bf4_0.conda 20 | https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.5.0-hcb278e6_1.conda 21 | https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 22 | https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-2.1.5.1-h0b41bf4_0.conda 23 | https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2 24 | https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2 25 | https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda 26 | https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.3.0-h0b41bf4_0.conda 27 | https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2 28 | https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2 29 | https://conda.anaconda.org/conda-forge/linux-64/openssl-3.1.0-hd590300_3.conda 30 | https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2 31 | https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.9-h7f98852_0.tar.bz2 32 | https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2 33 | https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 34 | https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2 35 | https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2 36 | https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2 37 | https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda 38 | https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.41.2-h2797004_1.conda 39 | https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2 40 | https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda 41 | https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2 42 | https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h3eb15da_6.conda 43 | https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2 44 | https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda 45 | https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2 46 | https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2 47 | https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-ha587672_6.conda 48 | https://conda.anaconda.org/conda-forge/linux-64/python-3.11.3-h2755cc3_0_cpython.conda 49 | https://conda.anaconda.org/conda-forge/noarch/blinker-1.6.2-pyhd8ed1ab_0.conda 50 | https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2 51 | https://conda.anaconda.org/conda-forge/noarch/certifi-2023.5.7-pyhd8ed1ab_0.conda 52 | https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.1.0-pyhd8ed1ab_0.conda 53 | https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2 54 | https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2 55 | https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2 56 | https://conda.anaconda.org/conda-forge/noarch/itsdangerous-2.1.2-pyhd8ed1ab_0.tar.bz2 57 | https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py311h4dd048b_1.tar.bz2 58 | https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.15-haa2dc70_1.conda 59 | https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.2-py311h2582759_0.conda 60 | https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2 61 | https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.3-py311h64a7726_0.conda 62 | https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-hfec8fc6_2.conda 63 | https://conda.anaconda.org/conda-forge/noarch/ordered-set-4.1.0-pyhd8ed1ab_0.tar.bz2 64 | https://conda.anaconda.org/conda-forge/linux-64/orjson-3.8.12-py311h34b1e23_0.conda 65 | https://conda.anaconda.org/conda-forge/noarch/packaging-23.1-pyhd8ed1ab_0.conda 66 | https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2 67 | https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2 68 | https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2 69 | https://conda.anaconda.org/conda-forge/noarch/setuptools-67.7.2-pyhd8ed1ab_0.conda 70 | https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2 71 | https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.5.0-pyha770c72_0.conda 72 | https://conda.anaconda.org/conda-forge/noarch/wheel-0.40.0-pyhd8ed1ab_0.conda 73 | https://conda.anaconda.org/conda-forge/noarch/zipp-3.15.0-pyhd8ed1ab_0.conda 74 | https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py311h409f033_3.conda 75 | https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.7-py311ha3edf6b_0.conda 76 | https://conda.anaconda.org/conda-forge/noarch/deepdiff-6.3.0-pyhd8ed1ab_0.conda 77 | https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.39.4-py311h459d7ec_0.conda 78 | https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.6.0-pyha770c72_0.conda 79 | https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2 80 | https://conda.anaconda.org/conda-forge/linux-64/pillow-9.5.0-py311h573f0d3_0.conda 81 | https://conda.anaconda.org/conda-forge/noarch/pip-23.1.2-pyhd8ed1ab_0.conda 82 | https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2 83 | https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.5.0-hd8ed1ab_0.conda 84 | https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.4-pyhd8ed1ab_0.conda 85 | https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py311hd4cff14_1005.tar.bz2 86 | https://conda.anaconda.org/conda-forge/linux-64/cryptography-40.0.2-py311h9b4c7bb_0.conda 87 | https://conda.anaconda.org/conda-forge/noarch/flask-2.3.2-pyhd8ed1ab_0.conda 88 | https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.7.1-py311h8597a09_0.conda 89 | https://conda.anaconda.org/conda-forge/noarch/platformdirs-3.5.1-pyhd8ed1ab_0.conda 90 | https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.1.1-pyhd8ed1ab_0.conda 91 | https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.15-pyhd8ed1ab_0.conda 92 | https://conda.anaconda.org/conda-forge/noarch/requests-2.29.0-pyhd8ed1ab_0.conda 93 | https://conda.anaconda.org/conda-forge/noarch/pooch-1.7.0-pyha770c72_3.conda 94 | https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.1-py311h64a7726_3.conda 95 | https://conda.anaconda.org/conda-forge/noarch/ase-3.22.1-pyhd8ed1ab_1.tar.bz2 96 | -------------------------------------------------------------------------------- /vasplib.py: -------------------------------------------------------------------------------- 1 | from ase.calculators.calculator import equal 2 | from ase.calculators.vasp import Vasp 3 | # from vasp_interactive import VaspInteractive as Vasp 4 | from ase.optimize import BFGS 5 | from ase.eos import EquationOfState 6 | from ase.io import read 7 | from ase import lattice 8 | from deepdiff import DeepDiff 9 | from datetime import datetime 10 | import time 11 | import json 12 | import numpy as np 13 | import matplotlib 14 | import matplotlib.pyplot as plt 15 | import os 16 | import math 17 | import sys 18 | 19 | '''This is a library for autoamted routines for VASP using ASE''' 20 | matplotlib.use('Agg') 21 | 22 | 23 | def param_convergence(calc, operation): 24 | '''Parameter convergence study by iterative parameter change and potential energy calculation''' 25 | prnt_subheader('Running ' + operation['name']) 26 | print( 27 | ' -Tolerance: {0:0.2e}'.format(operation['epsilon_value']) + operation['epsilon_unit']) 28 | if 'stop_when_converged' in operation.keys() and operation['stop_when_converged']: 29 | print(' -Stopping when converged') 30 | 31 | # Prepare variables 32 | values = operation['value'] 33 | outdir = operation['outdir'] 34 | result, unit = __result_template__(operation['param'], operation['unit']) 35 | 36 | # Loop over parameter and calculate the total energy 37 | for idx, value in enumerate(values): 38 | if idx >= 1: 39 | calc_info = calc.asdict() 40 | print(str(idx + 1) + '/' + str(len(values)) + ' - ' + operation['name'] + ': ' + 41 | str(value) + ' (' + operation['unit'] + '),', end="", flush=True) 42 | if all(isinstance(x, int) for x in values): 43 | calcdir = outdir + '/raw/{0}'.format(value) 44 | elif all(isinstance(x, float) for x in operation['value']): 45 | calcdir = outdir + '/raw/{0:1.2f}'.format(value) 46 | elif all(isinstance(x, list) for x in operation['value']): 47 | outstr = format(value).replace( 48 | ",", "_").strip("[]").replace(" ", "") 49 | calcdir = outdir + '/raw/' + outstr 50 | calc.set(directory=calcdir) 51 | eval('calc.set(' + operation['param'] + '=' + str(value) + ')') 52 | 53 | calc, e, v = calculate(calc) 54 | result["etot"].append(e) 55 | result["v0"].append(v) 56 | result["atoms"].append(calc.atoms.todict()) 57 | result[operation["param"]].append(value) 58 | 59 | # Print energies and check for convergence 60 | if 'epsilon_value' in operation.keys() and idx > 0 and np.abs(result["etot"][idx] - result["etot"][idx - 1]) < operation['epsilon_value']: 61 | if 'stop_when_converged' in operation.keys() and operation['stop_when_converged']: 62 | print("Stopping convergence loop: Difference in e_pot < {0:0.2e} {1}".format( 63 | operation['epsilon_value'], operation['epsilon_unit'])) 64 | operation['value'][idx + 1:] = [] 65 | txt = calc.txt 66 | calc = Vasp() 67 | calc.fromdict(calc_info) 68 | calc.txt = txt 69 | break 70 | 71 | return calc, result, unit 72 | 73 | 74 | def kpoint_optimization(calc, task): 75 | task['value'] = kpoint_lists(calc, task['value']) 76 | return param_convergence(calc, task) 77 | 78 | 79 | def cell_relaxation(calc, task): 80 | '''Cell relaxation by systematically varied cell volume and potential energy calculation''' 81 | atoms = calc.get_atoms() 82 | v0 = atoms.get_volume() 83 | cell0 = atoms.get_cell() 84 | calc.set(isif=task["isif"]) 85 | 86 | # Prepare variables 87 | values = task['value'] 88 | outdir = task['outdir'] 89 | result, unit = __result_template__(task['param'], task['unit']) 90 | 91 | prnt_subheader('Running ' + task['name']) 92 | for idx, value in enumerate(task['value']): 93 | print(str(idx + 1) + '/' + str(len(task['value'])) + ' - ' + task['name'] + 94 | ': {0:1.2f}'.format(value) + ' (' + task['unit'] + '),', end="", flush=True) 95 | atoms = stretch_cell(atoms, cell0, value) 96 | calc.set(atoms=atoms) 97 | 98 | calc_dir = outdir + '/raw/{0:1.2f}'.format(value) 99 | calc.set(directory=calc_dir) 100 | calc, e, v = calculate(calc) 101 | result["etot"].append(e) 102 | result["v0"].append(v) 103 | result["atoms"].append(calc.atoms.todict()) 104 | result[task["param"]].append(value) 105 | 106 | # Reset atoms to cell0 107 | atoms = stretch_cell(atoms, cell0, 1) 108 | calc.set(atoms=atoms) 109 | return calc, result, unit 110 | 111 | 112 | def fit_eos(calc, operation): 113 | # Fit an equation of state 114 | 115 | # Find and open data 116 | filename = os.path.abspath(os.path.join( 117 | operation["outdir"], os.pardir)) + "/" + operation["input_dataset"] + "/results/" + operation["job_id"] + ".json" 118 | with open(filename, 'r') as j: 119 | data = json.loads(j.read()) 120 | 121 | # Define variables for eos fit 122 | x = data["results"][operation["x"]] 123 | y = data["results"][operation["y"]] 124 | result, unit = __result_template__("B", "eV/A^3") 125 | 126 | # eos fit 127 | eos = EquationOfState(x, y) 128 | x, y, dxy = eos.fit() 129 | result[operation["x"]] = [x] 130 | result[operation["y"]] = [y] 131 | result["B"] = [dxy] 132 | # eos command window output 133 | print("") 134 | print('.......... Equation of State ..........') 135 | print('{0}_0 = {1:0.4e} ({2})'.format( 136 | operation["x"], result[operation["x"]][0], data["units"][operation["x"]])) 137 | print('{0}_0 = {1:0.4e} ({2})'.format( 138 | operation["y"], result[operation["y"]][0], data["units"][operation["y"]])) 139 | print('{0}_0 = {1:0.4e} ({2})'.format("B", result["B"][0], unit["B"])) 140 | print('.......................................') 141 | 142 | # eos plotting 143 | eos.plot() 144 | saveplot(operation["outdir"], operation["job_id"] + 145 | "_" + operation['name'] + '.png') 146 | return calc, result, unit 147 | 148 | 149 | def cell_relaxation_hcp(calc, task): 150 | '''Cell relaxation by systematically varied c/a ratio of hcp cell''' 151 | atoms0 = calc.get_atoms() 152 | v0 = atoms0.get_volume() 153 | cell0 = atoms0.get_cell().copy() 154 | a0 = cell0[0, 0] 155 | calc.set(isif=task["isif"]) 156 | # Prepare variables 157 | values = task['value'] 158 | outdir = task['outdir'] 159 | result, unit = __result_template__(task['param'], task['unit']) 160 | 161 | if not task['method'] == 'constantV': 162 | cell0 = None 163 | 164 | prnt_subheader('Running ' + task['name'] + ' - ' + task['method']) 165 | for id, value in enumerate(values): 166 | atoms = stretch_cell(atoms0, cell0, [a0, value], cell0) 167 | calc.set(atoms=atoms) 168 | a_plot = atoms.cell.array[0, 0] # Update a 169 | print('{0:d}/{1:d}: '.format(id + 1, len(values)) 170 | + task['name'] + 171 | ': {0:1.4f} [{1}], {2:1.4f} [{3}]'.format( 172 | a_plot, "AA", value, task['unit']), 173 | end="", flush=True) 174 | calc_dir = outdir + \ 175 | '/raw/{0:d}-{1:d}_{2:1.4f}--{3:1.4f}'.format( 176 | id + 1, len(values), a_plot, value) 177 | calc.set(directory=calc_dir) 178 | calc, e, v = calculate(calc) 179 | result["etot"].append(e) 180 | result["v0"].append(v) 181 | result["atoms"].append(calc.atoms.todict()) 182 | result[task["param"]].append(value) 183 | return calc, result, unit 184 | 185 | 186 | # def cell_refinement(calcs, job_info): 187 | # '''Cell relaxation by refinement of previous calculation and potential energy calculation''' 188 | # energy, volume = [], [] 189 | # param = {'name': 'volume', 190 | # 'unit': 'AA^3'} 191 | # proj_dir = job_info['subdir'] + '/' + \ 192 | # job_info['name'] + '/' + job_info['method'] 193 | 194 | # prnt_subheader('Running cell refinement: ' + job_info['method'] + ' ...') 195 | # for idx, calc in enumerate(calcs): 196 | # atoms = calc.get_atoms() 197 | # print(str(idx + 1) + '/' + str(len(calcs)) + ' - Refining job ' + job_info['load_dir'] + 198 | # ': volume {0:1.2f} AA^3,'.format(atoms.get_volume()), end="", flush=True) 199 | # calcname = os.path.basename(calc.directory) 200 | # calc_dir = proj_dir + '/' + calcname 201 | # calc.set(directory=calc_dir) 202 | # calc.set(atoms=atoms) 203 | # calc, e, v = calculate(calc) 204 | # energy.append(e) 205 | # volume.append(v) 206 | 207 | # # Save table 208 | # result2json(proj_dir, volume, energy, param) 209 | 210 | # # Plot and save figure 211 | # makeplot(volume, energy, param) 212 | # saveplot(proj_dir, param['name'] + '_vs_energy.png') 213 | 214 | # # Fit an equation of state 215 | # eos = EquationOfState(volume, energy) 216 | # v0, e0, B = eos.fit() 217 | # print('.......... Equation of State ..........') 218 | # print('v0 = {0} AA^3, E0 = {1} eV, B = {2} eV/A^3'.format(v0, e0, B)) 219 | # eos.plot() 220 | # saveplot(proj_dir, 'equation_of_state.png') 221 | 222 | # # Fit an equation of state 223 | # if len(energy) > 1: 224 | # eos = EquationOfState(volume, energy) 225 | # v0, e0, B = eos.fit() 226 | # print('.......... Equation of State ..........') 227 | # print('v0 = {0} AA^3, E0 = {1} eV, B = {2} eV/A^3'.format(v0, e0, B)) 228 | # eos.plot() 229 | # saveplot(proj_dir, 'equation_of_state.png') 230 | 231 | # # Compare to previous results 232 | # data = __fromjson__(job_info) 233 | # if data: 234 | # plt.figure() 235 | # makeplot(volume, energy, param) 236 | # makeplot(data['volume (AA^3)'], data['energies (eV)'], param) 237 | # plt.legend(['refined', 'initial'], loc='best') 238 | # saveplot(proj_dir, 'refinement_results.png') 239 | # return [volume, energy, [v0, e0, B]] 240 | # else: 241 | # print('v0 = {0} AA^3, E0 = {1} eV'.format(volume, energy)) 242 | # return [volume, energy, []] 243 | 244 | # return [volume, energy, [v0, e0, B]] 245 | 246 | 247 | def stretch_cell(atoms, cell0, param, cell_ref=None): 248 | if type(param) == float: 249 | cell_factor = param**(1. / 3.) 250 | atoms.set_cell(cell0 * cell_factor, scale_atoms=True) 251 | elif type(param) == list: 252 | lat = cell0.get_bravais_lattice() 253 | if type(lat) == lattice.HEX: # hexagonal 254 | if not (len(param) == 2): 255 | raise Exception( 256 | 'a in Angstrom and the c/a ratios are required for a hexagonal cell') 257 | if cell_ref: 258 | a_old = cell_ref.array[0, 0] 259 | coa_old = cell_ref.array[2, 2] / a_old 260 | coa_new = param[1] 261 | a_new = param[0] * (coa_old / coa_new)**(1 / 3) 262 | atoms.set_cell([a_new, a_new, a_new * param[1], 263 | cell0.angles()[0], cell0.angles()[1], cell0.angles()[2]], scale_atoms=True) 264 | else: 265 | atoms.set_cell([param[0], param[0], param[0] * param[1], 266 | cell0.angles()[0], cell0.angles()[1], cell0.angles()[2]], scale_atoms=True) 267 | 268 | return atoms 269 | 270 | 271 | def kpoint_lists(calc, kpoints): 272 | lattice_vectors = [np.linalg.norm(c) for c in calc.get_atoms().cell] 273 | ratios = [(1 / l) / (1 / np.min(lattice_vectors)) for l in lattice_vectors] 274 | kpoints = [[k * ratios[0], k * ratios[1], k * ratios[2]] 275 | for k in kpoints] # Make set of 3 k-points 276 | return [[round(k, 0) for k in ks] 277 | for ks in kpoints] # round 278 | 279 | 280 | def is_equal_calc(calc, calc_loaded, ignore_params=['kpar', 'ncore']): 281 | # Check if loaded calculation exists 282 | if calc_loaded == []: 283 | return False 284 | 285 | # Check for initial POSCAR file 286 | if not os.path.isfile(calc_loaded.directory + '/POSCAR0'): 287 | return False 288 | else: 289 | atoms_loaded = read(calc_loaded.directory + '/POSCAR0', format='vasp') 290 | 291 | # Check if the initial structures are identical 292 | equal_atoms = (calc.atoms.positions - atoms_loaded.positions < 1e-16).all() 293 | equal_cells = (calc.atoms.cell == atoms_loaded.cell).all() 294 | equal_elements = (calc.atoms.symbols == atoms_loaded.symbols).all() 295 | if not (equal_atoms and equal_cells and equal_elements): 296 | return False 297 | 298 | # Check differences in the calculation parameters 299 | diff = DeepDiff(calc.todict(), calc_loaded.todict()) 300 | if not 'values_changed' in diff: 301 | return True 302 | for iparam in ignore_params: 303 | if "root['" + iparam + "']" in diff['values_changed']: 304 | del diff['values_changed']["root['" + iparam + "']"] 305 | is_equal = not bool(diff['values_changed']) 306 | return is_equal 307 | 308 | 309 | def calculate(calc): 310 | calc_load = load_calc(calc.directory) 311 | if is_equal_calc(calc, calc_load) and calc_load.converged: 312 | energy = calc_load.get_potential_energy() 313 | volume = calc_load.get_atoms().get_volume() 314 | else: 315 | atoms_ini = calc.atoms.copy() 316 | energy = calc.get_potential_energy() 317 | __error_check__ 318 | volume = calc.atoms.get_volume() 319 | atoms_ini.write(calc.directory + '/POSCAR0', 'vasp') 320 | calc.write_json("calc_relaxed.json") 321 | print(" e_pot: {0:1.6f} eV, v: {1:1.6f} AA^3".format(energy, volume)) 322 | return calc, energy, volume 323 | 324 | 325 | def load_calc(load_dir, raise_error=False): 326 | import os 327 | # Load previous calculation 328 | if os.path.exists(load_dir) and os.path.exists(load_dir + '/OUTCAR'): 329 | try: 330 | calc = Vasp(directory=load_dir, restart=True) 331 | except: 332 | return [] 333 | elif os.path.exists(load_dir): 334 | load_dirs = sorted([x[0] for x in os.walk(load_dir)][1:]) 335 | if all([os.path.exists(s + '/OUTCAR') for s in load_dirs]): 336 | calc = [Vasp(directory=v, restart=True) for v in load_dirs] 337 | else: 338 | if raise_error: 339 | raise Exception( 340 | '"job_info[load_dir]" does not point to a VASP directory!') 341 | else: 342 | calc = [] 343 | else: 344 | if raise_error: 345 | raise Exception('Path ' + load_dir + ' does not exist!') 346 | else: 347 | calc = [] 348 | return calc 349 | 350 | 351 | def get_valence_electrons(calc, filename=None): 352 | """ 353 | Return the number of valence electrons for the atoms. 354 | Calculated from the POTCAR file. 355 | from https://github.com/jkitchin/vasp#vaspget_valence_electrons 356 | """ 357 | 358 | default_electrons = get_default_number_of_electrons(calc, filename) 359 | 360 | d = {} 361 | for s, n in default_electrons: 362 | d[s] = n 363 | atoms = calc.get_atoms() 364 | 365 | nelectrons = 0 366 | for atom in atoms: 367 | nelectrons += d[atom.symbol] 368 | return int(nelectrons) 369 | 370 | 371 | def get_default_number_of_electrons(calc, filename): 372 | """ 373 | Return the default electrons for each species. 374 | from https://github.com/jkitchin/vasp#vaspget_default_number_of_electrons 375 | """ 376 | if filename is None: 377 | filename = os.path.join(calc.directory, 'POTCAR') 378 | 379 | calc.write_input(calc.get_atoms()) 380 | 381 | nelect = [] 382 | lines = open(filename).readlines() 383 | for n, line in enumerate(lines): 384 | if line.find('TITEL') != -1: 385 | symbol = line.split('=')[1].split()[1].split('_')[0].strip() 386 | valence = float(lines[n + 4].split(';')[1] 387 | .split('=')[1].split()[0].strip()) 388 | nelect.append((symbol, valence)) 389 | return nelect 390 | 391 | 392 | def get_number_of_bands(atoms, job_info, f=1.0): 393 | """ with open(proj_dir+'/tables'+'/' + param['name'][0].replace('/','_over_') 394 | + '_' + param['name'][1].replace('/','-over-') + '_vs_energy.json', 'w') as f: 395 | f.write(json.dumps({param['name'][0]+' ('+param['unit'][0]+')': x[0].flatten(order='F').tolist(), 396 | param['name'][1]+' ('+param['unit'][1]+')': x[1].flatten(order='F').tolist(), 397 | 'energies (eV)': energy.flatten().tolist()})) 398 | Determines the number of bands for a structure according to 399 | https://www.vasp.at/wiki/index.php/Number_of_bands_NBANDS 400 | and an optional prefactor f 401 | Args: 402 | atoms (Atoms): ASE collection of atoms https://wiki.fysik.dtu.dk/ase/ase/atoms.html 403 | job_inf (dict): Dictionary with job info on directories 404 | f (float): Linear prefactor to increase the number of bands 405 | Returns: 406 | (nbands): Number of bands. 407 | """ 408 | calc = Vasp(directory=job_info['subdir'] + '/.temp') 409 | calc.set(atoms=atoms) 410 | n_electrons = get_valence_electrons(calc) 411 | n_ions = atoms.get_global_number_of_atoms() 412 | n_bands = int((0.5 * (n_electrons + n_ions)) * f) 413 | print("The cell has {0} valence electrons and {1} ions. For f = {2:1.1f} the number of bands should be 0.5*f*(N_ELEC+N_ION) = {3}".format( 414 | n_electrons, n_ions, f, n_bands)) 415 | return n_bands 416 | 417 | 418 | def get_number_of_cores(n_bands, fac_bpc=8, base=[24, 40]): 419 | """ 420 | Determines the number of cores based on the number of bands according to 421 | https://www.nsc.liu.se/~pla/blog/2015/01/12/vasp-how-many-cores/ 422 | based on a bands_per_core factor and base (cores per node) 423 | Args: 424 | n_bands (int): Number ob bands 425 | fac_bpc (int): Bands_per_core factor 426 | base ([int]): Cores per node to consider 427 | Returns: 428 | Text output 429 | """ 430 | n_cores = math.ceil(n_bands / fac_bpc) 431 | n_cores_r = [int(b * math.ceil(float(n_cores) / b)) for b in base] 432 | n_nodes_r = [int(n_cores_r[i] / b) for i, b in enumerate(base)] 433 | mod = [n_cores % b for b in base] 434 | n_bands_r = [int(fac_bpc * n) for n in n_cores_r] 435 | for i, b in enumerate(base): 436 | print("............") 437 | print("Using {0}-core nodes the number of cores is {1} and the number of nodes {2}.".format( 438 | b, n_cores_r[i], n_nodes_r[i])) 439 | print("{0} extra cores were added, increasing the number of bands from {1} to {2}.".format( 440 | mod[i], n_bands, n_bands_r[i])) 441 | 442 | 443 | def get_par_settings(argv): 444 | # Try mapping command line arguments to function argument 445 | if len(sys.argv[1:]) == 2: 446 | ncore = int(sys.argv[1]) 447 | kpar = int(sys.argv[2]) 448 | elif not len(sys.argv[1:]) and os.cpu_count() / 2 == 12: # MEK-425-026-01 449 | ncore = 3 450 | kpar = 4 451 | else: 452 | ncore = 1 453 | kpar = 1 454 | print(" *Running with npar={0:d} and kpar={1:d}".format(ncore, kpar)) 455 | return {"ncore": ncore, "kpar": kpar} 456 | 457 | 458 | def __error_check__(calc): 459 | '''Checking for errors after VASP calculation''' 460 | 461 | if not calc.converged: 462 | print("Stopping convergence loop: Calculation has not converged!") 463 | raise Exception('VASP did not converge') 464 | if [True for s in calc.load_file('OUTCAR') if 'TOO FEW BANDS!!' in s]: 465 | raise Exception( 466 | 'Too few bands! Increase the parameter NBANDS in file INCAR to ensure that the highest band is unoccupied at all k-points') 467 | 468 | 469 | def sfe_aim(etot, n_at, a): 470 | # SFE calculation according to Axial Interaction Model 471 | area = np.sqrt(3) / 4 * (a)**2 * 1e-20 472 | value = {"sfe": [2 * (etot[1] / n_at[1] - etot[0] / 473 | n_at[0]) * 1.602177e-16 / (area)]} 474 | unit = {"sfe": "mJ.m^(-2)"} 475 | print("The SFE from the AIM approach is {0:0.2f} {1}".format( 476 | value["sfe"][0], unit["sfe"])) 477 | return value, unit 478 | 479 | 480 | def save_workflow(workflow): 481 | if not os.path.exists(workflow["run_options"]["result_dir"]): 482 | os.makedirs(workflow["run_options"]["result_dir"]) 483 | 484 | with open(workflow["run_options"]["result_dir"] + '/workflow_' + workflow["run_options"]["job_id"] + '.json', 'w') as f: 485 | f.write(json.dumps(workflow, cls=NumpyEncoder)) 486 | 487 | 488 | def result2json(operation, task, run_options, results, unit): 489 | '''Export results to *.json file''' 490 | time = datetime.today().strftime('%Y-%m-%d_%H-%M-%S') 491 | outdir = operation["outdir"] + '/results' 492 | if not os.path.exists(outdir): 493 | os.makedirs(outdir) 494 | 495 | with open(outdir + '/' + run_options["job_id"] + '.json', 'w') as f: 496 | f.write(json.dumps({"info": {"job_id": run_options["job_id"], "time": time}, 497 | "operation": operation, "task": task, "run_options": run_options, "results": results, "units": unit}, cls=NumpyEncoder)) 498 | 499 | 500 | class NumpyEncoder(json.JSONEncoder): 501 | """ Special json encoder for numpy types """ 502 | 503 | def default(self, obj): 504 | if isinstance(obj, np.integer): 505 | return int(obj) 506 | elif isinstance(obj, np.floating): 507 | return float(obj) 508 | elif isinstance(obj, np.ndarray): 509 | return obj.tolist() 510 | return json.JSONEncoder.default(self, obj) 511 | 512 | 513 | def __fromjson__(job_info): 514 | '''Import results from *.json file''' 515 | fname = job_info['subdir'] + '/' + \ 516 | job_info['name'] + '/' + job_info['prev_results'] 517 | if os.path.exists(fname): 518 | f = open(fname) 519 | data = json.load(f) 520 | return data 521 | 522 | 523 | def __result_template__(param_name=None, param_unit=None): 524 | values = {"etot": [], "v0": [], "atoms": []} 525 | units = {"etot": "eV", "v0": "AA^3", "atoms": ""} 526 | if param_name: 527 | values[param_name] = [] 528 | if param_unit: 529 | units[param_name] = param_unit 530 | return values, units 531 | 532 | 533 | def ini_vasp_calculation(json_string, atoms=None, additional_settings=None, txt="vasp.out"): 534 | calc = Vasp() 535 | calc.read_json(json_string + ".json") 536 | if atoms: 537 | calc.set(atoms=atoms) 538 | if additional_settings: 539 | calc.fromdict({"inputs": additional_settings}) 540 | calc.txt = txt 541 | return calc 542 | 543 | 544 | def makeplot(x, energy, param_name, param_unit): 545 | '''Make a simple x-y plot''' 546 | # Plot results 547 | fig, ax = plt.subplots() 548 | if all([type(xx) is list for xx in x]) and all([xxx == xx[0] for xx in x for xxx in xx]): 549 | x = [xx[0] for xx in x] 550 | elif all([type(xx) is list for xx in x]): 551 | x = [np.prod(xx) for xx in x] 552 | ax.scatter(x, energy) 553 | ax.plot(x, energy) 554 | ax.set_xlabel(param_name + ' (' + param_unit + ')') 555 | ax.set_ylabel('Total Energy (eV)') 556 | 557 | 558 | def saveplot(outdir, filename): 559 | '''Save plot''' 560 | # Save plotted results 561 | if not os.path.exists(outdir + '/images'): 562 | os.makedirs(outdir + '/images') 563 | plt.savefig(outdir + '/images' + '/' + filename) 564 | plt.close() 565 | 566 | 567 | def prnt_header(string): 568 | print('---------------------------') 569 | print(string) 570 | print('---------------------------') 571 | 572 | 573 | def prnt_subheader(string): 574 | print('. . . . . . . . . . . . . . .') 575 | print(' ' + string) 576 | print('. . . . . . . . . . . . . . .') 577 | 578 | 579 | def is_hpc(hostname, hostnames_json): 580 | with open(libdir() + "/settings/" + hostnames_json, 'r') as j: 581 | hostnames = json.loads(j.read()) 582 | return hostname in hostnames['HPC'] 583 | 584 | 585 | class runtime(): 586 | def __init__(self): 587 | self.start_time = time.time() 588 | self.end_time = None 589 | self.elapsed_time = None 590 | self.print_time(self.start_time, 'Start time') 591 | 592 | def stop(self): 593 | self.end_time = time.time() 594 | self.elapsed_time = self.end_time - self.start_time 595 | self.print_time(self.end_time, 'End time') 596 | self.print_time(self.elapsed_time, 'Elapsed time', "gmtime") 597 | 598 | def print_time(self, t, string, timeformat="localtype"): 599 | if timeformat == "gmtime": 600 | print('{0}: {1} hh:mm:ss'.format( 601 | string, time.strftime("%H:%M:%S", time.gmtime(t)))) 602 | else: 603 | print('{0}: {1} hh:mm:ss'.format( 604 | string, time.strftime("%H:%M:%S", time.localtime(t)))) 605 | 606 | 607 | class Logger(object): 608 | def __init__(self, filename): 609 | self.terminal = sys.stdout 610 | if not os.path.exists(os.path.dirname(filename)): 611 | os.makedirs(os.path.dirname(filename)) 612 | self.log = open(filename, "a") 613 | 614 | def write(self, message): 615 | self.terminal.write(message) 616 | self.log.write(message) 617 | 618 | def flush(self): 619 | # this flush method is needed for python 3 compatibility. 620 | # this handles the flush command by doing nothing. 621 | # you might want to specify some extra behavior here. 622 | pass 623 | 624 | 625 | def make_jobid(uid): 626 | epoch = time.time() 627 | return "%s_%d" % (uid, epoch) 628 | 629 | 630 | def libdir(string=None): 631 | if not string: 632 | return os.path.dirname(__file__) 633 | else: 634 | return os.path.dirname(__file__) + string 635 | -------------------------------------------------------------------------------- /workflow_engine.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import os 4 | import shutil 5 | import numpy as np 6 | from ase.io import read 7 | from ase import Atoms, lattice 8 | import numpy as np 9 | import vasplib 10 | 11 | 12 | def execute_workflow(workflow): 13 | # Update run options 14 | 15 | workflow['run_options']['result_dir'] = workflow['run_options']['job_dir'] + '/' + workflow['run_options']['result_dir'] + \ 16 | '/' + workflow["label"] 17 | workflow['run_options']["job_id"] = vasplib.make_jobid(0) 18 | # Delete previous calculation for "run_from_scratch" = 0 19 | if os.path.isdir(workflow['run_options']['result_dir']) and len(os.listdir(workflow['run_options']['result_dir'])) and workflow["run_options"]["run_from_scratch"]: 20 | shutil.rmtree(workflow['run_options'] 21 | ['result_dir'], ignore_errors=True) 22 | 23 | # Start logging of command line output 24 | sys.stdout = vasplib.Logger( 25 | workflow['run_options']["result_dir"] + "/" + workflow['run_options']["job_id"] + "_log.output") 26 | print( 27 | ' ///// {0} - {1} /////'.format(workflow['name'], workflow['run_options']["job_id"])) 28 | 29 | # Time execution 30 | runtime = vasplib.runtime() 31 | 32 | # Start Workflow 33 | for ii, task in enumerate(workflow['tasks']): 34 | vasplib.prnt_header( 35 | 'Running Task: {0}'.format(task)) 36 | if workflow['tasks'][task]['method'] == 'total_energy_calculation': 37 | # Get lattice parameter from previous task 38 | if not workflow['tasks'][task]['lattice_param']: 39 | op_optim = workflow['tasks'][task_prev]["v_optim"]["operation"] 40 | op_index = workflow['tasks'][task_prev]["v_optim"]["index"] 41 | workflow['tasks'][task]['lattice_param'] = Atoms.fromdict( 42 | workflow['tasks'][task_prev]["operations"][op_optim]["result"]["value"]["atoms"][op_index]).cell.get_bravais_lattice().a 43 | workflow['tasks'][task] = total_energy_calculation( 44 | workflow['tasks'][task], workflow['run_options']) 45 | task_prev = task 46 | 47 | runtime.stop() 48 | return workflow 49 | 50 | 51 | def total_energy_calculation(task, run_options): 52 | # Read atoms 53 | atoms = read(run_options['job_dir'] + '/' + run_options['atoms_dir'] + '/' + 54 | task['atoms']['name'], format=run_options['software']['name']) 55 | # Initialize VASP calculation 56 | default_settings = vasplib.libdir( 57 | '/settings/' + run_options['software']['default_settings']) 58 | calc = vasplib.ini_vasp_calculation( 59 | default_settings, atoms, run_options['software']['settings']) 60 | # Set supercell volume and save atoms 61 | if type(atoms.cell.get_bravais_lattice()) == lattice.HEX: 62 | fac = 1 / np.sqrt(2) 63 | else: 64 | fac = 1 65 | 66 | calc.atoms.set_cell( 67 | calc.get_atoms().cell * task["lattice_param"] * fac, scale_atoms=True) 68 | initial_volume = calc.get_atoms().get_volume() 69 | task['atoms']['structure'] = calc.atoms.todict() 70 | 71 | # Execute Operations 72 | for op in task["operations"]: 73 | with open(vasplib.libdir('/operations/' + op + '.json'), 'r') as openfile: 74 | # Load task 75 | operation = json.load(openfile) 76 | # Attach additional information 77 | operation.update(task["operations"][op]['settings']) 78 | operation.update({"job_id": run_options["job_id"]}) 79 | operation["outdir"] = run_options['result_dir'] + '/' + \ 80 | task['atoms']['name'] + '/' + op 81 | # Execute task 82 | calc, value, unit = getattr( 83 | vasplib, operation["name"])(calc, operation) 84 | # Collect results 85 | task["operations"][op].update( 86 | {"result": {"value": value, "unit": unit}}) 87 | # Write and plot results 88 | vasplib.result2json(operation, task, run_options, value, unit) 89 | if operation["makeplot"]: 90 | vasplib.makeplot( 91 | operation['value'], value["etot"], operation['param'], operation['unit']) 92 | vasplib.saveplot( 93 | operation["outdir"], run_options["job_id"] + "_" + operation['name'] + '_etot.png') 94 | 95 | return task 96 | -------------------------------------------------------------------------------- /workflows/SFE_AIM_PCs_V_fixed.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SFE AIM Primitive Cells", 3 | "label": "SFE_AIM_V_fixed", 4 | "run_options": { 5 | "atoms_dir": "atoms", 6 | "result_dir": "results", 7 | "run_from_scratch": 0, 8 | "job_dir": "", 9 | "software": { 10 | "name": "vasp", 11 | "default_settings": "vasp_high_accuracy", 12 | "settings": { 13 | "ncore": 3, 14 | "kpar": 4, 15 | "nbands": 18 16 | } 17 | } 18 | }, 19 | "tasks": { 20 | "relax_fcc": { 21 | "method": "total_energy_calculation", 22 | "atoms": { 23 | "name": "fcc", 24 | "structure": [] 25 | }, 26 | "lattice_param": 3.573, 27 | "operations": { 28 | "kpoint_optimization": { 29 | "settings": { 30 | "value": [ 31 | 2, 32 | 4, 33 | 6, 34 | 8, 35 | 10, 36 | 12, 37 | 14, 38 | 16, 39 | 18, 40 | 20, 41 | 22, 42 | 24, 43 | 26, 44 | 28, 45 | 30 46 | ] 47 | } 48 | }, 49 | "cell_relaxation_isif2": { 50 | "settings": { 51 | "value": [ 52 | 1 53 | ] 54 | } 55 | } 56 | }, 57 | "v_optim": { 58 | "operation": "cell_relaxation_isif2", 59 | "index": -1 60 | } 61 | }, 62 | "relax_hcp": { 63 | "method": "total_energy_calculation", 64 | "atoms": { 65 | "name": "hcp", 66 | "structure": [] 67 | }, 68 | "lattice_param": [], 69 | "operations": { 70 | "kpoint_optimization": { 71 | "settings": { 72 | "value": [ 73 | 2, 74 | 4, 75 | 6, 76 | 8, 77 | 10, 78 | 12, 79 | 14, 80 | 16, 81 | 18, 82 | 20, 83 | 22, 84 | 24, 85 | 26, 86 | 28, 87 | 30 88 | ] 89 | } 90 | }, 91 | "cell_relaxation_isif2_coa": { 92 | "settings": { 93 | "value": [ 94 | 1.500, 95 | 1.525, 96 | 1.550, 97 | 1.575, 98 | 1.600, 99 | 1.625, 100 | 1.650, 101 | 1.675, 102 | 1.700, 103 | 1.725, 104 | 1.750, 105 | 1.775, 106 | 1.800 107 | ] 108 | } 109 | }, 110 | "fit_eos_isif2_coa": { 111 | "settings": { 112 | "input_dataset": "cell_relaxation_isif2_coa" 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /workflows/SFE_AIM_PCs_Vcoa_fixed.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SFE AIM Primitive Cells", 3 | "label": "SFE_AIM_Vcoa_fixed", 4 | "run_options": { 5 | "atoms_dir": "atoms", 6 | "result_dir": "results", 7 | "run_from_scratch": 0, 8 | "job_dir": "", 9 | "software": { 10 | "name": "vasp", 11 | "default_settings": "vasp_high_accuracy", 12 | "settings": { 13 | "ncore": 3, 14 | "kpar": 4, 15 | "nbands": 18 16 | } 17 | } 18 | }, 19 | "tasks": { 20 | "relax_fcc": { 21 | "method": "total_energy_calculation", 22 | "atoms": { 23 | "name": "fcc", 24 | "structure": [] 25 | }, 26 | "lattice_param": 3.573, 27 | "operations": { 28 | "kpoint_optimization": { 29 | "settings": { 30 | "value": [ 31 | 2, 32 | 4, 33 | 6, 34 | 8, 35 | 10, 36 | 12, 37 | 14, 38 | 16, 39 | 18, 40 | 20, 41 | 22, 42 | 24, 43 | 26, 44 | 28, 45 | 30 46 | ] 47 | } 48 | }, 49 | "cell_relaxation_isif2": { 50 | "settings": { 51 | "value": [ 52 | 1 53 | ] 54 | } 55 | } 56 | }, 57 | "v_optim": { 58 | "operation": "cell_relaxation_isif2", 59 | "index": -1 60 | } 61 | }, 62 | "relax_hcp": { 63 | "method": "total_energy_calculation", 64 | "atoms": { 65 | "name": "hcp", 66 | "structure": [] 67 | }, 68 | "lattice_param": [], 69 | "operations": { 70 | "kpoint_optimization": { 71 | "settings": { 72 | "value": [ 73 | 2, 74 | 4, 75 | 6, 76 | 8, 77 | 10, 78 | 12, 79 | 14, 80 | 16, 81 | 18, 82 | 20, 83 | 22, 84 | 24, 85 | 26, 86 | 28, 87 | 30 88 | ] 89 | } 90 | }, 91 | "cell_relaxation_isif2": { 92 | "settings": { 93 | "value": [ 94 | 1 95 | ] 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /workflows/SFE_AIM_PCs_coa_fixed.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SFE AIM Primitive Cells", 3 | "label": "SFE_AIM_coa_fixed", 4 | "run_options": { 5 | "atoms_dir": "atoms", 6 | "result_dir": "results", 7 | "run_from_scratch": 0, 8 | "job_dir": "", 9 | "software": { 10 | "name": "vasp", 11 | "default_settings": "vasp_high_accuracy", 12 | "settings": { 13 | "ncore": 3, 14 | "kpar": 4, 15 | "nbands": 18 16 | } 17 | } 18 | }, 19 | "tasks": { 20 | "relax_fcc": { 21 | "method": "total_energy_calculation", 22 | "atoms": { 23 | "name": "fcc", 24 | "structure": [] 25 | }, 26 | "lattice_param": 3.573, 27 | "operations": { 28 | "kpoint_optimization": { 29 | "settings": { 30 | "value": [ 31 | 2, 32 | 4, 33 | 6, 34 | 8, 35 | 10, 36 | 12, 37 | 14, 38 | 16, 39 | 18, 40 | 20, 41 | 22, 42 | 24, 43 | 26, 44 | 28, 45 | 30 46 | ] 47 | } 48 | }, 49 | "cell_relaxation_isif2": { 50 | "settings": { 51 | "value": [ 52 | 0.80, 53 | 0.85, 54 | 0.90, 55 | 0.95, 56 | 1.00, 57 | 1.05, 58 | 1.10, 59 | 1.15, 60 | 1.20 61 | ] 62 | } 63 | }, 64 | "fit_eos_isif2": { 65 | "settings": { 66 | "input_dataset": "cell_relaxation_isif2" 67 | } 68 | } 69 | }, 70 | "v_optim": { 71 | "operation": "fit_eos_isif2", 72 | "index": -1 73 | } 74 | }, 75 | "relax_hcp": { 76 | "method": "total_energy_calculation", 77 | "atoms": { 78 | "name": "hcp", 79 | "structure": [] 80 | }, 81 | "lattice_param": [], 82 | "operations": { 83 | "kpoint_optimization": { 84 | "settings": { 85 | "value": [ 86 | 2, 87 | 4, 88 | 6, 89 | 8, 90 | 10, 91 | 12, 92 | 14, 93 | 16, 94 | 18, 95 | 20, 96 | 22, 97 | 24, 98 | 26, 99 | 28, 100 | 30 101 | ] 102 | } 103 | }, 104 | "cell_relaxation_isif2": { 105 | "settings": { 106 | "value": [ 107 | 1 108 | ] 109 | } 110 | } 111 | } 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /workflows/SFE_AIM_PCs_optim.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SFE AIM Primitive Cells", 3 | "label": "SFE_AIM_optim", 4 | "run_options": { 5 | "atoms_dir": "atoms", 6 | "result_dir": "results", 7 | "run_from_scratch": 0, 8 | "job_dir": "", 9 | "software": { 10 | "name": "vasp", 11 | "default_settings": "vasp_high_accuracy", 12 | "settings": { 13 | "ncore": 3, 14 | "kpar": 4, 15 | "nbands": 18 16 | } 17 | } 18 | }, 19 | "tasks": { 20 | "relax_fcc": { 21 | "method": "total_energy_calculation", 22 | "atoms": { 23 | "name": "fcc", 24 | "structure": [] 25 | }, 26 | "lattice_param": 3.573, 27 | "operations": { 28 | "kpoint_optimization": { 29 | "settings": { 30 | "value": [ 31 | 2, 32 | 4, 33 | 6, 34 | 8, 35 | 10, 36 | 12, 37 | 14, 38 | 16, 39 | 18, 40 | 20, 41 | 22, 42 | 24, 43 | 26, 44 | 28, 45 | 30 46 | ] 47 | } 48 | }, 49 | "cell_relaxation_isif2": { 50 | "settings": { 51 | "value": [ 52 | 0.80, 53 | 0.85, 54 | 0.90, 55 | 0.95, 56 | 1.00, 57 | 1.05, 58 | 1.10, 59 | 1.15, 60 | 1.20 61 | ] 62 | } 63 | }, 64 | "fit_eos_isif2": { 65 | "settings": { 66 | "input_dataset": "cell_relaxation_isif2" 67 | } 68 | } 69 | }, 70 | "v_optim": { 71 | "operation": "fit_eos_isif2", 72 | "index": -1 73 | } 74 | }, 75 | "relax_hcp": { 76 | "method": "total_energy_calculation", 77 | "atoms": { 78 | "name": "hcp", 79 | "structure": [] 80 | }, 81 | "lattice_param": [], 82 | "operations": { 83 | "kpoint_optimization": { 84 | "settings": { 85 | "value": [ 86 | 2, 87 | 4, 88 | 6, 89 | 8, 90 | 10, 91 | 12, 92 | 14, 93 | 16, 94 | 18, 95 | 20, 96 | 22, 97 | 24, 98 | 26, 99 | 28, 100 | 30 101 | ] 102 | } 103 | }, 104 | "cell_relaxation_isif2_coa": { 105 | "settings": { 106 | "value": [ 107 | 1.500, 108 | 1.525, 109 | 1.550, 110 | 1.575, 111 | 1.600, 112 | 1.625, 113 | 1.650, 114 | 1.675, 115 | 1.700, 116 | 1.725, 117 | 1.750, 118 | 1.775, 119 | 1.800 120 | ] 121 | } 122 | }, 123 | "fit_eos_isif2_coa": { 124 | "settings": { 125 | "input_dataset": "cell_relaxation_isif2_coa" 126 | } 127 | } 128 | } 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /workflows/SFE_AIM_largeSCs_Vcoa_fixed.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SFE AIM Large Supercells", 3 | "label": "SFE_AIM_Vcoa_fixed", 4 | "run_options": { 5 | "atoms_dir": "atoms", 6 | "result_dir": "results", 7 | "run_from_scratch": 0, 8 | "job_dir": "", 9 | "software": { 10 | "name": "vasp", 11 | "default_settings": "vasp_high_accuracy_largeSC", 12 | "settings": { 13 | "ncore": 4, 14 | "kpar": 10, 15 | "nbands": 442, 16 | "kpts": [ 17 | 6, 18 | 6, 19 | 4 20 | ] 21 | } 22 | } 23 | }, 24 | "tasks": { 25 | "relax_fcc": { 26 | "method": "total_energy_calculation", 27 | "atoms": { 28 | "name": "fcc54_1H", 29 | "structure": [] 30 | }, 31 | "lattice_param": 3.573, 32 | "operations": { 33 | "cell_relaxation_isif2": { 34 | "settings": { 35 | "value": [ 36 | 1 37 | ] 38 | } 39 | } 40 | }, 41 | "v_optim": { 42 | "operation": "cell_relaxation_isif2", 43 | "index": -1 44 | } 45 | }, 46 | "relax_hcp": { 47 | "method": "total_energy_calculation", 48 | "atoms": { 49 | "name": "hcp54_1H", 50 | "structure": [] 51 | }, 52 | "lattice_param": [], 53 | "operations": { 54 | "cell_relaxation_isif2": { 55 | "settings": { 56 | "value": [ 57 | 1 58 | ] 59 | } 60 | } 61 | } 62 | } 63 | } 64 | } --------------------------------------------------------------------------------