Source code for pytopomat.workflows.core

"""
Wflows using irvsp, vasp2trace, and Z2Pack.

"""

import numpy as np
import itertools
import os

from uuid import uuid4

from pymatgen.io.vasp.inputs import Kpoints
from pymatgen.io.vasp.sets import MPStaticSet, MPRelaxSet
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer

from atomate.vasp.config import STABILITY_CHECK, VASP_CMD, DB_FILE, ADD_WF_METADATA
from atomate.vasp.powerups import (
    add_stability_check,
    add_modify_incar,
    add_wf_metadata,
    add_common_powerups,
    add_additional_fields_to_taskdocs,
    add_tags,
)
from atomate.vasp.workflows.base.core import get_wf
from atomate.vasp.fireworks.core import OptimizeFW, StaticFW

from fireworks import Workflow

from pytopomat.analyzer import StructureDimensionality
from pytopomat.workflows.fireworks import Z2PackFW, InvariantFW


__author__ = "Jason Munro, Nathan C. Frey"
__copyright__ = "MIT License"
__version__ = "0.0.1"
__maintainer__ = "Jason Munro, Nathan C. Frey"
__email__ = "jmunro@lbl.gov, ncfrey@lbl.gov"
__status__ = "Development"
__date__ = "August 2019"


[docs]def wf_irrep(structure, magnetic=False, soc=False, c=None): """ Fireworks workflow for running an irrep calculation. Args: structure (Structure): Pymatgen structure object magnetic (bool): Whether the calculation is on a magnetic structure soc (bool): Spin-orbit coupling included Returns: Workflow """ c = c or {} vasp_cmd = c.get("VASP_CMD", VASP_CMD) db_file = c.get("DB_FILE", DB_FILE) uuid = str(uuid4()) wf_meta = {"wf_uuid": uuid, "wf_name": "Irrep WF"} magmoms = None if magnetic and "magmom" in structure.site_properties: magmoms_orig = structure.site_properties["magmom"] magmoms = [str(m) for m in magmoms_orig] magmoms = " ".join(magmoms) elif magnetic: raise RuntimeError( "Structure must have magnetic moments in site_properties for magnetic calcualtion!" ) ncoords = 3 * len(structure.sites) nbands = 0 for site in structure.sites: nbands += site.species.total_electrons trim_kpoints = Kpoints( comment="TRIM Points", num_kpts=8, style=Kpoints.supported_modes.Reciprocal, kpts=( (0, 0, 0), (0.5, 0, 0), (0, 0.5, 0), (0, 0, 0.5), (0.5, 0.5, 0), (0, 0.5, 0.5), (0.5, 0, 0.5), (0.5, 0.5, 0.5), ), kpts_shift=(0, 0, 0), kpts_weights=[1, 1, 1, 1, 1, 1, 1, 1], coord_type="Reciprocal", labels=["gamma", "x", "y", "z", "s", "t", "u", "r"], tet_number=0, tet_weight=0, tet_connections=None, ) # params dicts for wf params = [ {}, # optimization {}, # standardization {}, # static { "input_set_overrides": { "other_params": {"user_kpoints_settings": trim_kpoints} } }, # nscf {"wf_uuid": uuid}, # irrep ] yaml_spec = "irrep.yaml" wf = get_wf( structure, yaml_spec, params=params, vis=MPStaticSet(structure, user_potcar_functional="PBE_54", force_gamma=True), common_params={"vasp_cmd": vasp_cmd, "db_file": db_file}, wf_metadata=wf_meta, ) dim_data = StructureDimensionality(structure) if np.any( [ dim == 2 for dim in [dim_data.larsen_dim, dim_data.cheon_dim, dim_data.gorai_dim] ] ): wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"IVDW": 11, "EDIFFG": 0.005, "IBRION": 2, "NSW": 100} }, fw_name_constraint="structure optimization", ) else: wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"EDIFFG": 0.005, "IBRION": 2, "NSW": 100} }, fw_name_constraint="structure optimization", ) wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"ADDGRID": ".TRUE.", "LASPH": ".TRUE.", "GGA": "PS"} }, ) # Includ ncl magmoms with saxis = (0, 0, 1) if magnetic and soc: magmoms = [] for m in magmoms_orig: magmoms += [0.0, 0.0, m] magmoms = [str(m) for m in magmoms] magmoms = " ".join(magmoms) wf = add_modify_incar( wf, modify_incar_params={"incar_update": {"ISYM": 2, "MAGMOM": "%s" % magmoms}}, ) if magnetic and not soc: # Include magmoms in every calculation wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"ISYM": 2, "MAGMOM": "%s" % magmoms, "ISPIN": 2} }, ) wf = add_modify_incar( wf, modify_incar_params={ "incar_update": { "ISYM": 2, "LSORBIT": ".TRUE." if soc else ".FALSE.", "MAGMOM": "%s" % magmoms if magnetic else "%i*0.0" % ncoords, "ISPIN": 2 if magnetic and not soc else 1, "LWAVE": ".TRUE.", # "NBANDS": nbands, } }, fw_name_constraint="nscf", ) wf = add_common_powerups(wf, c) wf = add_additional_fields_to_taskdocs( wf, {"wf_meta": wf_meta}, task_name_constraint="VaspToDb" ) if c.get("STABILITY_CHECK", STABILITY_CHECK): wf = add_stability_check(wf, fw_name_constraint="structure optimization") if c.get("ADD_WF_METADATA", ADD_WF_METADATA): wf = add_wf_metadata(wf, structure) return wf
[docs]def wf_irvsp(structure, magnetic=False, soc=False, v2t=False, c=None): """ Fireworks workflow for running an irvsp calculation. Optionally performs a vasp2trace calculation. Args: structure (Structure): Pymatgen structure object magnetic (bool): Whether the calculation is on a magnetic structure soc (bool): Spin-orbit coupling included v2t (bool): Do a vasp2trace calculation in addition to irvsp. Returns: Workflow """ c = c or {} vasp_cmd = c.get("VASP_CMD", VASP_CMD) db_file = c.get("DB_FILE", DB_FILE) uuid = str(uuid4()) wf_meta = {"wf_uuid": uuid, "wf_name": "Irvsp WF"} magmoms = None if magnetic and "magmom" in structure.site_properties: magmoms_orig = structure.site_properties["magmom"] magmoms = [str(m) for m in magmoms_orig] magmoms = " ".join(magmoms) elif magnetic: raise RuntimeError( "Structure must have magnetic moments in site_properties for magnetic calcualtion!" ) ncoords = 3 * len(structure.sites) nbands = 0 for site in structure.sites: nbands += site.species.total_electrons trim_kpoints = Kpoints( comment="TRIM Points", num_kpts=8, style=Kpoints.supported_modes.Reciprocal, kpts=( (0, 0, 0), (0.5, 0, 0), (0, 0.5, 0), (0, 0, 0.5), (0.5, 0.5, 0), (0, 0.5, 0.5), (0.5, 0, 0.5), (0.5, 0.5, 0.5), ), kpts_shift=(0, 0, 0), kpts_weights=[1, 1, 1, 1, 1, 1, 1, 1], coord_type="Reciprocal", labels=["gamma", "x", "y", "z", "s", "t", "u", "r"], tet_number=0, tet_weight=0, tet_connections=None, ) # params dicts for wf params = [ {}, # optimization {}, # standardization {}, # static { "input_set_overrides": { "other_params": {"user_kpoints_settings": trim_kpoints} } }, # nscf {"wf_uuid": uuid}, # irvsp ] if magnetic and v2t: yaml_spec = "irvsp_v2t_magnetic.yaml" params.append({}) elif v2t: yaml_spec = "irvsp_v2t.yaml" params.append({}) else: yaml_spec = "irvsp.yaml" wf = get_wf( structure, yaml_spec, params=params, vis=MPStaticSet(structure, user_potcar_functional="PBE_54", force_gamma=True), common_params={"vasp_cmd": vasp_cmd, "db_file": db_file}, wf_metadata=wf_meta, ) dim_data = StructureDimensionality(structure) if np.any( [ dim == 2 for dim in [dim_data.larsen_dim, dim_data.cheon_dim, dim_data.gorai_dim] ] ): wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"IVDW": 11, "EDIFFG": 0.005, "IBRION": 2, "NSW": 100} }, fw_name_constraint="structure optimization", ) else: wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"EDIFFG": 0.005, "IBRION": 2, "NSW": 100} }, fw_name_constraint="structure optimization", ) wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"ADDGRID": ".TRUE.", "LASPH": ".TRUE.", "GGA": "PS"} }, ) # Includ ncl magmoms with saxis = (0, 0, 1) if magnetic and soc: magmoms = [] for m in magmoms_orig: magmoms += [0.0, 0.0, m] magmoms = [str(m) for m in magmoms] magmoms = " ".join(magmoms) wf = add_modify_incar( wf, modify_incar_params={"incar_update": {"ISYM": 2, "MAGMOM": "%s" % magmoms}}, ) if magnetic and not soc: # Include magmoms in every calculation wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"ISYM": 2, "MAGMOM": "%s" % magmoms, "ISPIN": 2} }, ) wf = add_modify_incar( wf, modify_incar_params={ "incar_update": { "ISYM": 2, "LSORBIT": ".TRUE." if soc else ".FALSE.", "MAGMOM": "%s" % magmoms if magnetic else "%i*0.0" % ncoords, "ISPIN": 2 if magnetic and not soc else 1, "LWAVE": ".TRUE.", # "NBANDS": nbands, } }, fw_name_constraint="nscf", ) wf = add_common_powerups(wf, c) wf = add_additional_fields_to_taskdocs( wf, {"wf_meta": wf_meta}, task_name_constraint="VaspToDb" ) if c.get("STABILITY_CHECK", STABILITY_CHECK): wf = add_stability_check(wf, fw_name_constraint="structure optimization") if c.get("ADD_WF_METADATA", ADD_WF_METADATA): wf = add_wf_metadata(wf, structure) return wf
[docs]def wf_vasp2trace_nonmagnetic(structure, c=None): """ Fireworks workflow for running a vasp2trace calculation on a nonmagnetic material. Args: structure (Structure): Pymatgen structure object Returns: Workflow """ c = c or {} vasp_cmd = c.get("VASP_CMD", VASP_CMD) db_file = c.get("DB_FILE", DB_FILE) ncoords = 3 * len(structure.sites) nbands = 0 for site in structure.sites: nbands += site.species.total_electrons trim_kpoints = Kpoints( comment="TRIM Points", num_kpts=8, style=Kpoints.supported_modes.Reciprocal, kpts=( (0, 0, 0), (0.5, 0, 0), (0, 0.5, 0), (0, 0, 0.5), (0.5, 0.5, 0), (0, 0.5, 0.5), (0.5, 0, 0.5), (0.5, 0.5, 0.5), ), kpts_shift=(0, 0, 0), kpts_weights=[1, 1, 1, 1, 1, 1, 1, 1], coord_type="Reciprocal", labels=["gamma", "x", "y", "z", "s", "t", "u", "r"], tet_number=0, tet_weight=0, tet_connections=None, ) wf = get_wf( structure, "vasp2trace_nonmagnetic.yaml", params=[ {}, {}, { "input_set_overrides": { "other_params": {"user_kpoints_settings": trim_kpoints} } }, {}, ], vis=MPStaticSet(structure, user_potcar_functional="PBE_54", force_gamma=True), common_params={"vasp_cmd": vasp_cmd, "db_file": db_file}, ) dim_data = StructureDimensionality(structure) if np.any( [ dim == 2 for dim in [dim_data.larsen_dim, dim_data.cheon_dim, dim_data.gorai_dim] ] ): wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"IVDW": 11, "EDIFFG": 0.005, "IBRION": 2, "NSW": 100} }, fw_name_constraint="structure optimization", ) else: wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"EDIFFG": 0.005, "IBRION": 2, "NSW": 100} }, fw_name_constraint="structure optimization", ) wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"ADDGRID": ".TRUE.", "LASPH": ".TRUE.", "GGA": "PS"} }, ) wf = add_modify_incar( wf, modify_incar_params={ "incar_update": { "ISYM": 2, "LSORBIT": ".TRUE.", "MAGMOM": "%i*0.0" % ncoords, "ISPIN": 1, "LWAVE": ".TRUE.", "NBANDS": nbands, } }, fw_name_constraint="nscf", ) wf = add_common_powerups(wf, c) if c.get("STABILITY_CHECK", STABILITY_CHECK): wf = add_stability_check(wf, fw_name_constraint="structure optimization") if c.get("ADD_WF_METADATA", ADD_WF_METADATA): wf = add_wf_metadata(wf, structure) return wf
[docs]def wf_vasp2trace_magnetic(structure, c=None): """ Fireworks workflow for running a vasp2trace calculation on a magnetic material. Args: structure (Structure): Pymatgen structure object with magmom site property. Returns: Workflow """ c = c or {} vasp_cmd = c.get("VASP_CMD", VASP_CMD) db_file = c.get("DB_FILE", DB_FILE) if "magmom" in structure.site_properties: magmoms = structure.site_properties["magmom"] magmoms = [str(m) for m in magmoms] magmoms = " ".join(magmoms) nbands = 0 for site in structure.sites: nbands += site.species.total_electrons trim_kpoints = Kpoints( comment="TRIM Points", num_kpts=8, style=Kpoints.supported_modes.Reciprocal, kpts=( (0, 0, 0), (0.5, 0, 0), (0, 0.5, 0), (0, 0, 0.5), (0.5, 0.5, 0), (0, 0.5, 0.5), (0.5, 0, 0.5), (0.5, 0.5, 0.5), ), kpts_shift=(0, 0, 0), kpts_weights=[1, 1, 1, 1, 1, 1, 1, 1], coord_type="Reciprocal", labels=["gamma", "x", "y", "z", "s", "t", "u", "r"], tet_number=0, tet_weight=0, tet_connections=None, ) wf_path = os.path.join(os.path.dirname(__file__), "vasp2trace_magnetic.yaml") wf = get_wf( structure, wf_path, params=[ {}, {}, { "input_set_overrides": { "other_params": {"user_kpoints_settings": trim_kpoints} } }, {}, ], vis=MPStaticSet(structure, user_potcar_functional="PBE_54", force_gamma=True), common_params={"vasp_cmd": vasp_cmd, "db_file": db_file}, ) dim_data = StructureDimensionality(structure) if np.any( [ dim == 2 for dim in [dim_data.larsen_dim, dim_data.cheon_dim, dim_data.gorai_dim] ] ): wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"IVDW": 11, "EDIFFG": 0.005, "IBRION": 2, "NSW": 100} }, fw_name_constraint="structure optimization", ) else: wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"EDIFFG": 0.005, "IBRION": 2, "NSW": 100} }, fw_name_constraint="structure optimization", ) wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"ADDGRID": ".TRUE.", "LASPH": ".TRUE.", "GGA": "PS"} }, ) # Include magmoms in every calculation wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"ISYM": 2, "MAGMOM": "%s" % magmoms, "ISPIN": 2} }, ) # Spin-polarized band structure wf = add_modify_incar( wf, modify_incar_params={ "incar_update": { "ISYM": 2, "LSORBIT": ".FALSE.", "MAGMOM": "%s" % magmoms, "ISPIN": 2, "LWAVE": ".TRUE.", "NBANDS": nbands, } }, fw_name_constraint="nscf", ) wf = add_common_powerups(wf, c) if c.get("STABILITY_CHECK", STABILITY_CHECK): wf = add_stability_check(wf, fw_name_constraint="structure optimization") if c.get("ADD_WF_METADATA", ADD_WF_METADATA): wf = add_wf_metadata(wf, structure) return wf
class Z2PackWF: def __init__( self, structure, symmetry_reduction=True, vasp_cmd=VASP_CMD, db_file=DB_FILE ): """ Fireworks workflow for running Z2Pack to compute Z2 invariants and Chern numbers. ***VASP_CMD in my_fworker.yaml MUST be set to "vasp_ncl" for Z2Pack. Args: structure (Structure): Pymatgen structure object symmetry_reduction (bool): Set to False to disable symmetry reduction and include all 6 BZ surfaces (for magnetic systems). """ self.structure = structure self.symmetry_reduction = symmetry_reduction self.uuid = str(uuid4()) self.wf_meta = {"wf_uuid": self.uuid, "wf_name": "Z2Pack WF"} @staticmethod def _get_reciprocal_point_group_nonmagnetic(struct): """ Obtain the symmetry ops. in the reciprocal point group of an input structure. Returns: recip_point_group (list): List of symmetry operations as numpy arrays in the fractional reciprocal space basis. """ R = -1 * np.eye(3) sga = SpacegroupAnalyzer(struct) ops = sga.get_symmetry_operations() isomorphic_point_group = [op.rotation_matrix for op in ops] V = struct.lattice.matrix.T # fractional real space to cartesian real space # fractional reciprocal space to cartesian reciprocal space W = struct.lattice.reciprocal_lattice.matrix.T # fractional real space to fractional reciprocal space A = np.dot(np.linalg.inv(W), V) Ainv = np.linalg.inv(A) # convert to reciprocal primitive basis recip_point_group = [np.around(np.dot(A, np.dot(R, Ainv)), decimals=2)] for op in isomorphic_point_group: op = np.around(np.dot(A, np.dot(op, Ainv)), decimals=2) new = True new_coset = True for thing in recip_point_group: if (thing == op).all(): new = False if (thing == np.dot(R, op)).all(): new_coset = False if new: recip_point_group.append(op) if new_coset: recip_point_group.append(np.dot(R, op)) return recip_point_group @staticmethod def _is_permutation_eq(A, B): """ Check for equivalency between two arrays including permutations. Returns: Whether the two arrays are equivalent (True) or not (False). """ count = {} for a in A: count[str(a)] = 1 for b in B: if str(b) in count: if count[str(b)] == 0: return False else: count[str(b)] = count[str(b)] - 1 else: return False return True def get_equiv_planes(self): """ Get equivalent TRIM planes in the BZ using the reciprocal point symmetry. Returns: plane_equiv (dict): Dictionary providing equivalent TRIM plane names. """ struct = self.structure rpg_ops = Z2PackWF._get_reciprocal_point_group_nonmagnetic(struct) trim_pts = list(itertools.product((0.0, 0.5), repeat=3)) planes = {} plane_equiv = {} symbols = ["x", "y", "z"] for coord in range(3): for plane_num in range(2): planes["k%s_%s" % (symbols[coord], str(plane_num))] = [ np.array(pt) for pt in trim_pts if pt[coord] == float(plane_num) / 2.0 ] plane_equiv["k%s_%s" % (symbols[coord], str(plane_num))] = [] for plane in planes.keys(): for op in rpg_ops: trans_pts = [np.dot(op, pt) % 1.0 for pt in planes[plane]] for other_plane in planes.keys(): if other_plane != plane: check_eq = Z2PackWF._is_permutation_eq( planes[other_plane], trans_pts ) if check_eq and other_plane not in plane_equiv[plane]: plane_equiv[plane].append(other_plane) return plane_equiv def get_wf(self, c=None): """ Get the workflow. Returns: Workflow """ c = c or {"VASP_CMD": VASP_CMD, "DB_FILE": DB_FILE} vis = MPRelaxSet( self.structure, user_potcar_functional="PBE_54", force_gamma=True ) opt_fw = OptimizeFW( self.structure, vasp_input_set=vis, vasp_cmd=c["VASP_CMD"], db_file=c["DB_FILE"], ) vis = MPStaticSet( self.structure, user_potcar_functional="PBE_54", force_gamma=True ) static_fw = StaticFW( self.structure, vasp_input_set=vis, vasp_cmd=c["VASP_CMD"], db_file=c["DB_FILE"], parents=[opt_fw], ) # Separate FW for each BZ surface calc # Run Z2Pack on unique TRIM planes in the BZ surfaces = ["kx_0", "kx_1"] equiv_planes = self.get_equiv_planes() # Only run calcs on inequivalent BZ surfaces if self.symmetry_reduction: for add_surface in equiv_planes.keys(): mark = True for surface in surfaces: if surface in equiv_planes[add_surface]: mark = False if mark and add_surface not in surfaces: surfaces.append(add_surface) else: # 4 TRI surfaces define Z2 in 3D surfaces = ["kx_1", "ky_1", "kz_0", "kz_1"] z2pack_fws = [] for surface in surfaces: z2pack_fw = Z2PackFW( parents=[static_fw], structure=self.structure, surface=surface, uuid=self.uuid, name="z2pack", vasp_cmd=c["VASP_CMD"], db_file=c["DB_FILE"], ) z2pack_fws.append(z2pack_fw) analysis_fw = InvariantFW( parents=z2pack_fws, structure=self.structure, symmetry_reduction=self.symmetry_reduction, equiv_planes=equiv_planes, uuid=self.uuid, name="invariant", db_file=c["DB_FILE"], ) fws = [opt_fw, static_fw] + z2pack_fws + [analysis_fw] wf = Workflow(fws) wf = add_additional_fields_to_taskdocs(wf, {"wf_meta": self.wf_meta}) # Add vdW corrections if structure is layered dim_data = StructureDimensionality(self.structure) if np.any( [ dim == 2 for dim in [dim_data.larsen_dim, dim_data.cheon_dim, dim_data.gorai_dim] ] ): wf = add_modify_incar( wf, modify_incar_params={ "incar_update": { "IVDW": 11, "EDIFFG": 0.005, "IBRION": 2, "NSW": 100, } }, fw_name_constraint="structure optimization", ) wf = add_modify_incar( wf, modify_incar_params={"incar_update": {"IVDW": 11}}, fw_name_constraint="static", ) wf = add_modify_incar( wf, modify_incar_params={"incar_update": {"IVDW": 11}}, fw_name_constraint="z2pack", ) else: wf = add_modify_incar( wf, modify_incar_params={ "incar_update": {"EDIFFG": 0.005, "IBRION": 2, "NSW": 100} }, fw_name_constraint="structure optimization", ) # Helpful vasp settings and no parallelization wf = add_modify_incar( wf, modify_incar_params={ "incar_update": { "ADDGRID": ".TRUE.", "LASPH": ".TRUE.", "GGA": "PS", "NCORE": 1, } }, ) # Generate inputs for Z2Pack with a static calc wf = add_modify_incar( wf, modify_incar_params={"incar_update": {"PREC": "Accurate"}}, fw_name_constraint="static", ) wf = add_common_powerups(wf, c) wf.name = "{} {}".format(self.structure.composition.reduced_formula, "Z2Pack") if c.get("STABILITY_CHECK", STABILITY_CHECK): wf = add_stability_check(wf, fw_name_constraint="structure optimization") if c.get("ADD_WF_METADATA", ADD_WF_METADATA): wf = add_wf_metadata(wf, self.structure) tag = "z2pack: {}".format(self.uuid) wf = add_tags(wf, [tag]) return wf