Source code for ase2sprkkr.asr.setup.defects

"""Generate defective atomic structures."""
from typing import List
from pathlib import Path
from asr.core import command, option, ASRResult
import click


@command('asr.setup.defects',
         creates=['general_parameters.json'])
@option('-a', '--atomfile', type=str,
        help='Atomic structure.')
@option('-q', '--chargestates', type=int,
        help='Charge states included (-q, ..., +q).')
@option('--supercell', nargs=3, type=click.Tuple([int, int, int]),
        help='List of repetitions in lat. vector directions [N_x, N_y, N_z]')
@option('--maxsize', type=float,
        help='Maximum supercell size in Å.')
@option('--intrinsic', type=bool,
        help='Specify whether you want to incorporate anti-site defects.')
@option('--vacancies', type=bool,
        help='Specify whether you want to incorporate vacancies.')
def main(atomfile: str = 'unrelaxed.json', chargestates: int = 3,
         supercell: List[int] = [0, 0, 0],
         maxsize: float = 8, intrinsic: bool = True,
         vacancies: bool = True) -> ASRResult:
    """Set up defect structures for a given host.

    Recipe setting up all possible defects within a reasonable supercell as
    well as the respective pristine system for a given input structure.
    Defects include: vacancies, anti-site defects. For a given primitive input
    structure this recipe will create a directory tree in the following way:

    For the example of MoS2:

    - There has to be a 'unrelaxed.json' file with the primitive structure
      of the desired system in the folder you run setup.defects. The tree
      structure will then look like this

    .                                                                        '
    ├── general_parameters.json                                              '
    ├── MoS2_231.v_at_b0                                                     '
    │   ├── charge_0                                                         '
    │   │   ├── params.json                                                  '
    │   │   └── unrelaxed.json                                               '
    │   ├── charge_1                                                         '
    │   │   ├── ...                                                          '
    │   │   .                                                                '
    │   .                                                                    '
    │                                                                        '
    ├── MoS2_231.Mo_at_i1                                                    '
    │   ├── charge_0                                                         '
    .   .                                                                    '
    .                                                                        '
    ├── pristine                                                             '
    │   └── neutral                                                          '
    │       ├── params.json                                                  '
    │       └── unrelaxed.json                                               '
    ├── pristine_sc                                                          '
    │   └── neutral                                                          '
    │       ├── params.json                                                  '
    │       └── unrelaxed.json                                               '
    ├── results_setup.defects.json                                           '
    └── unrelaxed.json                                                       '

    - Here, the notation for the defects is the following:
      'formula_supercellsize.defect_at_substitutionposition' where 'v'
      denotes a vacancy
    - In the resulting folders you can find the unrelaxed structures, as
      well as a 'params.json' file which contains the charge states of the
      different defect structures.

    """
    from ase.io import read
    import numpy as np

    # first, read input atomic structure and store it in ase's atoms object
    structure = read(atomfile)
    print('INFO: starting recipe for setting up defect systems of '
          '{} host system.'.format(structure.symbols))

    # check dimensionality of initial parent structure
    nd = int(np.sum(structure.get_pbc()))
    if nd == 3:
        is2d = False
    elif nd == 2:
        is2d = True
    elif nd == 1:
        raise NotImplementedError('Setup defects not implemented for 1D '
                                  f'structures')
    # set up the different defect systems and store their properties
    # in a dictionary
    structure_dict = setup_defects(structure=structure, intrinsic=intrinsic,
                                   charge_states=chargestates,
                                   vacancies=vacancies, sc=supercell,
                                   max_lattice=maxsize, is_2D=is2d)

    # based on this dictionary, create a folder structure for all defects
    # and respective charge states
    create_folder_structure(structure, structure_dict, chargestates,
                            intrinsic=intrinsic, vacancies=vacancies,
                            sc=supercell, max_lattice=maxsize, is_2D=is2d)

    return None


