From 89b3e817e6542e11b2ff3e7bfb9c9af10fb987a7 Mon Sep 17 00:00:00 2001 From: Zezhong Zhang Date: Tue, 16 Jul 2024 21:39:32 +0200 Subject: [PATCH] support Dirac GOS --- doc/user_guide/eels.rst | 1 + examples/model_fitting/EELS_curve_fitting.py | 9 ++ exspy/components/eels_cl_edge.py | 9 +- exspy/docstrings/model.py | 4 +- exspy/misc/eels/__init__.py | 3 +- exspy/misc/eels/dirac_gos.py | 137 +++++++++++++++++++ exspy/models/eelsmodel.py | 1 + exspy/signals/eels.py | 1 + exspy/tests/misc/test_gos.py | 22 +++ 9 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 exspy/misc/eels/dirac_gos.py diff --git a/doc/user_guide/eels.rst b/doc/user_guide/eels.rst index fd1066e71..cdd54fd15 100644 --- a/doc/user_guide/eels.rst +++ b/doc/user_guide/eels.rst @@ -274,6 +274,7 @@ The model for the GOS can be specified with the ``GOS`` argument By default, a freely usable tabulated dataset, in `gosh `__ format, is downloaded from Zenodo: `doi:10.5281/zenodo.7645765 `_. +As alternative, one can use the `Dirac GOS `__ to include the relativistic effects, which is downloaded from Zenodo: `doi:10.5281/zenodo.12751756 `_ and also follows the `gosh` format. Custom GOS saved in the `gosh `__ format can be used, the following example download a previous version (1.0) of the GOS file from Zenodo diff --git a/examples/model_fitting/EELS_curve_fitting.py b/examples/model_fitting/EELS_curve_fitting.py index 4087127c8..cca37f979 100644 --- a/examples/model_fitting/EELS_curve_fitting.py +++ b/examples/model_fitting/EELS_curve_fitting.py @@ -1,3 +1,4 @@ +# %% """ EELS curve fitting ================== @@ -21,3 +22,11 @@ m.enable_fine_structure() m.multifit(kind="smart") m.plot() + +# one can also use the Dirac GOS by specifying the GOS parameter +m = s.create_model(low_loss=ll, GOS="Dirac") +m.enable_fine_structure() +m.multifit(kind="smart") +m.plot() + +# %% diff --git a/exspy/components/eels_cl_edge.py b/exspy/components/eels_cl_edge.py index 3a2f9d8dd..aad155660 100644 --- a/exspy/components/eels_cl_edge.py +++ b/exspy/components/eels_cl_edge.py @@ -26,6 +26,7 @@ from hyperspy.component import Component from exspy.misc.eels.gosh_gos import GoshGOS, _GOSH_DOI +from exspy.misc.eels.dirac_gos import DiracGOS from exspy.misc.eels.hartree_slater_gos import HartreeSlaterGOS from exspy.misc.eels.hydrogenic_gos import HydrogenicGOS from exspy.misc.eels.effective_angle import effective_angle @@ -92,7 +93,7 @@ class EELSCLEdge(Component): Usually a string, for example, ``'Ti_L3'`` for the GOS of the titanium L3 subshell. If a dictionary is passed, it is assumed that Hartree Slater GOS was exported using `GOS.as_dictionary`, and will be reconstructed. - GOS : ``'gosh'``, ``'hydrogenic'``, ``'Hartree-Slater'`` or str + GOS : ``'gosh'``,``'Dirac'``, ``'hydrogenic'``, ``'Hartree-Slater'`` or str The GOS to use. Default is ``'gosh'``. If str, it must the path to gosh GOS file. gos_file_path : str, None @@ -169,13 +170,15 @@ def __init__(self, element_subshell, GOS="gosh", gos_file_path=None): if GOS == "gosh": self.GOS = GoshGOS(element_subshell, gos_file_path=gos_file_path) + elif GOS == "Dirac": + self.GOS = DiracGOS(element_subshell,gos_file_path=gos_file_path) elif GOS == "Hartree-Slater": # pragma: no cover self.GOS = HartreeSlaterGOS(element_subshell) elif GOS == "hydrogenic": self.GOS = HydrogenicGOS(element_subshell) else: raise ValueError( - "GOS must be one of 'gosh', 'hydrogenic' or 'Hartree-Slater'." + "GOS must be one of 'gosh', 'Dirac','hydrogenic' or 'Hartree-Slater'." ) self.onset_energy.value = self.GOS.onset_energy self.onset_energy.free = False @@ -189,6 +192,8 @@ def __init__(self, element_subshell, GOS="gosh", gos_file_path=None): self._whitelist["GOS"] = ("init", GOS) if GOS == "gosh": self._whitelist["element_subshell"] = ("init", self.GOS.as_dictionary(True)) + elif GOS == "Dirac": + self._whitelist["element_subshell"] = ("init", self.GOS.as_dictionary(True)) elif GOS == "Hartree-Slater": # pragma: no cover self._whitelist["element_subshell"] = ("init", self.GOS.as_dictionary(True)) elif GOS == "hydrogenic": diff --git a/exspy/docstrings/model.py b/exspy/docstrings/model.py index 30db3d820..7f9465845 100644 --- a/exspy/docstrings/model.py +++ b/exspy/docstrings/model.py @@ -21,11 +21,11 @@ from exspy.misc.eels.gosh_gos import _GOSH_DOI -GOS_PARAMETER = """GOS : 'hydrogenic', 'gosh', 'Hartree-Slater'. +GOS_PARAMETER = """GOS : 'hydrogenic', 'gosh', 'Dirac', 'Hartree-Slater'. The GOS to use. Default is ``'gosh'``. gos_file_path : str, None - Only with GOS='gosh'. Specify the file path of the gosh file + Only with GOS='gosh' or 'Dirac'. Specify the file path of the gosh file to use. If None, use the file from doi:{}""".format(_GOSH_DOI) EELSMODEL_PARAMETERS = """ll : None or EELSSpectrum diff --git a/exspy/misc/eels/__init__.py b/exspy/misc/eels/__init__.py index 76a0d5eb9..3dd45ad3b 100644 --- a/exspy/misc/eels/__init__.py +++ b/exspy/misc/eels/__init__.py @@ -1,5 +1,6 @@ from exspy.misc.eels.hydrogenic_gos import HydrogenicGOS from exspy.misc.eels.gosh_gos import GoshGOS +from exspy.misc.eels.dirac_gos import DiracGOS from exspy.misc.eels.hartree_slater_gos import HartreeSlaterGOS -__all__ = ["HydrogenicGOS", "GoshGOS", "HartreeSlaterGOS"] +__all__ = ["HydrogenicGOS", "GoshGOS", "DiracGOS", "HartreeSlaterGOS"] diff --git a/exspy/misc/eels/dirac_gos.py b/exspy/misc/eels/dirac_gos.py new file mode 100644 index 000000000..cfcea99e2 --- /dev/null +++ b/exspy/misc/eels/dirac_gos.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# Copyright 2007-2024 The eXSpy developers +# +# This file is part of eXSpy. +# +# eXSpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# eXSpy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with eXSpy. If not, see . + +import logging + +import h5py +import numpy as np +import pooch +from scipy import constants + +from hyperspy.defaults_parser import preferences +from exspy.misc.eels.base_gos import TabulatedGOS + + +_logger = logging.getLogger(__name__) + +R = constants.value("Rydberg constant times hc in eV") +a0 = constants.value("Bohr radius") + +_Dirac_GOSH_DOI = "10.5281/zenodo.12752410" +_Dirac_GOSH_URL = f"doi:{_Dirac_GOSH_DOI}/Dirac_GOS.gosh" +_Dirac_GOSH_KNOWN_HASH = "md5:02fb22ab55e39e51eb03c08dbf699545" + + +class DiracGOS(TabulatedGOS): + """Read Generalized Oscillator Strength from a Dirac GOSH database. + + Parameters + ---------- + element_subshell : {str, dict} + Usually a string, for example, 'Ti_L3' for the GOS of the titanium L3 + subshell. If a dictionary is passed, it is assumed that a GOSH GOS was + exported using `GOS.as_dictionary`, and will be reconstructed. + + Methods + ------- + readgosarray() + Read the GOS files of the element subshell from the location + defined in Preferences. + get_qaxis_and_gos(ienergy, qmin, qmax) + given the energy axis index and qmin and qmax values returns + the qaxis and gos between qmin and qmax using linear + interpolation to include qmin and qmax in the range. + + Attributes + ---------- + energy_axis : array + The tabulated energy axis + qaxis : array + The tabulated qaxis + energy_onset: float + The energy onset for the given element subshell as obtained + from iternal tables. + + """ + + _name = "gosh" + _whitelist = { + "gos_array": None, + "rel_energy_axis": None, + "qaxis": None, + "element": None, + "subshell": None, + "subshell_factor": None, + } + + def __init__(self, element_subshell, gos_file_path=None): + """ + Parameters + ---------- + element_subshell : str + For example, 'Ti_L3' for the GOS of the titanium L3 subshell + gos_file_path : str + The path of the gosh file to use. + """ + + if gos_file_path is None: + gos_file_path = pooch.retrieve( + url=_Dirac_GOSH_URL, + known_hash=_Dirac_GOSH_KNOWN_HASH, + progressbar=preferences.General.show_progressbar, + ) + self.gos_file_path = gos_file_path + super().__init__(element_subshell=element_subshell) + + def read_gos_data(self): + _logger.info( + "GOSH precomputed GOS\n" + f"\tElement: {self.element} " + f"\tSubshell: {self.subshell}" + f"\tOnset Energy = {self.onset_energy}" + ) + element = self.element + subshell = self.subshell + + error_message = ( + "The GOSH Parametrized GOS database does not " + f"contain a valid entry the {subshell} subshell " + f"of {element}. Please select a different database." + ) + + with h5py.File(self.gos_file_path, "r") as h: + conventions = h["metadata/edges_info"] + if subshell not in conventions: + raise ValueError(error_message) + table = conventions[subshell].attrs["table"] + self.subshell_factor = conventions[subshell].attrs["occupancy_ratio"] + stem = f"/{element}/{table}" + if stem not in h: + raise ValueError(error_message) + gos_group = h[stem] + gos = gos_group["data"][:] + q = gos_group["q"][:] + free_energies = gos_group["free_energies"][:] + doi = h["/metadata/data_ref"].attrs["data_doi"] + + gos = np.squeeze(gos.T) + self.doi = doi + self.gos_array = gos + self.qaxis = q + self.rel_energy_axis = free_energies - min(free_energies) + self.energy_axis = self.rel_energy_axis + self.onset_energy diff --git a/exspy/models/eelsmodel.py b/exspy/models/eelsmodel.py index b6b6a8d8d..b1b590e99 100644 --- a/exspy/models/eelsmodel.py +++ b/exspy/models/eelsmodel.py @@ -75,6 +75,7 @@ def __init__( Parameters ---------- + GOS : Generalized Oscillator Strength, availiable option in ['hydrogenic', 'gosh', 'Dirac', 'Hartree-Slater'], default is 'gosh'. spectrum : a EELSSpectrum instance %s diff --git a/exspy/signals/eels.py b/exspy/signals/eels.py index 4dbf0bcc2..67e372c4f 100644 --- a/exspy/signals/eels.py +++ b/exspy/signals/eels.py @@ -1635,6 +1635,7 @@ def create_model( Parameters ---------- %s + GOS: Generalized Oscillator Strength, availiable option in ['hydrogenic', 'gosh', 'Dirac', 'Hartree-Slater'], default is 'gosh' Returns ------- diff --git a/exspy/tests/misc/test_gos.py b/exspy/tests/misc/test_gos.py index dccb8bc0c..06b157046 100644 --- a/exspy/tests/misc/test_gos.py +++ b/exspy/tests/misc/test_gos.py @@ -24,6 +24,7 @@ from exspy._defaults_parser import preferences from exspy.misc.eels.gosh_gos import GoshGOS +from exspy.misc.eels.dirac_gos import DiracGOS from exspy.misc.eels.hartree_slater_gos import HartreeSlaterGOS from exspy.misc.eels import HydrogenicGOS from exspy.misc.elements import elements @@ -80,3 +81,24 @@ def test_binding_energy_database(): # These elements are not in the database if element not in ["Bk", "Cf", "Cm", "metadata"]: assert "Binding_energies" in elements[element]["Atomic_properties"].keys() + +def test_dirac_gosh_not_in_conventions(): + gos = DiracGOS("Ti_L2") + gos.subshell = "L234" + with pytest.raises(ValueError): + gos.read_gos_data() + + +def test_dirac_gosh_not_in_file(): + # Use version 1.0 which doesn't have the Ac element + with pytest.raises(ValueError): + _ = DiracGOS("Ac_L3", gos_file_path=GOSH10) + + +def test_dirac_binding_energy_database(): + gos = DiracGOS("Ti_L3") + gosh15 = h5py.File(gos.gos_file_path) + for element in gosh15.keys(): + # These elements are not in the database + if element not in ["Bk", "Cf", "Cm", "metadata"]: + assert "Binding_energies" in elements[element]["Atomic_properties"].keys()