Skip to content

Commit

Permalink
Merge pull request #72 from zezhong-zhang/dev
Browse files Browse the repository at this point in the history
support Dirac GOS
  • Loading branch information
ericpre authored Jul 27, 2024
2 parents 9b612ba + c46b5d3 commit 3b7425e
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 36 deletions.
6 changes: 6 additions & 0 deletions doc/user_guide/eels.rst
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,12 @@ The model for the GOS can be specified with the ``GOS`` argument
By default, a freely usable tabulated dataset, in `gosh <https://gitlab.com/gguzzina/gosh>`__
format, is downloaded from Zenodo:
`doi:10.5281/zenodo.7645765 <https://zenodo.org/record/7645765>`_.
As an alternative, one can use the `Dirac GOS <https://arxiv.org/abs/2405.10151>`_ to include the relativistic effects using the Dirac solution, which can be downloaded from Zenodo: `doi:10.5281/zenodo.12800856 <https://zenodo.org/record/12800856>`_. The Dirac GOS can be used as follows:

.. code-block:: python
>>> m = s.create_model(ll=ll, GOS="Dirac")
Custom GOS saved in the `gosh <https://gitlab.com/gguzzina/gosh>`__ format can be used,
the following example download a previous version (1.0) of the GOS file from Zenodo
Expand Down
9 changes: 9 additions & 0 deletions examples/model_fitting/EELS_curve_fitting.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# %%
"""
EELS curve fitting
==================
Expand All @@ -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()

# %%
48 changes: 35 additions & 13 deletions exspy/components/eels_cl_edge.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,19 @@

import functools
import logging
import warnings
import math

import numpy as np
from scipy.interpolate import splev

from hyperspy.component import Component
from exspy.misc.eels.gosh_gos import GoshGOS, _GOSH_DOI
from exspy.misc.eels.gosh_gos import GoshGOS, GOSH_SOURCES
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
from hyperspy.ui_registry import add_gui_method

from hyperspy.exceptions import VisibleDeprecationWarning

_logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -75,8 +76,9 @@ class EELSCLEdge(Component):
The preferred option is to use a database of cross sections in GOSH
format. One such database can be freely downloaded from Zenodo at:
https://doi.org/%s while information on the GOSH format
are available at: https://gitlab.com/gguzzina/gosh .
https://doi.org/%s while information on the GOSH format are available at: https://gitlab.com/gguzzina/gosh. Alternatively,
one can use the Dirac GOSH database to include relativistic effects,
available at: https://doi.org/%s.
eXSpy also supports Peter Rez's Hartree Slater cross sections
parametrised as distributed by Gatan in their Digital Micrograph (DM)
Expand All @@ -92,11 +94,11 @@ 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
The GOS to use. Default is ``'gosh'``. If str, it must the path to gosh
GOS file.
GOS : ``'dft'``,``'dirac'``, ``'hydrogenic'``, ``'Hartree-Slater'`` or str
The GOS to use. Default is ``'dft'``. If str, it must the path to gosh GOS file.
The ``'dft'`` and ``'dirac'`` databases are in the ``'gosh'`` format.
gos_file_path : str, None
Only with ``GOS='gosh'``. Specify the file path of the gosh file
Only with ``GOS='dft' or 'dirac'``. Specify the file path of the gosh file
to use. If None, use the file from https://doi.org/%s
Attributes
Expand Down Expand Up @@ -146,7 +148,7 @@ class EELSCLEdge(Component):
_fine_structure_coeff_free = True
_fine_structure_spline_active = True

def __init__(self, element_subshell, GOS="gosh", gos_file_path=None):
def __init__(self, element_subshell, GOS="dft", gos_file_path=None):
# Declare the parameters
self.fine_structure_components = FSet(component=self)
Component.__init__(
Expand All @@ -168,14 +170,28 @@ def __init__(self, element_subshell, GOS="gosh", gos_file_path=None):
self.GOS = None

if GOS == "gosh":
self.GOS = GoshGOS(element_subshell, gos_file_path=gos_file_path)
warnings.warn(
"The value 'gosh' of the `GOS` parameter has been renamed to 'dft' in "
"eXSpy 0.3.0, use `GOS='dft'` instead. "
"Using `GOS='gosh'` will stop working in eXSpy 1.0.",
VisibleDeprecationWarning,
)
GOS = "dft"
if GOS == "dft":
self.GOS = GoshGOS(
element_subshell, gos_file_path=gos_file_path, source="dft"
)
elif GOS == "dirac":
self.GOS = GoshGOS(
element_subshell, gos_file_path=gos_file_path, source="dirac"
)
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 'dft', 'dirac','hydrogenic' or 'Hartree-Slater'."
)
self.onset_energy.value = self.GOS.onset_energy
self.onset_energy.free = False
Expand All @@ -187,7 +203,9 @@ def __init__(self, element_subshell, GOS="gosh", gos_file_path=None):
self.intensity.bmax = None

self._whitelist["GOS"] = ("init", GOS)
if GOS == "gosh":
if GOS == "dft":
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))
Expand Down Expand Up @@ -535,4 +553,8 @@ def as_dictionary(self, fullcopy=True):
return dic


EELSCLEdge.__doc__ %= (_GOSH_DOI, _GOSH_DOI)
EELSCLEdge.__doc__ %= (
GOSH_SOURCES["dft"]["DOI"],
GOSH_SOURCES["dirac"]["DOI"],
GOSH_SOURCES["dft"]["DOI"],
)
12 changes: 7 additions & 5 deletions exspy/docstrings/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@

"""Common docstring snippets for model."""

from exspy.misc.eels.gosh_gos import _GOSH_DOI
from exspy.misc.eels.gosh_gos import GOSH_SOURCES

GOS_PARAMETER = """GOS : 'hydrogenic', 'gosh', 'Hartree-Slater'.
The GOS to use. Default is ``'gosh'``.
GOS_PARAMETER = """GOS : 'hydrogenic', 'dft', 'dirac', 'Hartree-Slater'.
The GOS to use. Default is ``'dft'``.
gos_file_path : str, None
Only with GOS='gosh'. Specify the file path of the gosh file
to use. If None, use the file from doi:{}""".format(_GOSH_DOI)
Only with GOS='dft' or 'dirac'. Specify the file path of the gosh file
to use. If None, use the file from doi:{}""".format(
GOSH_SOURCES["dft"]["DOI"]
)

EELSMODEL_PARAMETERS = """ll : None or EELSSpectrum
If an EELSSpectrum is provided, it will be assumed that it is
Expand Down
25 changes: 19 additions & 6 deletions exspy/misc/eels/gosh_gos.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,17 @@
R = constants.value("Rydberg constant times hc in eV")
a0 = constants.value("Bohr radius")

