Skip to content

Commit

Permalink
Fix fdmnes formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewcarbone committed May 14, 2024
1 parent e870f13 commit 787609e
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 68 deletions.
79 changes: 47 additions & 32 deletions lightshow/_tests/test_fdmnes.py
Original file line number Diff line number Diff line change
@@ -1,64 +1,79 @@
import re
from pathlib import Path
import pytest

from pymatgen.core.structure import Structure
from xml.etree import ElementTree

from lightshow.parameters.fdmnes import FDMNESParameters


def test_default_parameters(mp_Structure_mp390: Structure, tmp_path: Path):
## Test on TiO2 O-K default input
# Test on TiO2 O-K default input

fdmnes_parameters = FDMNESParameters()
assert fdmnes_parameters.name == "FDMNES"

write_results = fdmnes_parameters.write(
tmp_path, structure=mp_Structure_mp390, Z_absorber=8
)

input_file = tmp_path / "O_in.txt"
assert write_results == {"pass": True, "errors": {}, "path": str(input_file)}
assert write_results == {
"pass": True,
"errors": {},
"path": str(input_file),
}
assert input_file.exists(), "Input file should exist after write operation"
with open(input_file, 'r') as file:

with open(input_file, "r") as file:
contents = file.read()
assert "Spinorbit" in contents, "Spinorbit should be turned on for O-K edge"
assert "Full_atom" in contents, "Full atom should be turned on for oxides"
assert (
"Spinorbit" in contents
), "Spinorbit should be turned on for O-K edge"
assert (
"Full_atom" in contents
), "Full atom should be turned on for oxides"
assert "Crystal" in contents, "Crystal should be written in the file"


def test_customized_parameters(mp_Structure_mp390: Structure, tmp_path: Path):
## Test on customized input on TiO2 Ti-L
# Test on customized input on TiO2 Ti-L

FDMNES_DEFAULT_CARDS = {
"Energpho": True,
"Memory_save": True,
"Quadrupole": False,
"Relativism": False,
"Spinorbit": None,
"SCF": True,
"SCFexc": False,
"Screening": False,
"Full_atom": False,
"TDDFT": False,
"PBE96": False
"Energpho": True,
"Memory_save": True,
"Quadrupole": False,
"Relativism": False,
"Spinorbit": None,
"SCF": True,
"SCFexc": False,
"Screening": False,
"Full_atom": False,
"TDDFT": False,
"PBE96": False,
}

cards = FDMNES_DEFAULT_CARDS
cards["PBE96"] = True
fdmnes_parameters = FDMNESParameters(cards=cards, edge='L')

fdmnes_parameters = FDMNESParameters(cards=cards, edge="L")
write_results = fdmnes_parameters.write(
tmp_path, structure=mp_Structure_mp390, Z_absorber=22
)

input_file = tmp_path / "Ti_in.txt"
assert write_results == {"pass": True, "errors": {}, "path": str(input_file)}
assert write_results == {
"pass": True,
"errors": {},
"path": str(input_file),
}
assert input_file.exists(), "Input file should exist after write operation"

assert fdmnes_parameters._edge == "L23", "L edge should be adjust automatically"

with open(input_file, 'r') as file:
assert (
fdmnes_parameters._edge == "L23"
), "L edge should be adjust automatically"

with open(input_file, "r") as file:
contents = file.read()
assert "TDDFT" in contents, "TDDFT should be turned on for Ti-L edges"
assert "Relativism" not in contents, "Relativism should be turned off for this system"
assert (
"Relativism" not in contents
), "Relativism should be turned off for this system"
95 changes: 59 additions & 36 deletions lightshow/parameters/fdmnes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from copy import copy
from pathlib import Path
from warnings import warn

Expand All @@ -7,7 +6,6 @@

from lightshow.parameters._base import _BaseParameters


FDMNES_DEFAULT_CARDS = {
"Energpho": True,
"Memory_save": True,
Expand All @@ -19,7 +17,7 @@
"Screening": False,
"Full_atom": False,
"TDDFT": False,
"PBE96": False
"PBE96": False,
}


Expand Down Expand Up @@ -49,23 +47,23 @@ class FDMNESParameters(MSONable, _BaseParameters):
"Screening": False,
"Full_atom": False,
"TDDFT": False,
"PBE96": False
"PBE96": False,
}
The detailed description of the FDMNES parameters can be find at
its official website. If the user wants to change some parameters,
they can just add a key-value pair to the cards.
e_range : str
The energy range E that one defines in the input is the energy of
the photoelectron relative to the phonon level. If one wants the
The energy range E that one defines in the input is the energy of
the photoelectron relative to the phonon level. If one wants the
output energy relative to the Fermi level, put ``Energpho`` = False.
edge : str
The XAS edge of the calculation.
radius : float
FDMNES uses clusters for its calculations. The ``radius`` parameter
determines how large to make the cluster. It is calculated from the
absorbing atom center in units of Angstroms. The cluster radius is
absorbing atom center in units of Angstroms. The cluster radius is
applicable to both SCF and XAS caluclations.
name : str
The name of the calculation.
Expand All @@ -77,9 +75,8 @@ def __init__(
e_range="-5. 0.5 60.",
edge="K",
radius=7.0,
name=None
name=None,
):

