Source code for pyiron_atomistics.dft.job.generic

# coding: utf-8
# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
# Distributed under the terms of "New BSD License", see the LICENSE file.

import warnings

import numpy as np

from pyiron_atomistics.atomistics.job.atomistic import (
    AtomisticGenericJob,
)
from pyiron_atomistics.atomistics.job.atomistic import (
    MapFunctions as AtomisticMapFunctions,
)
from pyiron_atomistics.dft.waves.electronic import ElectronicStructure

__author__ = "Jan Janssen"
__copyright__ = (
    "Copyright 2021, Max-Planck-Institut für Eisenforschung GmbH - "
    "Computational Materials Design (CM) Department"
)
__version__ = "1.0"
__maintainer__ = "Jan Janssen"
__email__ = "janssen@mpie.de"
__status__ = "production"
__date__ = "Sep 1, 2017"


[docs] class GenericDFTJob(AtomisticGenericJob):
[docs] def __init__(self, project, job_name): super(GenericDFTJob, self).__init__(project, job_name) self._generic_input["fix_symmetry"] = True self.map_functions = MapFunctions() self._generic_input["k_mesh_spacing"] = None self._generic_input["k_mesh_center_shift"] = None self._generic_input["reduce_kpoint_symmetry"] = True
@property def encut(self): return self.plane_wave_cutoff @encut.setter def encut(self, val): self.plane_wave_cutoff = val @property def kpoint_mesh(self): return self.get_kpoints() @kpoint_mesh.setter def kpoint_mesh(self, val): self.set_kpoints(mesh=val) @property def xc(self): return self.exchange_correlation_functional @xc.setter def xc(self, val): self.exchange_correlation_functional = val @property def plane_wave_cutoff(self): raise NotImplementedError( "The encut property is not implemented for this code." ) @plane_wave_cutoff.setter def plane_wave_cutoff(self, val): raise NotImplementedError( "The encut property is not implemented for this code." ) @property def spin_constraints(self): raise NotImplementedError( "The spin_constraints property is not implemented for this code." ) @spin_constraints.setter def spin_constraints(self, val): raise NotImplementedError( "The spin_constraints property is not implemented for this code." ) @property def exchange_correlation_functional(self): raise NotImplementedError( "The exchange property is not implemented for this code." ) @exchange_correlation_functional.setter def exchange_correlation_functional(self, val): raise NotImplementedError( "The exchange property is not implemented for this code." ) @property def k_mesh_spacing(self): """ Number of unreduced k-points per Angstrom of the lattice vector Returns: float: Number of k-points per Angstrom """ return self._generic_input["k_mesh_spacing"] @k_mesh_spacing.setter def k_mesh_spacing(self, val): self._generic_input["k_mesh_spacing"] = val @property def k_mesh_center_shift(self): """ Number of unreduced k-points per Angstrom of the lattice vector Returns: float: Number of k-points per Angstrom """ return self._generic_input["k_mesh_center_shift"] @k_mesh_center_shift.setter def k_mesh_center_shift(self, val): self._generic_input["k_mesh_center_shift"] = val @property def reduce_kpoint_symmetry(self): """ Number of unreduced k-points per Angstrom of the lattice vector Returns: float: Number of k-points per Angstrom """ return self._generic_input["reduce_kpoint_symmetry"] @reduce_kpoint_symmetry.setter def reduce_kpoint_symmetry(self, boolean): self._generic_input["reduce_kpoint_symmetry"] = boolean @property def fix_spin_constraint(self): return self._generic_input["fix_spin_constraint"] @fix_spin_constraint.setter def fix_spin_constraint(self, boolean): if not isinstance(boolean, bool): raise AssertionError() self._generic_input["fix_spin_constraint"] = boolean @property def fix_symmetry(self): return self._generic_input["fix_symmetry"] @fix_symmetry.setter def fix_symmetry(self, boolean): if not isinstance(boolean, bool): raise AssertionError() self._generic_input["fix_symmetry"] = boolean def _get_structure(self, frame=-1, wrap_atoms=True): snapshot = super(GenericDFTJob, self)._get_structure( frame=frame, wrap_atoms=wrap_atoms ) spins = self.get_magnetic_moments(iteration_step=frame) if spins is not None: snapshot.set_initial_magnetic_moments(spins) return snapshot
[docs] def set_mixing_parameters( self, method=None, n_pulay_steps=None, density_mixing_parameter=None, spin_mixing_parameter=None, density_residual_scaling=None, spin_residual_scaling=None, ): """ Args: method (str): mixing method 'PULAY' or 'KERKER' (default: PULAY) n_pulay_steps (int): number of previous densities to use for the Pulay mixing (default: 7) density_mixing_parameter (float): mixing ratio of rho_opt to rho_in spin_mixing_parameter (float): linear mixing parameter for spin densities density_residual_scaling (float): scaling for the residual contribution of the Kerker mixing. 1 means it takes as much rho_in (input density) as residual (where Kerker preconditioning applies). The lower the value, the smaller the residual contribution becomes. spin_residual_scaling (float): scaling for the spin residual (cf. density_residual_scaling) Mixing ratio m is given by: rho^n = (m-1)*rho_in+m*preconditioner*rho_opt A low value of density mixing parameter may lead to a more stable convergence, but will slow down the calculation if set too low. In systems with bands around the Fermi energy (i.e. metals, e.g. Fe, Mn), the mixing parameters should be small. In insulators (e.g. Al, Si) the mixing parameter should be high. """ raise NotImplementedError( "set_mixing_parameters is not implemented for this code." )
[docs] def restart_for_band_structure_calculations(self, job_name=None): """ Restart a new job created from an existing calculation by reading the charge density for band structure calculations. Args: job_name (str/None): Job name Returns: new_ham (pyiron_atomistics.dft.job.generic.GenericDFTJob): New job """ raise NotImplementedError( "restart_for_band_structure_calculations is not implemented for this code." )
[docs] def get_magnetic_moments(self, iteration_step=-1): """ Gives the magnetic moments of a calculation for each iteration step. Args: iteration_step (int): Step for which the structure is requested Returns: numpy.ndarray/None: array of final magmetic moments or None if no magnetic moment is given """ spins = self.get("output/generic/dft/atom_spins") if spins is not None: return spins[iteration_step] else: return None
def get_kpoints(self): raise NotImplementedError( "The get_kpoints() function is not implemented for this code." ) def get_valence_and_total_charge_density(self): raise NotImplementedError( "The get_valence_and_total_charge_density() function is not implemented for this code." )
[docs] def get_k_mesh_by_cell(self, k_mesh_spacing, cell=None): """ Get k-mesh density according to the box size. Args: k_mesh_spacing (float): K-point spacing in units of 2 * pi reciprocal Angstrom. (smaller values result in a denser mesh for a given structure). cell (numpy.ndarray/list): The cell shape Returns: list/numpy.ndarray: Mesh size """ if cell is None: if self.structure is None: raise ValueError( "Can't generate k-points without structure being set and if cell is not specified" ) cell = self.structure.cell return get_k_mesh_by_density(cell=cell, k_mesh_spacing=k_mesh_spacing)
[docs] def set_kpoints( self, mesh=None, scheme="MP", center_shift=None, symmetry_reduction=True, manual_kpoints=None, weights=None, reciprocal=True, k_mesh_spacing=None, n_path=None, path_name=None, ): """ Function to setup the k-points Args: mesh (list/numpy.ndarray): Size of the mesh (ignored if scheme is not set to 'MP' or kpoints_per_reciprocal_ angstrom is set) scheme (str): Type of k-point generation scheme (MP/GP(gamma point)/Manual/Line) center_shift (list/numpy.ndarray/None): Shifts the center of the mesh from the gamma point by the given vector in relative coordinates symmetry_reduction (boolean): Tells if the symmetry reduction is to be applied to the k-points manual_kpoints (list/numpy.ndarray): Manual list of k-points weights(list/numpy.ndarray): Manually supplied weights to each k-point in case of the manual mode reciprocal (bool): Tells if the supplied values are in reciprocal (direct) or cartesian coordinates (in reciprocal space) k_mesh_spacing (float): K-point spacing in units of 2 * pi reciprocal Angstrom. (smaller values result in a denser mesh for a given structure). n_path (int): Number of points per trace part for line mode path_name (str): Name of high symmetry path used for band structure calculations. """ if k_mesh_spacing is not None: if mesh is not None: warnings.warn("mesh value is overwritten by k_mesh_spacing") mesh = self.get_k_mesh_by_cell(k_mesh_spacing=k_mesh_spacing) self.k_mesh_spacing = k_mesh_spacing self.k_mesh_center_shift = center_shift self.reduce_kpoint_symmetry = symmetry_reduction if mesh is not None: if np.min(mesh) <= 0: raise ValueError("mesh values must be larger than 0") if center_shift is not None: if np.min(center_shift) < 0 or np.max(center_shift) > 1: warnings.warn("center_shift is given in relative coordinates") self._set_kpoints( mesh=mesh, scheme=scheme, center_shift=center_shift, symmetry_reduction=symmetry_reduction, manual_kpoints=manual_kpoints, weights=weights, reciprocal=reciprocal, n_path=n_path, path_name=path_name, )
[docs] def calc_static( self, electronic_steps=60, ): super().calc_static()
[docs] def calc_minimize( self, electronic_steps=60, ionic_steps=100, max_iter=None, pressure=None, ionic_energy_tolerance=None, ionic_force_tolerance=None, ionic_energy=None, ionic_forces=None, volume_only=False, ): super().calc_minimize(max_iter=max_iter, pressure=pressure)
def calc_md( self, temperature=None, n_ionic_steps=1000, n_print=1, time_step=1.0, **kwargs, ): self._generic_input["fix_symmetry"] = False super().calc_md( temperature=temperature, n_ionic_steps=n_ionic_steps, n_print=n_print, time_step=time_step, )
[docs] def nbands_convergence_check(self): """ Function to check if there are a sufficient number of empty bands in the calculation to ensure electronic convergence. Returns: bool : True if the highest band is unoccupied, False if the highest band is occupied """ return np.all( np.isclose(self["output/electronic_structure/occ_matrix"][:, :, -1], 0) ) # shape is n_spin x n_kpoints x n_bands
# Backward compatibility def get_encut(self): return self.encut
[docs] def set_encut(self, encut): """ Sets the plane-wave energy cutoff Args: encut (float): The energy cutoff in eV """ self.plane_wave_cutoff = encut
def set_exchange_correlation_functional(self, exchange_correlation_functional): self.exchange_correlation_functional = exchange_correlation_functional def set_empty_states(self, n_empty_states=None): raise NotImplementedError( "The set_empty_states function is not implemented for this code." )
[docs] def get_bader_charges(self): """ Returns the total charge on every atom determined by the Bader charge partitioning scheme. Returns: numpy.ndarray: The Bader charges for each atom """ return self["output/generic/dft/bader_charges"]
[docs] def get_bader_volumes(self): """ Returns the integration volume around every atom from the Bader charge partitioning scheme. Returns: numpy.ndarray: The Bader charges for each atom """ return self["output/generic/dft/bader_volumes"]
def _set_kpoints( self, mesh=None, scheme="MP", center_shift=None, symmetry_reduction=True, manual_kpoints=None, weights=None, reciprocal=True, n_path=None, path_name=None, ): raise NotImplementedError( "The set_kpoints function is not implemented for this code." )
[docs] def get_electronic_structure(self): """ Gets the electronic structure instance from the hdf5 file Returns: pyiron_atomistics.atomistics.waves.electronic.ElectronicStructure instance """ if self.status not in ["finished", "warning", "not_converged"]: return else: with self.project_hdf5.open("output") as ho: es_obj = ElectronicStructure() es_obj.from_hdf(ho) return es_obj
def modify_kpoints(self): if self.k_mesh_spacing is not None: self.set_kpoints( center_shift=self.k_mesh_center_shift, k_mesh_spacing=self.k_mesh_spacing, symmetry_reduction=self.reduce_kpoint_symmetry, )
[docs] def save(self): self.modify_kpoints() super(GenericDFTJob, self).save()
[docs] def get_density_of_states(self, sigma=0.1, shift_by_fermi_energy=True, grid=None): """ Get density of states from a fully converged result. A Gaussian smeared histogram is returned Args: sigma (float): Gaussian smearing parameter in energy unit. shift_by_fermi_energy (bool): Shift the histogram by the Fermi energy value. Setting this to False will return code specific absolute values which have physically no meaning. grid (list/numpy.ndarray): Energy grid. If None, the interval between maximum and minimum eigenvalues plus 5 x sigma with a step length of sigma will be taken. Returns: (dict): grid and density of states (n_spin x energy_grid) """ if sigma <= 0: raise ValueError("Sigma must be a positive float") k_weights = self["output/electronic_structure/k_weights"] if k_weights is None: raise ValueError("k-point weighting not found") e_fermi = 0 if shift_by_fermi_energy: e_fermi = self["output/electronic_structure/efermi"] eigen_values = self["output/electronic_structure/eig_matrix"] eigen_values = eigen_values - e_fermi if grid is None: grid = np.arange( eigen_values.min() - 5 * sigma, eigen_values.max() + 5 * sigma, sigma ) hist = ( eigen_values[:, :, :, np.newaxis] - grid[np.newaxis, np.newaxis, np.newaxis, :] ) hist = ( np.exp(-((hist) ** 2) / (2 * sigma**2)) * k_weights[np.newaxis, :, np.newaxis, np.newaxis] ) hist = np.sum(hist, axis=(1, 2)) hist *= 2 / len(eigen_values) / np.sqrt(2 * np.pi * sigma**2) return {"grid": grid, "dos": hist}
[docs] def get_k_mesh_by_density(cell, k_mesh_spacing=0.5): """ Get k-mesh density according to the box size. Args: cell (numpy.ndarray/list): The cell shape k_mesh_spacing (float): K-point spacing in units of 2 * pi reciprocal Angstrom. (smaller values result in a denser mesh for a given structure). Returns: list/numpy.ndarray: Mesh size """ omega = np.linalg.det(cell) l1, l2, l3 = cell g1 = 2 * np.pi / omega * np.cross(l2, l3) g2 = 2 * np.pi / omega * np.cross(l3, l1) g3 = 2 * np.pi / omega * np.cross(l1, l2) kmesh = np.rint( np.array([np.linalg.norm(g) for g in [g1, g2, g3]]) / k_mesh_spacing ) kmesh[kmesh < 1] = 1 return [int(k) for k in kmesh]
[docs] def set_encut(job, parameter): job.set_encut(parameter) return job
[docs] def set_kpoints(job, parameter): job.set_kpoints(parameter) return job
[docs] class MapFunctions(AtomisticMapFunctions):
[docs] def __init__(self): super().__init__() self.set_encut = set_encut self.set_kpoints = set_kpoints