# coding: utf-8
# Copyright (c) Pymatgen Development Team.
# Distributed under the terms of the MIT License.
"""
This module defines the FeffInputSet abstract base class and a concrete
implementation for the Materials Project. The basic concept behind an input
set is to specify a scheme to generate a consistent set of Feff inputs from a
structure without further user intervention. This ensures comparability across
runs.
"""
import sys
import os
import abc
from copy import deepcopy
import logging
from monty.serialization import loadfn
from monty.json import MSONable
from monty.os.path import zpath
from pymatgen.io.feff.inputs import Atoms, Tags, Potential, Header
import numpy as np
__author__ = "Kiran Mathew"
__credits__ = "Alan Dozier, Anubhav Jain, Shyue Ping Ong"
__version__ = "1.1"
__maintainer__ = "Kiran Mathew"
__email__ = "kmathew@lbl.gov"
__date__ = "Sept 10, 2016"
MODULE_DIR = os.path.dirname(os.path.abspath(__file__))
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s: %(levelname)s: %(name)s: %(message)s')
sh = logging.StreamHandler(stream=sys.stdout)
sh.setFormatter(formatter)
logger.addHandler(sh)
[docs]class FEFFDictSet(AbstractFeffInputSet):
"""
Standard implementation of FeffInputSet, which can be extended by specific
implementations.
"""
def __init__(self, absorbing_atom, structure, radius, config_dict,
edge="K", spectrum="EXAFS", nkpts=1000,
user_tag_settings=None):
"""
Args:
absorbing_atom (str/int): absorbing atom symbol or site index
structure (Structure): input structure
radius (float): cluster radius
config_dict (dict): control tag settings dict
edge (str): absorption edge
spectrum (str): type of spectrum to calculate, available options :
EXAFS, XANES, DANES, XMCD, ELNES, EXELFS, FPRIME, NRIXS, XES.
The default is EXAFS.
nkpts (int): Total number of kpoints in the brillouin zone. Used
only when feff is run in the reciprocal space mode.
user_tag_settings (dict): override default tag settings. To delete
tags, set the key '_del' in the user_tag_settings.
eg: user_tag_settings={"_del": ["COREHOLE", "EXCHANGE"]}
"""
self.absorbing_atom = absorbing_atom
self.structure = structure
self.radius = radius
self.config_dict = deepcopy(config_dict)
self.edge = edge
self.spectrum = spectrum
self.nkpts = nkpts
self.user_tag_settings = user_tag_settings or {}
self.config_dict["EDGE"] = self.edge
self.config_dict.update(self.user_tag_settings)
if "_del" in self.user_tag_settings:
for tag in self.user_tag_settings["_del"]:
if tag in self.config_dict:
del self.config_dict[tag]
del self.config_dict["_del"]
# k-space feff only for small systems. The hardcoded system size in
# feff is around 14 atoms.
self.small_system = True if (len(self.structure) < 14 and 'EXAFS' not in self.config_dict) else False
@property
def tags(self):
"""
FEFF job parameters.
Returns:
Tags
"""
if "RECIPROCAL" in self.config_dict:
if self.small_system:
self.config_dict["CIF"] = "{}.cif".format(
self.structure.formula.replace(" ", ""))
self.config_dict["TARGET"] = self.atoms.center_index + 1
self.config_dict["COREHOLE"] = "RPA"
logger.warning("Setting COREHOLE = RPA for K-space calculation")
if not self.config_dict.get("KMESH", None):
abc = self.structure.lattice.abc
mult = (self.nkpts * abc[0] * abc[1] * abc[2]) ** (1 / 3)
self.config_dict["KMESH"] = [int(round(mult / l)) for l in abc]
else:
logger.warning("Large system(>=14 atoms) or EXAFS calculation, \
removing K-space settings")
del self.config_dict["RECIPROCAL"]
self.config_dict.pop("CIF", None)
self.config_dict.pop("TARGET", None)
self.config_dict.pop("KMESH", None)
self.config_dict.pop("STRFAC", None)
return Tags(self.config_dict)
@property
def potential(self):
"""
FEFF potential
Returns:
Potential
"""
return Potential(self.structure, self.absorbing_atom)
@property
def atoms(self):
"""
absorber + the rest
Returns:
Atoms
"""
return Atoms(self.structure, self.absorbing_atom, self.radius)
def __str__(self):
output = [self.spectrum]
output.extend(["%s = %s" % (k, str(v))
for k, v in self.config_dict.items()])
output.append("")
return "\n".join(output)
[docs] @staticmethod
def from_directory(input_dir):
"""
Read in a set of FEFF input files from a directory, which is
useful when existing FEFF input needs some adjustment.
"""
sub_d = {}
for fname, ftype in [("HEADER", Header), ("PARAMETERS", Tags)]:
fullzpath = zpath(os.path.join(input_dir, fname))
sub_d[fname.lower()] = ftype.from_file(fullzpath)
# Generation of FEFFDict set requires absorbing atom, need to search
# the index of absorption atom in the structure according to the
# distance matrix and shell species information contained in feff.inp
absorber_index = []
radius = None
feffinp = zpath(os.path.join(input_dir, 'feff.inp'))
if "RECIPROCAL" not in sub_d["parameters"]:
input_atoms = Atoms.cluster_from_file(feffinp)
shell_species = np.array([x.species_string for x in input_atoms])
# First row of distance matrix represents the distance from the absorber to
# the rest atoms
distance_matrix = input_atoms.distance_matrix[0, :]
# Get radius value
from math import ceil
radius = int(ceil(input_atoms.get_distance(input_atoms.index(input_atoms[0]),
input_atoms.index(input_atoms[-1]))))
for site_index, site in enumerate(sub_d['header'].struct):
if site.specie == input_atoms[0].specie:
site_atoms = Atoms(sub_d['header'].struct, absorbing_atom=site_index,
radius=radius)
site_distance = np.array(site_atoms.get_lines())[:, 5].astype(np.float64)
site_shell_species = np.array(site_atoms.get_lines())[:, 4]
shell_overlap = min(shell_species.shape[0], site_shell_species.shape[0])
if np.allclose(distance_matrix[:shell_overlap], site_distance[:shell_overlap]) and \
np.all(site_shell_species[:shell_overlap] == shell_species[:shell_overlap]):
absorber_index.append(site_index)
if "RECIPROCAL" in sub_d["parameters"]:
absorber_index = sub_d["parameters"]["TARGET"]
absorber_index[0] = int(absorber_index[0]) - 1
# Generate the input set
if 'XANES' in sub_d["parameters"]:
CONFIG = loadfn(os.path.join(MODULE_DIR, "MPXANESSet.yaml"))
if radius is None:
radius = 10
return FEFFDictSet(absorber_index[0], sub_d['header'].struct, radius=radius,
config_dict=CONFIG, edge=sub_d["parameters"]["EDGE"],
nkpts=1000, user_tag_settings=sub_d["parameters"])
[docs]class MPXANESSet(FEFFDictSet):
"""
FeffDictSet for XANES spectroscopy.
"""
CONFIG = loadfn(os.path.join(MODULE_DIR, "MPXANESSet.yaml"))
def __init__(self, absorbing_atom, structure, edge="K", radius=10.,
nkpts=1000, user_tag_settings=None):
"""
Args:
absorbing_atom (str/int): absorbing atom symbol or site index
structure (Structure): input
edge (str): absorption edge
radius (float): cluster radius in Angstroms.
nkpts (int): Total number of kpoints in the brillouin zone. Used
only when feff is run in the reciprocal space mode.
user_tag_settings (dict): override default tag settings
"""
super().__init__(absorbing_atom, structure, radius,
MPXANESSet.CONFIG, edge=edge,
spectrum="XANES", nkpts=nkpts,
user_tag_settings=user_tag_settings)
[docs]class MPEXAFSSet(FEFFDictSet):
"""
FeffDictSet for EXAFS spectroscopy.
"""
CONFIG = loadfn(os.path.join(MODULE_DIR, "MPEXAFSSet.yaml"))
def __init__(self, absorbing_atom, structure, edge="K", radius=10.,
nkpts=1000, user_tag_settings=None):
"""
Args:
absorbing_atom (str/int): absorbing atom symbol or site index
structure (Structure): input structure
edge (str): absorption edge
radius (float): cluster radius in Angstroms.
nkpts (int): Total number of kpoints in the brillouin zone. Used
only when feff is run in the reciprocal space mode.
user_tag_settings (dict): override default tag settings
"""
super().__init__(absorbing_atom, structure, radius,
MPEXAFSSet.CONFIG, edge=edge,
spectrum="EXAFS", nkpts=nkpts,
user_tag_settings=user_tag_settings)
[docs]class MPEELSDictSet(FEFFDictSet):
"""
FeffDictSet for ELNES spectroscopy.
"""
def __init__(self, absorbing_atom, structure, edge, spectrum, radius,
beam_energy, beam_direction, collection_angle,
convergence_angle, config_dict, user_eels_settings=None,
nkpts=1000, user_tag_settings=None):
"""
Args:
absorbing_atom (str/int): absorbing atom symbol or site index
structure (Structure): input structure
edge (str): absorption edge
spectrum (str): ELNES or EXELFS
radius (float): cluster radius in Angstroms.
beam_energy (float): Incident beam energy in keV
beam_direction (list): Incident beam direction. If None, the
cross section will be averaged.
collection_angle (float): Detector collection angle in mrad.
convergence_angle (float): Beam convergence angle in mrad.
user_eels_settings (dict): override default EELS config.
See MPELNESSet.yaml for supported keys.
nkpts (int): Total number of kpoints in the brillouin zone. Used
only when feff is run in the reciprocal space mode.
user_tag_settings (dict): override default tag settings
"""
self.beam_energy = beam_energy
self.beam_direction = beam_direction
self.collection_angle = collection_angle
self.convergence_angle = convergence_angle
self.user_eels_settings = user_eels_settings
eels_config_dict = deepcopy(config_dict)
if beam_direction:
beam_energy_list = [beam_energy, 0, 1, 1]
eels_config_dict[spectrum]["BEAM_DIRECTION"] = beam_direction
else:
beam_energy_list = [beam_energy, 1, 0, 1]
del eels_config_dict[spectrum]["BEAM_DIRECTION"]
eels_config_dict[spectrum]["BEAM_ENERGY"] = beam_energy_list
eels_config_dict[spectrum]["ANGLES"] = [collection_angle,
convergence_angle]
if user_eels_settings:
eels_config_dict[spectrum].update(user_eels_settings)
super().__init__(absorbing_atom, structure, radius,
eels_config_dict, edge=edge,
spectrum=spectrum, nkpts=nkpts,
user_tag_settings=user_tag_settings)
[docs]class MPELNESSet(MPEELSDictSet):
"""
FeffDictSet for ELNES spectroscopy.
"""
CONFIG = loadfn(os.path.join(MODULE_DIR, "MPELNESSet.yaml"))
def __init__(self, absorbing_atom, structure, edge="K", radius=10.,
beam_energy=100, beam_direction=None, collection_angle=1,
convergence_angle=1, user_eels_settings=None, nkpts=1000,
user_tag_settings=None):
"""
Args:
absorbing_atom (str/int): absorbing atom symbol or site index
structure (Structure): input structure
edge (str): absorption edge
radius (float): cluster radius in Angstroms.
beam_energy (float): Incident beam energy in keV
beam_direction (list): Incident beam direction. If None, the
cross section will be averaged.
collection_angle (float): Detector collection angle in mrad.
convergence_angle (float): Beam convergence angle in mrad.
user_eels_settings (dict): override default EELS config.
See MPELNESSet.yaml for supported keys.
nkpts (int): Total number of kpoints in the brillouin zone. Used
only when feff is run in the reciprocal space mode.
user_tag_settings (dict): override default tag settings
"""
super().__init__(absorbing_atom, structure, edge,
"ELNES", radius, beam_energy,
beam_direction, collection_angle,
convergence_angle, MPELNESSet.CONFIG,
user_eels_settings=user_eels_settings,
nkpts=nkpts, user_tag_settings=user_tag_settings)
[docs]class MPEXELFSSet(MPEELSDictSet):
"""
FeffDictSet for EXELFS spectroscopy.
"""
CONFIG = loadfn(os.path.join(MODULE_DIR, "MPEXELFSSet.yaml"))
def __init__(self, absorbing_atom, structure, edge="K", radius=10.,
beam_energy=100, beam_direction=None, collection_angle=1,
convergence_angle=1, user_eels_settings=None, nkpts=1000,
user_tag_settings=None):
"""
Args:
absorbing_atom (str/int): absorbing atom symbol or site index
structure (Structure): input structure
edge (str): absorption edge
radius (float): cluster radius in Angstroms.
beam_energy (float): Incident beam energy in keV
beam_direction (list): Incident beam direction. If None, the
cross section will be averaged.
collection_angle (float): Detector collection angle in mrad.
convergence_angle (float): Beam convergence angle in mrad.
user_eels_settings (dict): override default EELS config.
See MPEXELFSSet.yaml for supported keys.
nkpts (int): Total number of kpoints in the brillouin zone. Used
only when feff is run in the reciprocal space mode.
user_tag_settings (dict): override default tag settings
"""
super().__init__(absorbing_atom, structure, edge,
"EXELFS", radius, beam_energy,
beam_direction, collection_angle,
convergence_angle, MPEXELFSSet.CONFIG,
user_eels_settings=user_eels_settings,
nkpts=nkpts, user_tag_settings=user_tag_settings)