diff --git a/.zenodo.json b/.zenodo.json index 54a92515..bdd52c45 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -63,6 +63,11 @@ { "name":"Tina Bergh" }, + { + "name":"Dieter Weber", + "orcid": "0000-0001-6635-9567", + "affiliation": "Forschungszentrum Jülich" + }, { "name":"Tomas Ostasevicius" }, diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a7709051..2c1f5f12 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,17 @@ The format is based on `Keep a Changelog `_ this project tries its best to adhere to `Semantic Versioning `_. +Unreleased +========== + +Added +----- + +- Support for wider range of `CIF atom type symbols + `_, + notably element symbol followed by oxidation number as found in CIF files + recently pulled from `ICSD `_ (#245). + 2025-06-02 - version 0.7.0 ========================== diff --git a/diffsims/release_info.py b/diffsims/release_info.py index bc78be2f..09215327 100644 --- a/diffsims/release_info.py +++ b/diffsims/release_info.py @@ -23,6 +23,7 @@ "Jedrzej Morzy", "Endre Jacobsen", "Tina Bergh", + "Dieter Weber", "Tomas Ostasevicius", "Eirik Opheim", ] diff --git a/diffsims/structure_factor/atomic_scattering_parameters.py b/diffsims/structure_factor/atomic_scattering_parameters.py index 34148a3c..6286e1dc 100644 --- a/diffsims/structure_factor/atomic_scattering_parameters.py +++ b/diffsims/structure_factor/atomic_scattering_parameters.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU General Public License # along with diffsims. If not, see . +import re + import numpy as np # List of elements Z = 1-118 @@ -140,6 +142,28 @@ # fmt: on +def get_element(atom_type_symbol): + """Extract element symbol from _atom_type_symbol CIF key + + See https://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Iatom_type_symbol.html + for specification. + + Parameters + ---------- + atom_type_symbol : str + Symbol following the specification of _atom_type_symbol CIF key + + Returns + ------- + + element : str + Alphabetic head of atom_type_symbol, notably without oxidation state. + Usually an element symbol, but not guaranteed by the specification. + """ + reg = "^(?P[A-Za-z]*)" + return re.match(reg, atom_type_symbol).group("element") + + def get_atomic_scattering_parameters(element, unit=None): """Return the eight atomic scattering parameters a_1-4, b_1-4 for elements with atomic numbers Z = 1-98 from Table 12.1 in @@ -191,20 +215,26 @@ def get_atomic_scattering_parameters(element, unit=None): def get_element_id_from_string(element_str): r"""Get periodic element ID for elements :math:`Z` = 1-98 from - one-two letter string. + element symbol. Parameters ---------- element_str : str - One-two letter string. + String starting with an element symbol, optionally delimited from other + following characters by a non-alphabetic character. Only the element + symbol is evaluated. In particular, compatible with common atom type + symbols in CIF files that often start with an element symbol followed by + e.g. an optional oxidation number, although this is not guaranteed: + https://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Iatom_type_symbol.html Returns ------- element_id : int Integer ID in the periodic table of elements. """ + element = get_element(element_str) element2periodic = dict(zip(ELEMENTS[:N_ELEMENTS], np.arange(1, N_ELEMENTS))) - element_id = element2periodic[element_str.lower()] + element_id = element2periodic[element.lower()] return element_id diff --git a/diffsims/tests/structure_factor/test_structure_factor.py b/diffsims/tests/structure_factor/test_structure_factor.py index 2f7af528..cecbdf5a 100644 --- a/diffsims/tests/structure_factor/test_structure_factor.py +++ b/diffsims/tests/structure_factor/test_structure_factor.py @@ -34,7 +34,10 @@ space_group=225, structure=Structure( lattice=Lattice(3.5236, 3.5236, 3.5236, 90, 90, 90), - atoms=[Atom(xyz=[0, 0, 0], atype="Ni", Uisoequiv=0.006332)], + # Atom specified in a way found in CIF files from ICSD + # See also + # https://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Iatom_type_symbol.html + atoms=[Atom(xyz=[0, 0, 0], atype="Ni0+", Uisoequiv=0.006332)], ), ) ferrite = Phase( diff --git a/diffsims/tests/utils/test_sim_utils.py b/diffsims/tests/utils/test_sim_utils.py index 27572e2e..ab9f693a 100644 --- a/diffsims/tests/utils/test_sim_utils.py +++ b/diffsims/tests/utils/test_sim_utils.py @@ -20,6 +20,9 @@ import numpy as np import diffpy +from diffpy.structure import Atom, Lattice, Structure +from orix.crystal_map import Phase + from diffsims.utils.sim_utils import ( get_electron_wavelength, @@ -348,3 +351,44 @@ def test_get_kinematical_intensities(default_structure, scattering_params, answe scattering_params=scattering_params, ) np.testing.assert_array_almost_equal(i_hkls, ([answer]), decimal=4) + + +# Copied from Nickel, with unknown element Unobtainium +# Arbitrary strings can be found in CIF files, see +# https://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Iatom_type_symbol.html +unobtainium = Phase( + space_group=225, + structure=Structure( + lattice=Lattice(3.5236, 3.5236, 3.5236, 90, 90, 90), + # Note that the standard explicitly shows "NiFe" and "dummy" + # as examples + atoms=[Atom(xyz=[0, 0, 0], atype="UnOb", Uisoequiv=0.006332)], + ), +) + + +@pytest.mark.parametrize( + "scattering_params, answer", + [ + ("lobato", 0.0), + (None, 1.0), + ], +) +def test_get_kinematical_intensities_unknown(scattering_params, answer): + latt = unobtainium.structure.lattice + reciprocal_lattice = latt.reciprocal() + reciprocal_radius = 0.2 + unique_hkls, multiplicites, g_hkls = get_intensities_params( + reciprocal_lattice, reciprocal_radius + ) + g_hkls_array = np.asarray(g_hkls) + # Debatable if this should actually be an error and not just a warning + with pytest.warns(UserWarning, match="not found in scattering parameter library."): + i_hkls = get_kinematical_intensities( + unobtainium.structure, + g_indices=unique_hkls, + g_hkls_array=g_hkls_array, + prefactor=multiplicites, + scattering_params=scattering_params, + ) + np.testing.assert_array_almost_equal(i_hkls, ([answer]), decimal=4) diff --git a/diffsims/utils/sim_utils.py b/diffsims/utils/sim_utils.py index 5bfcddb3..76118b78 100644 --- a/diffsims/utils/sim_utils.py +++ b/diffsims/utils/sim_utils.py @@ -18,6 +18,7 @@ import collections import math +from warnings import warn import diffpy.structure import numpy as np @@ -25,6 +26,7 @@ from diffsims.utils.atomic_scattering_params import ATOMIC_SCATTERING_PARAMS from diffsims.utils.lobato_scattering_params import ATOMIC_SCATTERING_PARAMS_LOBATO +from diffsims.structure_factor.atomic_scattering_parameters import get_element __all__ = [ @@ -194,22 +196,29 @@ def get_vectorized_list_for_atomic_scattering_factors( dwfactors : numpy.ndarray Debye-Waller factors for each atom in the structure. """ - if scattering_params is not None: scattering_params_dict = get_scattering_params_dict(scattering_params) else: scattering_params_dict = {} n_structures = len(structure) - coeffs = np.empty((n_structures, 5, 2)) + coeffs = np.zeros((n_structures, 5, 2)) fcoords = np.empty((n_structures, 3)) occus = np.empty(n_structures) dwfactors = np.empty(n_structures) - default = np.zeros((5, 2)) for i, site in enumerate(structure): - coeffs[i] = scattering_params_dict.get(site.element, default) - dwfactors[i] = debye_waller_factors.get(site.element, 0) + element = get_element(site.element) + # At least emit a warning if a key is not found. + # Probably better to error out since the simulation will be invalid. + try: + coeffs[i] = scattering_params_dict[element] + except KeyError: + warn( + f"Element {element} from atom type symbol {site.element} not " + "found in scattering parameter library." + ) + dwfactors[i] = debye_waller_factors.get(element, 0) fcoords[i] = site.xyz occus[i] = site.occupancy diff --git a/setup.py b/setup.py index 9356ebd3..f397bd8d 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,8 @@ # fmt: off extra_feature_requirements = { "doc": [ - "numpydoc", + # Restriction due to https://github.com/pyxem/orix/issues/570 + "numpydoc != 1.9.0", "pydata-sphinx-theme", "sphinx >= 3.0.2", "sphinx-design",