class PPModel(AbivarAble, PMGSONable): """ Parameters defining the plasmon-pole technique. The common way to instanciate a PPModel object is via the class method PPModel.as_ppmodel(string) """ _mode2ppmodel = { "noppmodel": 0, "godby" : 1, "hybersten": 2, "linden" : 3, "farid" : 4, } modes = Enum(k for k in _mode2ppmodel) @classmethod def as_ppmodel(cls, obj): """ Constructs an instance of PPModel from obj. Accepts obj in the form: * PPmodel instance * string. e.g "godby:12.3 eV", "linden". """ if isinstance(obj, cls): return obj # obj is a string if ":" not in obj: mode, plasmon_freq = obj, None else: # Extract mode and plasmon_freq mode, plasmon_freq = obj.split(":") try: plasmon_freq = float(plasmon_freq) except ValueError: plasmon_freq, unit = plasmon_freq.split() plasmon_freq = units.Energy(float(plasmon_freq), unit).to("Ha") return cls(mode=mode, plasmon_freq=plasmon_freq) def __init__(self, mode="godby", plasmon_freq=None): assert mode in PPModel.modes self.mode = mode self.plasmon_freq = plasmon_freq def __eq__(self, other): if other is None: return False else: if self.mode != other.mode: return False if self.plasmon_freq is None: return other.plasmon_freq is None else: return np.allclose(self.plasmon_freq, other.plasmon_freq) def __ne__(self, other): return not self == other def __bool__(self): return self.mode != "noppmodel" # py2 old version __nonzero__ = __bool__ def __repr__(self): return "<%s at %s, mode = %s>" % (self.__class__.__name__, id(self), str(self.mode)) def to_abivars(self): if self: return {"ppmodel": self._mode2ppmodel[self.mode], "ppmfrq": self.plasmon_freq} else: return {} @classmethod def noppmodel(cls): return cls(mode="noppmodel", plasmon_freq=None) def as_dict(self): return {"mode": self.mode, "plasmon_freq": self.plasmon_freq, "@module": self.__class__.__module__, "@class": self.__class__.__name__} @staticmethod def from_dict(d): return PPModel(mode=d["mode"], plasmon_freq=d["plasmon_freq"])
class KSampling(AbivarAble): """ Input variables defining the K-point sampling. """ # Modes supported by the constructor. modes = Enum(('monkhorst', 'path', 'automatic',)) def __init__(self, mode="monkhorst", num_kpts= 0, kpts=((1, 1, 1),), kpt_shifts=(0.5, 0.5, 0.5), kpts_weights=None, use_symmetries=True, use_time_reversal=True, chksymbreak=None, comment=None): """ Highly flexible constructor for KSampling objects. The flexibility comes at the cost of usability and in general, it is recommended that you use the default constructor only if you know exactly what you are doing and requires the flexibility. For most usage cases, the object be constructed far more easily using the convenience static constructors: #. gamma_only #. gamma_centered #. monkhorst #. monkhorst_automatic #. path and it is recommended that you use those. Args: mode: Mode for generating k-poits. Use one of the KSampling.modes enum types. num_kpts: Number of kpoints if mode is "automatic" Number of division for the sampling of the smallest segment if mode is "path". Not used for the other modes kpts: Number of divisions. Even when only a single specification is required, e.g. in the automatic scheme, the kpts should still be specified as a 2D array. e.g., [[20]] or [[2,2,2]]. kpt_shifts: Shifts for Kpoints. use_symmetries: False if spatial symmetries should not be used to reduce the number of independent k-points. use_time_reversal: False if time-reversal symmetry should not be used to reduce the number of independent k-points. kpts_weights: Optional weights for kpoints. For explicit kpoints. chksymbreak: Abinit input variable: check whether the BZ sampling preserves the symmetry of the crystal. comment: String comment for Kpoints .. note:: The default behavior of the constructor is monkhorst. """ if mode not in KSampling.modes: raise ValueError("Unknown kpoint mode %s" % mode) super(KSampling, self).__init__() self.mode = mode self.comment = comment abivars = {} if mode in ("monkhorst",): assert num_kpts == 0 ngkpt = np.reshape(kpts, (-1,3)) shiftk = np.reshape(kpt_shifts, (-1,3)) if use_symmetries and use_time_reversal: kptopt = 1 if not use_symmetries and use_time_reversal: kptopt = 2 if not use_symmetries and not use_time_reversal: kptopt = 3 if use_symmetries and not use_time_reversal: kptopt = 4 abivars.update({ "ngkpt" : ngkpt, "shiftk" : shiftk, "nshiftk" : len(shiftk), "kptopt" : kptopt, "chksymbreak": chksymbreak, }) elif mode in ("path",): if num_kpts <= 0: raise ValueError("For Path mode, num_kpts must be specified and >0") kptbounds = np.reshape(kpts, (-1,3,)) #print("in path with kptbound: %s " % kptbounds) abivars.update({ "ndivsm" : num_kpts, "kptbounds": kptbounds, "kptopt" : -len(kptbounds)+1, }) elif mode in ("automatic",): kpts = np.reshape(kpts, (-1,3)) if len(kpts) != num_kpts: raise ValueError("For Automatic mode, num_kpts must be specified.") kptnrm = np.ones(num_kpts) abivars.update({ "kptopt" : 0, "kpt" : kpts, "nkpt" : num_kpts, "kptnrm" : kptnrm, "wtk" : kpts_weights, # for iscf/=-2, wtk. "chksymbreak": chksymbreak, }) else: raise ValueError("Unknown mode %s" % mode) self.abivars = abivars self.abivars["#comment"] = comment @property def is_homogeneous(self): return self.mode not in ["path"] @classmethod def gamma_only(cls): """Gamma-only sampling""" return cls(kpt_shifts=(0.0,0.0,0.0), comment="Gamma-only sampling") @classmethod def gamma_centered(cls, kpts=(1, 1, 1), use_symmetries=True, use_time_reversal=True): """ Convenient static constructor for an automatic Gamma centered Kpoint grid. Args: kpts: Subdivisions N_1, N_2 and N_3 along reciprocal lattice vectors. use_symmetries: False if spatial symmetries should not be used to reduce the number of independent k-points. use_time_reversal: False if time-reversal symmetry should not be used to reduce the number of independent k-points. Returns: :class:`KSampling` object. """ return cls(kpts=[kpts], kpt_shifts=(0.0, 0.0, 0.0), use_symmetries=use_symmetries, use_time_reversal=use_time_reversal, comment="gamma-centered mode") @classmethod def monkhorst(cls, ngkpt, shiftk=(0.5, 0.5, 0.5), chksymbreak=None, use_symmetries=True, use_time_reversal=True, comment=None): """ Convenient static constructor for a Monkhorst-Pack mesh. Args: ngkpt: Subdivisions N_1, N_2 and N_3 along reciprocal lattice vectors. shiftk: Shift to be applied to the kpoints. use_symmetries: Use spatial symmetries to reduce the number of k-points. use_time_reversal: Use time-reversal symmetry to reduce the number of k-points. Returns: :class:`KSampling` object. """ return cls( kpts=[ngkpt], kpt_shifts=shiftk, use_symmetries=use_symmetries, use_time_reversal=use_time_reversal, chksymbreak=chksymbreak, comment=comment if comment else "Monkhorst-Pack scheme with user-specified shiftk") @classmethod def monkhorst_automatic(cls, structure, ngkpt, use_symmetries=True, use_time_reversal=True, chksymbreak=None, comment=None): """ Convenient static constructor for an automatic Monkhorst-Pack mesh. Args: structure: paymatgen structure object. ngkpt: Subdivisions N_1, N_2 and N_3 along reciprocal lattice vectors. use_symmetries: Use spatial symmetries to reduce the number of k-points. use_time_reversal: Use time-reversal symmetry to reduce the number of k-points. Returns: :class:`KSampling` object. """ sg = SpacegroupAnalyzer(structure) #sg.get_crystal_system() #sg.get_point_group() # TODO nshiftk = 1 #shiftk = 3*(0.5,) # this is the default shiftk = 3*(0.5,) #if lattice.ishexagonal: #elif lattice.isbcc #elif lattice.isfcc return cls.monkhorst( ngkpt, shiftk=shiftk, use_symmetries=use_symmetries, use_time_reversal=use_time_reversal, chksymbreak=chksymbreak, comment=comment if comment else "Automatic Monkhorst-Pack scheme") @classmethod def _path(cls, ndivsm, structure=None, kpath_bounds=None, comment=None): """ Static constructor for path in k-space. Args: structure: pymatgen structure. kpath_bounds: List with the reduced coordinates of the k-points defining the path. ndivsm: Number of division for the smallest segment. comment: Comment string. Returns: :class:`KSampling` object. """ if kpath_bounds is None: # Compute the boundaries from the input structure. from pymatgen.symmetry.bandstructure import HighSymmKpath sp = HighSymmKpath(structure) # Flat the array since "path" is a a list of lists! kpath_labels = [] for labels in sp.kpath["path"]: kpath_labels.extend(labels) kpath_bounds = [] for label in kpath_labels: red_coord = sp.kpath["kpoints"][label] #print("label %s, red_coord %s" % (label, red_coord)) kpath_bounds.append(red_coord) return cls(mode=KSampling.modes.path, num_kpts=ndivsm, kpts=kpath_bounds, comment=comment if comment else "K-Path scheme") @classmethod def path_from_structure(cls, ndivsm, structure): """See _path for the meaning of the variables""" return cls._path(ndivsm, structure=structure, comment="K-path generated automatically from pymatgen structure") @classmethod def explicit_path(cls, ndivsm, kpath_bounds): """See _path for the meaning of the variables""" return cls._path(ndivsm, kpath_bounds=kpath_bounds, comment="Explicit K-path") @classmethod def automatic_density(cls, structure, kppa, chksymbreak=None, use_symmetries=True, use_time_reversal=True, shifts=(0.5, 0.5, 0.5)): """ Returns an automatic Kpoint object based on a structure and a kpoint density. Uses Gamma centered meshes for hexagonal cells and Monkhorst-Pack grids otherwise. Algorithm: Uses a simple approach scaling the number of divisions along each reciprocal lattice vector proportional to its length. Args: structure: Input structure kppa: Grid density """ lattice = structure.lattice lengths = lattice.abc ngrid = kppa / structure.num_sites mult = (ngrid * lengths[0] * lengths[1] * lengths[2]) ** (1 / 3.) num_div = [int(round(1.0 / lengths[i] * mult)) for i in range(3)] # ensure that num_div[i] > 0 num_div = [i if i > 0 else 1 for i in num_div] angles = lattice.angles hex_angle_tol = 5 # in degrees hex_length_tol = 0.01 # in angstroms right_angles = [i for i in range(3) if abs(angles[i] - 90) < hex_angle_tol] hex_angles = [i for i in range(3) if abs(angles[i] - 60) < hex_angle_tol or abs(angles[i] - 120) < hex_angle_tol] is_hexagonal = (len(right_angles) == 2 and len(hex_angles) == 1 and abs(lengths[right_angles[0]] - lengths[right_angles[1]]) < hex_length_tol) #style = Kpoints.modes.gamma #if not is_hexagonal: # num_div = [i + i % 2 for i in num_div] # style = Kpoints.modes.monkhorst comment = "pymatgen generated KPOINTS with grid density = " + "{} / atom".format(kppa) shifts = np.reshape(shifts, (-1, 3)) return cls( mode="monkhorst", num_kpts=0, kpts=[num_div], kpt_shifts=shifts, use_symmetries=use_symmetries, use_time_reversal=use_time_reversal, chksymbreak=chksymbreak, comment=comment) def to_abivars(self): return self.abivars