Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support Dirac GOS #72

Merged
merged 27 commits into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
89b3e81
support Dirac GOS
zezhong-zhang Jul 16, 2024
e765f2b
remove test as the database has more elements than the dict
zezhong-zhang Jul 16, 2024
7c4d4b9
fix rst format
zezhong-zhang Jul 16, 2024
8f17731
use the registry in GoshGOS to support Dirac GOS
zezhong-zhang Jul 22, 2024
bfd8890
fix name error
zezhong-zhang Jul 22, 2024
9f5694e
apply ruff format
zezhong-zhang Jul 22, 2024
e48c06b
minor changes on the GOS _name and doc
zezhong-zhang Jul 23, 2024
9386834
apply ruff format
zezhong-zhang Jul 23, 2024
b7c4bff
add test for dirac gos
zezhong-zhang Jul 23, 2024
b9db10a
apply ruff format
zezhong-zhang Jul 23, 2024
adb65ac
add gos_file_path when append subshells
zezhong-zhang Jul 23, 2024
b58cd02
add dirac gos in test_eels_cl_edge
zezhong-zhang Jul 23, 2024
79f2ceb
remove the self.GOS reset as not necessary
zezhong-zhang Jul 23, 2024
155ea20
update the doi to the compact dirac gos
zezhong-zhang Jul 23, 2024
118aa78
add changelog
zezhong-zhang Jul 23, 2024
c13bfa8
update link in the doc and add example
zezhong-zhang Jul 23, 2024
4ad8634
remove extra title in the changelog
zezhong-zhang Jul 23, 2024
47a7e7c
Update upcoming_changes/72.new.rst
zezhong-zhang Jul 24, 2024
720075a
Update CHANGES.rst
zezhong-zhang Jul 24, 2024
dc71ae1
Update exspy/components/eels_cl_edge.py
zezhong-zhang Jul 24, 2024
fe6a99b
import warnings
zezhong-zhang Jul 24, 2024
971ef5b
test gosh redirect
zezhong-zhang Jul 24, 2024
5ce710b
Update exspy/tests/models/test_eelsmodel.py
zezhong-zhang Jul 27, 2024
3263d6e
Update exspy/misc/eels/gosh_gos.py
zezhong-zhang Jul 27, 2024
ff943f7
add missing import
zezhong-zhang Jul 27, 2024
7b84b5d
remove GOS.lower() as Hartree-Slater has upper case
zezhong-zhang Jul 27, 2024
c46b5d3
update gosh tests
zezhong-zhang Jul 27, 2024
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
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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A side note as this is outside of the scope of this PR: I find it confusing to have both GOS and gos_file_path. I am wondering if these should be merged together to make GOS accept one of the supported string ("dft", "dirac", etc.) or a path (which could a str or pathlib.Path`) in which case, it will try to read the file assuming this is gosh file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, this makes sense to me.

# 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":
zezhong-zhang marked this conversation as resolved.
Show resolved Hide resolved
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 = f"{source}_gosh"
zezhong-zhang marked this conversation as resolved.
Show resolved Hide resolved
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
6 changes: 4 additions & 2 deletions 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 @@ -1658,8 +1659,9 @@ def create_model(
low_loss=low_loss,
auto_background=auto_background,
auto_add_edges=auto_add_edges,
GOS=GOS,
GOS=GOS.lower(),
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")
33 changes: 29 additions & 4 deletions exspy/tests/models/test_eelsmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
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


Expand Down Expand Up @@ -73,19 +73,44 @@ def test_gos_hydrogenic(self):

def test_gos_gosh(self):
m = self.s.create_model(auto_add_edges=True, GOS="gosh")
zezhong-zhang marked this conversation as resolved.
Show resolved Hide resolved
assert m["B_K"].GOS._name == "gosh"
assert m["B_K"].GOS._name == "dft_gosh"
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_gosh"
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_gosh"
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
1 change: 1 addition & 0 deletions upcoming_changes/72.new.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support for `Dirac GOS <https://zenodo.org/records/12800856>`_ in the gosh format for EELS quantification, which includes the relativistic effects (e.g. spin-orbit coupling) based on Dirac equation.