_GOSH_DOI = "10.5281/zenodo.7645765"
_GOSH_URL = f"doi:{_GOSH_DOI}/Segger_Guzzinati_Kohl_1.5.0.gosh"
_GOSH_KNOWN_HASH = "md5:7fee8891c147a4f769668403b54c529b"
DFT_GOSH = {
"DOI": "10.5281/zenodo.7645765",
"URL": "doi:10.5281/zenodo.7645765/Segger_Guzzinati_Kohl_1.5.0.gosh",
"KNOWN_HASH": "md5:7fee8891c147a4f769668403b54c529b",
}
DIRAC_GOSH = {
"DOI": "10.5281/zenodo.12800856",
"URL": "doi:10.5281/zenodo.12800856/Dirac_GOS_compact.gosh",
"KNOWN_HASH": "md5:01a855d3750d2c063955248358dbee8d",
}
GOSH_SOURCES = {"dft": DFT_GOSH, "dirac": DIRAC_GOSH}


class GoshGOS(TabulatedGOS):
Expand Down Expand Up @@ -79,20 +87,25 @@ class GoshGOS(TabulatedGOS):
"subshell_factor": None,
}

def __init__(self, element_subshell, gos_file_path=None):
def __init__(self, element_subshell, gos_file_path=None, source="dft"):
"""
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.
source : str
The source of the GOS data. Options are 'dft' or 'dirac'.
"""

