Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ generation, and currently supports the following codes:
- [OCEAN](https://www.nist.gov/services-resources/software/ocean)
- [EXCITING](https://exciting-code.org)
- [Xspectra](https://gitlab.com/QEF/q-e/-/tree/master/XSpectra)
- [FDMNES](https://fdmnes.neel.cnrs.fr)

with more on the way! The software is intended to be user-friendly,
extensively documented and tested, and extendable for those users who
Expand Down
157 changes: 86 additions & 71 deletions lightshow/parameters/fdmnes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from pathlib import Path
from warnings import warn

from monty.json import MSONable
Expand All @@ -17,7 +16,8 @@
"Screening": False,
"Full_atom": False,
"TDDFT": False,
"PBE96": False,
"Perdew": True,
"Green": False
}


Expand Down Expand Up @@ -72,23 +72,22 @@ class FDMNESParameters(MSONable, _BaseParameters):
def __init__(
self,
cards=FDMNES_DEFAULT_CARDS,
e_range="-5. 0.5 60.",
e_range="-5. 0.2 60.",
edge="K",
radius=7.0,
name=None,
radius=5.0,
name=None
):
self._cards = cards
self._radius = radius
self._range = e_range

self._name = name if name is not None else "FDMNES"
self._e_range = e_range
self._edge = edge


self._name = name if name is not None else "FDMNES"
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 Down Expand Up @@ -160,29 +159,33 @@ def get_FDMNESinput(self, structure, Z_absorber):

transition_metal_ranges = [range(21, 31), range(39, 49), range(57, 81)]

if self._edge == "K":
if "Nonrelat" not in cards.keys():
if cards.get("Green", False):
return cards

else:
if self._edge == "K":
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. "
)
if any(Z_absorber in r for r in transition_metal_ranges):
cards["Quadrupole"] = True

elif self._edge == "L23" and Z_absorber in range(21, 26):
cards["TDDFT"] = True

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
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. "
)
if any(Z_absorber in r for r in transition_metal_ranges):
cards["Quadrupole"] = True

elif self._edge == "L23" and Z_absorber in range(21, 26):
cards["TDDFT"] = True

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
if 8 in species_z_list:
cards["Full_atom"] = True

return cards

Expand Down Expand Up @@ -211,61 +214,73 @@ def write(self, target_directory, **kwargs):
``{"pass": True, "errors": dict(), "path": ...}``.
"""

structure = kwargs["structure"]
Z_absorber = kwargs["Z_absorber"]

if structure is None:
raise ValueError("Structure must be provided.")
structure = kwargs.get("structure_uc", kwargs["structure"])

Z_absorber = kwargs.get("Z_absorber")

sites = kwargs.get("sites")
if sites is None:
# Infer absorber sites from Z_absorber; if absent, default to the first site
if Z_absorber is None:
sites = [0]
else:
if isinstance(Z_absorber, (list, tuple, set)):
zset = {int(z) for z in Z_absorber}
else:
zset = {int(Z_absorber)}

sites = [i for i, s in enumerate(structure) if int(s.specie.Z) in zset]
if not sites:
raise ValueError(f"No absorber sites found for Z_absorber={Z_absorber}.")

all_species = [structure[site].specie.symbol for site in sites]
species = list(dict.fromkeys(all_species))
Z_absorbers = [Element(specie).Z for specie in species]

# prepare lattice parameters, atomic numbers and fractional coordinates
a, b, c = structure.lattice.abc
alpha, beta, gamma = structure.lattice.angles
atomic_numbers = structure.atomic_numbers
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"
f"to be the absorbing specie Z={atomic_numbers[0]} "
)
for i in range(len(Z_absorbers)):
specie = species[i]
Z_absorber = Z_absorbers[i]

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

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

with open(filepath, "w") as f:
f.write("Filout\n")
f.write(f" {element_absorber}\n\n")
with open(filepath, "w") as f:
f.write("Filout\n")
f.write(f" {specie}\n\n")

f.write("Range\n")
f.write(f" {self._range}\n\n")
f.write("Range\n")
f.write(f" {self._e_range}\n\n")

f.write("Radius\n")
f.write(f" {self._radius}\n\n")
f.write("Radius\n")
f.write(f" {self._radius}\n\n")

for key, value in fdmnesinput.items():
if value:
f.write(f"{key}\n\n")
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"
)
for atomic_number, pos in zip(atomic_numbers, scaled_positions):
f.write("Crystal \n")
f.write(
f" {atomic_number} {pos[0]:.4f} {pos[1]:.4f} "
f"{pos[2]:.4f}\n"
f"{a:.4f} {b:.4f} {c:.4f} {alpha:.1f} {beta:.1f} {gamma:.1f}\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")
for atomic_number, pos in zip(atomic_numbers, scaled_positions):
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)}
95 changes: 86 additions & 9 deletions notebooks/00_basic_usage.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "48a5bfa0-ae30-4a80-ba7f-ac094e404318",
"id": "99bb2f21-1a1a-4a34-b19a-deb9a8aa6b9a",
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -175,10 +175,8 @@
{
"cell_type": "code",
"execution_count": null,
"id": "1e52ffc5-4209-47af-964b-e9d3401480f2",
"metadata": {
"tags": []
},
"id": "fac0a077-9a11-4a3c-92ad-04d837cc1bb5",
"metadata": {},
"outputs": [],
"source": [
"import lightshow\n",
Expand All @@ -193,7 +191,7 @@
"metadata": {},
"outputs": [],
"source": [
"database = Database.from_materials_project(material_ids=[\n",
"database = Database.from_materials_project(api_key=api_key, material_ids=[\n",
" \"mp-390\",\n",
" \"mp-1215\",\n",
" \"mp-1840\",\n",
Expand Down Expand Up @@ -249,7 +247,7 @@
"metadata": {},
"outputs": [],
"source": [
"from lightshow import FEFFParameters, VASPParameters, OCEANParameters, EXCITINGParameters, XSpectraParameters"
"from lightshow import FEFFParameters, VASPParameters, OCEANParameters, EXCITINGParameters, XSpectraParameters, FDMNESParameters"
]
},
{
Expand Down Expand Up @@ -599,6 +597,7 @@
"execution_count": null,
"id": "bc6b6e25",
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
},
Expand Down Expand Up @@ -748,6 +747,84 @@
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af7b7b86-d078-4f33-91ca-31358f245c21",
"metadata": {},
"outputs": [],
"source": [
"database.write(\"test\", options=[xspectra_params], absorbing_atoms=[\"O\"])"
]
},
{
"cell_type": "markdown",
"id": "3a6ddf91-4f45-41f5-afe3-f3c7a7295ed4",
"metadata": {},
"source": [
"### FDMNES Parameters\n",
"Documentation can be found [here](https://cloud.neel.cnrs.fr/index.php/s/nL2c6kH2PLwcB5r)."
]
},
{
"cell_type": "markdown",
"id": "6736ee82-eae9-4851-bb09-4e30e68d1ce9",
"metadata": {},
"source": [
"Next let's construct the FDMNES inputs. FDMNESParamters class takes a few core arguments, including `cards`(simulation settings), `e_range`(the energy range and step size of the calculated spectrum, e.g., `\"-5. 0.2 60.\"` or `\"-5. 0.2 10. 0.5 30. 1. 60. 2. 100.\"`), `edge`(the type of absorption edge or edges, e.g., `\"K\"` or `\"L23\"`), and `radius`(the surrounding cluster size Angstroms of the absorbing atom). The default values are:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e48ba71c-55b1-4738-8d9d-d563f5205501",
"metadata": {},
"outputs": [],
"source": [
"fdmnes_params = FDMNESParameters(\n",
" cards={\n",
" \"Energpho\": True,\n",
" \"Memory_save\": True,\n",
" \"Quadrupole\": False,\n",
" \"Relativism\": False,\n",
" \"Spinorbit\": None,\n",
" \"SCF\": True,\n",
" \"SCFexc\": False,\n",
" \"Screening\": False,\n",
" \"Full_atom\": False,\n",
" \"TDDFT\": False,\n",
" \"Perdew\": True,\n",
" \"Green\": False,\n",
" },\n",
" e_range=\"-5. 0.2 60.\",\n",
" edge=\"K\",\n",
" radius=5.0,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "337687b6-2512-43bd-a562-1402d982ca97",
"metadata": {},
"source": [
"FDMNES is a DFT program with a TDDFT extension. DFT is generally sufficient for the K-edges of all elements and the L23 edges of heavy elements. To improve the L23 edge split ratio of the first half of the 3d elements, `TDDFT` is turned on.\n",
"\n",
"If you are not satisfied with the calculation results from the default settings, try the following:\n",
"- Increase `radius`: Adjust the value up to convergence.\n",
"- Turn on `SCFexc`: By default, in FDMNES, self-consistency is performed without a core-hole at the K, L1, M1, and O1 edges, with the core-hole and its screening charge added only during the XANES step. When the agreement is not sufficiently good, particularly at the rising edge, this behavior can be modified using `SCFexc` (which includes the core-hole during the SCF step).\n",
"- Improve convolution: Experiment with `Gamma_max` and/or `Gamma_hole`. Refer to the documentation for more details.\n"
]
},
{
"cell_type": "markdown",
"id": "f265e6b4-10be-4e82-98b7-1e8f0b296779",
"metadata": {},
"source": [
"If you encounter memory issues, try turning off `Spinorbit`, downgrading to `Relativism`, or switching to a non-relativistic calculation by enabling `Nonrelat`.\n",
"\n",
"If you just want to perform a quick calculation, use the multiple scattering theory by turning on `Green`."
]
},
{
"cell_type": "markdown",
"id": "9430320f-2838-44a8-bab0-e01a7e59c877",
Expand Down Expand Up @@ -775,7 +852,7 @@
"metadata": {},
"outputs": [],
"source": [
"database.write(\"test\", options=[feff_params, vasp_params_corehole, ocean_params, exciting_params, xspectra_params], absorbing_atoms=[\"Ti\"])"
"database.write(\"test\", options=[feff_params, vasp_params_corehole, ocean_params, exciting_params, xspectra_params, fdmnes_params], absorbing_atoms=[\"Ti\"])"
]
},
{
Expand Down Expand Up @@ -871,7 +948,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
"version": "3.10.16"
},
"toc-autonumbering": true
},
Expand Down
Loading
Loading