self._cards = cards
self._radius = radius
self._range = e_range
Expand All @@ -88,10 +85,10 @@ def __init__(
self._edge = edge

self.validate_edge()

def validate_edge(self):
"""
Validates and adjusts the edge attribute based on standard edge choices
Validates and adjusts the edge attribute based on standard edge choices
supported by FDMNES.
Edge types recognized:
Expand All @@ -102,22 +99,43 @@ def validate_edge(self):
- 'N4', 'N5', 'N45'
Warnings:
Warns if the provided edge is 'L' that it is being modified to 'L23'.
Warns if the provided edge is not recognized and is being set to 'K'.
Warns if the provided edge is 'L' that it is being modified to
'L23'.
Warns if the provided edge is not recognized and is being set to
'K'.
Returns:
None.
"""
valid_edges = ["K", "L1", "L2", "L3", "L23", "M1", "M2", "M3", "M23",
"M4", "M5", "M45", "N1", "N2", "N3", "N23", "N4", "N5", "N45"]

valid_edges = [
"K",
"L1",
"L2",
"L3",
"L23",
"M1",
"M2",
"M3",
"M23",
"M4",
"M5",
"M45",
"N1",
"N2",
"N3",
"N23",
"N4",
"N5",
"N45",
]
if self._edge == "L":
warn("Edge 'L' changed to 'L23' for FDMNES compatibility.")
self._edge = "L23"
elif self._edge not in valid_edges:
warn(f"Edge {self._edge} not recognized. Defaulting to 'K'.")
self._edge = "K"


def get_FDMNESinput(self, structure, Z_absorber):
"""Constructs and returns a dictionary corresponds to the
parameters in FDMNES optimized for the input structure and edge
Expand All @@ -136,7 +154,7 @@ def get_FDMNESinput(self, structure, Z_absorber):
dict
A dictionary of FDMNES input parameters.
"""

cards = self._cards.copy()
species_z_list = [species.Z for species in structure.species]

Expand All @@ -146,9 +164,10 @@ def get_FDMNESinput(self, structure, Z_absorber):
if "Nonrelat" not in cards.keys():
cards["Spinorbit"] = True
warn(
"Spin-orbit has been turned on for K-edge calculation for accuracy."
"The simulation is typically 4 to 8 times longer and need 2 times "
"more memory space. To turn it off, set 'Nonrelat' = True. "
"Spin-orbit has been turned on for K-edge calculation "
"for accuracy. The simulation is typically 4 to 8 times "
"longer and need 2 times more memory space. To turn"
" it off, set 'Nonrelat' = True. "
)
if any(Z_absorber in r for r in transition_metal_ranges):
cards["Quadrupole"] = True
Expand All @@ -158,19 +177,18 @@ def get_FDMNESinput(self, structure, Z_absorber):

if any(z > 36 for z in species_z_list):
cards["Relativism"] = True

if any(z > 50 for z in species_z_list):
cards["Spinorbit"] = True

if 8 in species_z_list:
cards["Full_atom"] = True

return cards


def write(self, target_directory, **kwargs):
"""Writes the input files for the provided structure and absoring specie.
In the case of Fdmnes, if Z_absorber is None, then the absorbing specie
"""Writes the input files for the provided structure and absorber.
In the case of Fdmnes, if Z_absorber is None, then the absorbing specie
is the first one in the atom list.
Parameters
Expand All @@ -180,7 +198,7 @@ def write(self, target_directory, **kwargs):
**kwargs
Must contain the ``structure`` key (the
:class:`pymatgen.core.structure.Structure` of interest) and the
``Z_absorber`` key (an int indicates the atomic number of the
``Z_absorber`` key (an int indicates the atomic number of the
absorbing chemical specie).
Returns
Expand All @@ -203,21 +221,21 @@ def write(self, target_directory, **kwargs):
a, b, c = structure.lattice.abc
alpha, beta, gamma = structure.lattice.angles
atomic_numbers = structure.atomic_numbers
scaled_positions = structure.frac_coords
scaled_positions = structure.frac_coords

if Z_absorber is None:
Z_absorber = atomic_numbers[0]
warn(
"Z_absorber is not provided, apply the first atom specie"
"Z_absorber is not provided, apply the first atom specie"
f"to be the absorbing specie Z={atomic_numbers[0]} "
)

element_absorber = Element.from_Z(Z_absorber).symbol
target_directory = Path(target_directory)
target_directory.mkdir(exist_ok=True, parents=True)

fdmnesinput = self.get_FDMNESinput(structure, Z_absorber)
filepath = target_directory / f"{element_absorber}_in.txt"
filepath = target_directory / f"{element_absorber}_in.txt"

with open(filepath, "w") as f:
f.write("Filout\n")
Expand All @@ -232,17 +250,22 @@ def write(self, target_directory, **kwargs):
for key, value in fdmnesinput.items():
if value:
f.write(f"{key}\n\n")

f.write("Crystal \n")
f.write(f"{a:.4f} {b:.4f} {c:.4f} {alpha:.1f} {beta:.1f} {gamma:.1f}\n")
f.write(
f"{a:.4f} {b:.4f} {c:.4f} {alpha:.1f} {beta:.1f} {gamma:.1f}\n"
)
for atomic_number, pos in zip(atomic_numbers, scaled_positions):
f.write(f" {atomic_number} {pos[0]:.4f} {pos[1]:.4f} {pos[2]:.4f}\n")

f.write(f"\nZ_Absorber\n")
f.write(
f" {atomic_number} {pos[0]:.4f} {pos[1]:.4f} "
f"{pos[2]:.4f}\n"
)

f.write("\nZ_Absorber\n")
f.write(f" {Z_absorber}\n\n")
f.write("Edge \n")
f.write(f" {self._edge}\n\n")
f.write("Convolution \n\n")
f.write("End")

return {"pass": True, "errors": dict(), "path": str(filepath)}
return {"pass": True, "errors": dict(), "path": str(filepath)}

0 comments on commit 787609e

Please sign in to comment.