if gos_file_path is None:
source = source.lower()
assert source in GOSH_SOURCES.keys(), f"Invalid source: {source}"
self._name = source
gos_file_path = pooch.retrieve(
url=_GOSH_URL,
known_hash=_GOSH_KNOWN_HASH,
url=GOSH_SOURCES[source]["URL"],
known_hash=GOSH_SOURCES[source]["KNOWN_HASH"],
progressbar=preferences.General.show_progressbar,
)
self.gos_file_path = gos_file_path
Expand Down
16 changes: 10 additions & 6 deletions exspy/models/eelsmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,16 @@ def __init__(
auto_background=True,
auto_add_edges=True,
low_loss=None,
GOS="gosh",
GOS="dft",
dictionary=None,
gos_file_path=None,
):
"""
Build an EELS model.
Parameters
----------
GOS : Generalized Oscillator Strength, availiable option in ['hydrogenic', 'dft', 'dirac', 'Hartree-Slater'], default is 'dft'.
spectrum : a EELSSpectrum instance
%s
Expand All @@ -94,6 +96,7 @@ def __init__(
self.convolution_axis = None
self.low_loss = low_loss
self.GOS = GOS
self.gos_file_path = gos_file_path
self.edges = []
self._background_components = []
self._whitelist.update(
Expand Down Expand Up @@ -410,10 +413,9 @@ def _add_edges_from_subshells_names(self, e_shells=None):
if e_shells is None:
e_shells = list(self.signal.subshells)
e_shells.sort()
master_edge = EELSCLEdge(e_shells.pop(), self.GOS)
# If self.GOS was None, the GOS is set by eels_cl_edge so
# we reassing the value of self.GOS
self.GOS = master_edge.GOS._name
master_edge = EELSCLEdge(
e_shells.pop(), self.GOS, gos_file_path=self.gos_file_path
)
self.append(master_edge)
element = master_edge.element
while len(e_shells) > 0:
Expand All @@ -429,7 +431,9 @@ def _add_edges_from_subshells_names(self, e_shells=None):
# Add the other subshells of the same element
# and couple their intensity and onset_energy to that of the
# master edge
edge = EELSCLEdge(e_shells.pop(), GOS=self.GOS)
edge = EELSCLEdge(
e_shells.pop(), GOS=self.GOS, gos_file_path=self.gos_file_path
)

edge.intensity.twin = master_edge.intensity
edge.onset_energy.twin = master_edge.onset_energy
Expand Down
4 changes: 3 additions & 1 deletion exspy/signals/eels.py
Original file line number Diff line number Diff line change
Expand Up @@ -1626,7 +1626,7 @@ def create_model(
low_loss=None,
auto_background=True,
auto_add_edges=True,
GOS="gosh",
GOS="dft",
gos_file_path=None,
dictionary=None,
):
Expand All @@ -1635,6 +1635,7 @@ def create_model(
Parameters
----------
%s
GOS: Generalized Oscillator Strength, availiable option in ['hydrogenic', 'dft', 'dirac', 'Hartree-Slater'], default is 'dft'
Returns
-------
Expand All @@ -1660,6 +1661,7 @@ def create_model(
auto_add_edges=auto_add_edges,
GOS=GOS,
dictionary=dictionary,
gos_file_path=gos_file_path,
)
return model

Expand Down
26 changes: 26 additions & 0 deletions exspy/tests/components/test_eels_cl_edge.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,29 @@ def test_restore_EELS_model(tmp_path):
s2 = hs.load(fname)
m3 = s2.models.restore(model_name)
np.testing.assert_allclose(m.as_signal(), m3.as_signal())


def test_restore_EELS_model_dirac(tmp_path):
s = hs.load(TEST_DATA_DIR / "coreloss_spectrum.msa", signal_type="EELS")
ll = hs.load(TEST_DATA_DIR / "lowloss_spectrum.msa", signal_type="EELS")

s.add_elements(("Mn", "O"))
s.set_microscope_parameters(
beam_energy=300, convergence_angle=24.6, collection_angle=13.6
)

m = s.create_model(low_loss=ll, GOS="dirac")
m.enable_fine_structure()
m.multifit(kind="smart")

model_name = "fit1"
m.store(model_name)
fname = tmp_path / "test_save_eelsmodel.hspy"
s.save(tmp_path / fname)
m2 = s.models.restore(model_name)

np.testing.assert_allclose(m.as_signal(), m2.as_signal())

s2 = hs.load(fname)
m3 = s2.models.restore(model_name)
np.testing.assert_allclose(m.as_signal(), m3.as_signal())
13 changes: 13 additions & 0 deletions exspy/tests/misc/test_gos.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,16 @@ 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 = GoshGOS("Ti_L2", source="dirac")
gos.subshell = "L234"
with pytest.raises(ValueError):
gos.read_gos_data()


def test_dirac_gosh_not_in_file():
# Dirac GOS which doesn't have the Uue element
with pytest.raises(ValueError):
_ = GoshGOS("Uue_L3", source="dirac")
37 changes: 32 additions & 5 deletions exspy/tests/models/test_eelsmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@
import hyperspy.api as hs
from exspy.misc.elements import elements_db as elements
from hyperspy.decorators import lazifyTestClass
from exspy.misc.eels.gosh_gos import _GOSH_URL, _GOSH_KNOWN_HASH
from exspy.misc.eels.gosh_gos import DFT_GOSH, DIRAC_GOSH
from exspy.signals import EELSSpectrum
from hyperspy.exceptions import VisibleDeprecationWarning


# Dask does not always work nicely with np.errstate,
Expand Down Expand Up @@ -72,20 +73,46 @@ def test_gos_hydrogenic(self):
m.fit()

def test_gos_gosh(self):
m = self.s.create_model(auto_add_edges=True, GOS="gosh")
assert m["B_K"].GOS._name == "gosh"
with pytest.warns(VisibleDeprecationWarning):
m = self.s.create_model(auto_add_edges=True, GOS="gosh")
assert m["B_K"].GOS._name == "dft"
m.fit()

with pytest.raises(ValueError):
self.s.create_model(auto_add_edges=True, GOS="not_a_GOS")

def test_gos_gosh_dft(self):
m = self.s.create_model(auto_add_edges=True, GOS="dft")
assert m["B_K"].GOS._name == "dft"
m.fit()

with pytest.raises(ValueError):
self.s.create_model(auto_add_edges=True, GOS="not_a_GOS")

def test_gos_gosh_dirac(self):
m = self.s.create_model(auto_add_edges=True, GOS="dirac")
assert m["B_K"].GOS._name == "dirac"
m.fit()

with pytest.raises(ValueError):
self.s.create_model(auto_add_edges=True, GOS="not_a_GOS")

def test_gos_file(self):
gos_file_path = pooch.retrieve(
url=_GOSH_URL,
known_hash=_GOSH_KNOWN_HASH,
url=DFT_GOSH["URL"],
known_hash=DFT_GOSH["KNOWN_HASH"],
)
self.s.create_model(auto_add_edges=True, gos_file_path=gos_file_path)

def test_gos_file_dirac(self):
gos_file_path = pooch.retrieve(
url=DIRAC_GOSH["URL"],
known_hash=DIRAC_GOSH["KNOWN_HASH"],
)
self.s.create_model(
auto_add_edges=True, gos_file_path=gos_file_path, GOS="dirac"
)

def test_auto_add_background_true(self):
m = self.s.create_model(auto_background=True)
from hyperspy.components1d import PowerLaw
Expand Down
Loading

0 comments on commit 3b7425e

Please sign in to comment.