[docs] def setup_supercell(structure, max_lattice, is_2D): """Set up the supercell of a given structure depending. Set up the supercell of a given structure depending on a maximum supercell lattice vector length for 2D or 3D structures. Parameters ---------- structure input structure (primitive cell) max_lattice : float maximum supercell lattice vector length in Å is_2D : bool choose 2D or 3D supercell (is_2D=False) Returns ------- structure_sc supercell structure """ for x in range(1, 50): struc_temp = structure.repeat((x, 1, 1)) diff = struc_temp.get_distance(0, -1) if diff > max_lattice: x_size = x - 1 break for y in range(1, 50): struc_temp = structure.repeat((1, y, 1)) diff = struc_temp.get_distance(0, -1) if diff > max_lattice: y_size = y - 1 break if not is_2D: for z in range(1, 50): struc_temp = structure.repeat((1, 1, z)) diff = struc_temp.get_distance(0, -1) if diff > max_lattice: z_size = z - 1 break else: z_size = 1 print('INFO: setting up supercell: ({0}, {1}, {2})'.format( x_size, y_size, z_size)) x_size = max(1, x_size) y_size = max(1, y_size) z_size = max(1, z_size) structure_sc = structure.repeat((x_size, y_size, z_size)) return structure_sc, x_size, y_size, z_size
[docs] def setup_defects(structure, intrinsic, charge_states, vacancies, sc, max_lattice, is_2D): """Set up all possible defects. Sets up all possible defects (i.e. vacancies, intrinsic anti-sites, extrinsic point defects('extrinsic=True')) for a given structure. Parameters ---------- structure input structure (primitive cell) intrinsic : bool incorporate intrinsic point defects vacancies : bool incorporate vacancies Returns ------- structure_dict : dict dictionary of all possible defect configurations of the given structure with different charge states. The dictionary is built up in the following way: see folder structure in 'main()'. """ import spglib # set up artificial array in order to check for equivalent positions later cell = (structure.cell.array, structure.get_scaled_positions(), structure.numbers) # set up a dictionary structure_dict = {} formula = structure.symbols # first, find the desired supercell if sc == (0, 0, 0): pristine, N_x, N_y, N_z = setup_supercell( structure, max_lattice, is_2D) else: N_x = sc[0] N_y = sc[1] N_z = sc[2] print('INFO: setting up supercell: ({0}, {1}, {2})'.format( N_x, N_y, N_z)) pristine = structure.repeat((N_x, N_y, N_z)) parameters = {} string = 'defects.pristine_sc' parameters['asr.relax'] = {} parameters['asr.gs@calculate'] = {} structure_dict[string] = {'structure': pristine, 'parameters': parameters} # incorporate the possible vacancies dataset = spglib.get_symmetry_dataset(cell) eq_pos = dataset.get('equivalent_atoms') wyckoffs = dataset.get('wyckoffs') finished_list = [] if vacancies: temp_dict = {} for i in range(len(structure)): if not eq_pos[i] in finished_list: vacancy = pristine.copy() vacancy.pop(i) string = 'defects.{0}_{1}{2}{3}.v_{4}{5}'.format( formula, N_x, N_y, N_z, wyckoffs[i], i) charge_dict = {} for q in range((-1) * charge_states, charge_states + 1): parameters = {} parameters['asr.relax'] = {'chargestate': q} parameters['asr.gs@calculate'] = {'chargestate': q} charge_string = 'charge_{}'.format(q) charge_dict[charge_string] = {'structure': vacancy, 'parameters': parameters} temp_dict[string] = charge_dict finished_list.append(eq_pos[i]) # incorporate anti-site defects finished_list = [] if intrinsic: defect_list = [] for i in range(len(structure)): symbol = structure[i].symbol if symbol not in defect_list: defect_list.append(symbol) for i in range(len(structure)): if not eq_pos[i] in finished_list: for element in defect_list: if not structure[i].symbol == element: defect = pristine.copy() defect[i].symbol = element string = 'defects.{0}_{1}{2}{3}.{4}_at_{5}{6}'.format( formula, N_x, N_y, N_z, element, wyckoffs[i], i) charge_dict = {} for q in range( (-1) * charge_states, charge_states + 1): parameters = {} parameters['asr.relax'] = {'chargestate': q} parameters['asr.gs@calculate'] = {'chargestate': q} charge_string = 'charge_{}'.format(q) charge_dict[charge_string] = { 'structure': defect, 'parameters': parameters} temp_dict[string] = charge_dict finished_list.append(eq_pos[i]) # put together structure dict structure_dict['defects'] = temp_dict print('INFO: setting up {0} different defect supercell systems in ' 'charge states -{1}, ..., +{1}, as well as the pristine supercell ' 'system.'.format(len(structure_dict['defects']), charge_states)) return structure_dict
[docs] def create_folder_structure(structure, structure_dict, chargestates, intrinsic, vacancies, sc, max_lattice, is_2D): """Create folder for all configurations. Creates a folder for every configuration of the defect supercell in the following way: - see example directory tree in 'main()' - these each contain two files: 'unrelaxed.json' (the defect supercell structure), 'params.json' (the non-general parameters of each system) - the content of those folders can then be used to do further processing (e.g. relax the defect structure) """ from ase.io import write from asr.core import write_json # create a json file for general parameters that are equivalent for all # the different defect systems if sc == [0, 0, 0]: pristine, N_x, N_y, N_z = setup_supercell( structure, max_lattice, is_2D) else: N_x = sc[0] N_y = sc[1] N_z = sc[2] gen_params = {} gen_params['chargestates'] = chargestates gen_params['is_2D'] = is_2D gen_params['supercell'] = [N_x, N_y, N_z] gen_params['intrinsic'] = intrinsic gen_params['vacancies'] = vacancies write_json('general_parameters.json', gen_params) # then, create a seperate folder for each possible defect # configuration of this parent folder, as well as the pristine # supercell system for element in structure_dict: folder_name = element try: if not folder_name == 'defects': Path(folder_name).mkdir() except FileExistsError: print('WARNING: folder ("{0}") already exists in this ' f'directory. Skip creating it.'.format(folder_name)) if structure_dict[element].get('structure') is not None: struc = structure_dict[element].get('structure') params = structure_dict[element].get('parameters') try: write(folder_name + '/unrelaxed.json', struc) write_json(folder_name + '/params.json', params) except FileExistsError: print('WARNING: files already exist inside this folder.') else: sub_dict = structure_dict[element] j = 0 for sub_element in sub_dict: defect_name = [key for key in sub_dict.keys()] defect_folder_name = defect_name[j] j = j + 1 try: Path(defect_folder_name).mkdir() except FileExistsError: print( 'WARNING: folder ("{0}") already exists in this ' f'directory. Skip creating ' f'it.'.format(defect_folder_name)) for i in range((-1) * chargestates, chargestates + 1): charge_name = 'charge_{}'.format(i) charge_folder_name = defect_folder_name + '/' + charge_name try: Path(charge_folder_name).mkdir() except FileExistsError: print( 'WARNING: folder ("{0}") already exists in this ' f'directory. Skip creating ' f'it.'.format(charge_folder_name)) struc = sub_dict[sub_element].get( charge_name).get('structure') params = sub_dict[sub_element].get( charge_name).get('parameters') write(charge_folder_name + '/unrelaxed.json', struc) write_json(charge_folder_name + '/params.json', params) return None
[docs] def collect_data(): return None
# def webpanel(result, row, key_descriptions): # from asr.browser import fig, table # # if 'something' not in row.data: # return None, [] # # table1 = table(row, # 'Property', # ['something'], # kd=key_descriptions) # panel = ('Title', # [[fig('something.png'), table1]]) # things = [(create_plot, ['something.png'])] # return panel, things # def create_plot(row, fname): # import matplotlib.pyplot as plt # # data = row.data.something # fig = plt.figure() # ax = fig.gca() # ax.plot(data.things) # plt.savefig(fname) if __name__ == '__main__': main.cli()