From 248b348381ce9a11b7c10fc057a1598202142fee Mon Sep 17 00:00:00 2001 From: Dieter Weber Date: Tue, 7 Oct 2025 14:25:25 +0200 Subject: [PATCH 1/6] Support CIF files from ICSD A recent CIF file pulled from https://icsd.fiz-karlsruhe.de/ for collection code 163723 contained the atom type symbol "Au0+" instead of just Au. This lead to empty diffraction patterns. With this change only the first alphabetical part of the atom type symbol is used as an element, and a warning is emitted if unknown elements are encountered. See https://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Iatom_type_symbol.html for the definition of the atom type symbol. It can be debated if unrecognized atom type symbols should actually be a hard error since the current code will quietly zero the contribution of sites with unknown atom types, which may lead to plausible but wrong diffraction patterns if recognized and unrecognized atom type symbols are mixed in a CIF file. --- .../atomic_scattering_parameters.py | 27 +++++++++++- .../structure_factor/test_structure_factor.py | 5 ++- diffsims/tests/utils/test_sim_utils.py | 44 +++++++++++++++++++ diffsims/utils/sim_utils.py | 19 +++++--- 4 files changed, 88 insertions(+), 7 deletions(-) diff --git a/diffsims/structure_factor/atomic_scattering_parameters.py b/diffsims/structure_factor/atomic_scattering_parameters.py index 34148a3c..ee62fb23 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 part of atom_type_symbol, notably without oxidation state. + Usually the element name, 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 @@ -203,8 +227,9 @@ def get_element_id_from_string(element_str): 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..b248aa04 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.), + (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 From 1ea2853cca68bbe242b4b03129d7edf714ccb596 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 12:43:22 +0000 Subject: [PATCH 2/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- diffsims/structure_factor/atomic_scattering_parameters.py | 4 ++-- diffsims/tests/utils/test_sim_utils.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/diffsims/structure_factor/atomic_scattering_parameters.py b/diffsims/structure_factor/atomic_scattering_parameters.py index ee62fb23..0d552535 100644 --- a/diffsims/structure_factor/atomic_scattering_parameters.py +++ b/diffsims/structure_factor/atomic_scattering_parameters.py @@ -160,8 +160,8 @@ def get_element(atom_type_symbol): Alphabetic part of atom_type_symbol, notably without oxidation state. Usually the element name, but not guaranteed by the specification. """ - reg = '^(?P[A-Za-z]*)' - return re.match(reg, atom_type_symbol).group('element') + reg = "^(?P[A-Za-z]*)" + return re.match(reg, atom_type_symbol).group("element") def get_atomic_scattering_parameters(element, unit=None): diff --git a/diffsims/tests/utils/test_sim_utils.py b/diffsims/tests/utils/test_sim_utils.py index b248aa04..ab9f693a 100644 --- a/diffsims/tests/utils/test_sim_utils.py +++ b/diffsims/tests/utils/test_sim_utils.py @@ -370,7 +370,7 @@ def test_get_kinematical_intensities(default_structure, scattering_params, answe @pytest.mark.parametrize( "scattering_params, answer", [ - ("lobato", 0.), + ("lobato", 0.0), (None, 1.0), ], ) @@ -383,7 +383,7 @@ def test_get_kinematical_intensities_unknown(scattering_params, answer): ) 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.'): + with pytest.warns(UserWarning, match="not found in scattering parameter library."): i_hkls = get_kinematical_intensities( unobtainium.structure, g_indices=unique_hkls, From b4e5d93897f1c650562334c0571ca98cc69c963b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Akon=20Wiik=20A=CC=8Anes?= Date: Tue, 7 Oct 2025 22:27:58 +0200 Subject: [PATCH 3/6] Skip incompatible numpydoc v1.9.0 for doc build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Håkon Wiik Ånes --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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", From 72d0b1eb569ffb510bca12ebd10784e6771634d1 Mon Sep 17 00:00:00 2001 From: Dieter Weber Date: Wed, 8 Oct 2025 09:28:34 +0200 Subject: [PATCH 4/6] Authorship This should be about right? --- .zenodo.json | 5 +++++ diffsims/release_info.py | 1 + 2 files changed, 6 insertions(+) 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/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", ] From 6938b3997dccf2b9badf0dd1e5fa945513a7bbc1 Mon Sep 17 00:00:00 2001 From: Dieter Weber Date: Wed, 8 Oct 2025 09:28:58 +0200 Subject: [PATCH 5/6] Adapt docstrings Specify more clearly what kind of strings are accepted, update to new feature of supporting more valid atom type symbols found in CIF files in the wild. --- .../atomic_scattering_parameters.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/diffsims/structure_factor/atomic_scattering_parameters.py b/diffsims/structure_factor/atomic_scattering_parameters.py index 0d552535..6286e1dc 100644 --- a/diffsims/structure_factor/atomic_scattering_parameters.py +++ b/diffsims/structure_factor/atomic_scattering_parameters.py @@ -157,8 +157,8 @@ def get_element(atom_type_symbol): ------- element : str - Alphabetic part of atom_type_symbol, notably without oxidation state. - Usually the element name, but not guaranteed by the specification. + 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") @@ -215,12 +215,17 @@ 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 ------- From f14d66ee34f3fda7f5ec9508f26f4a7cd8370e6b Mon Sep 17 00:00:00 2001 From: Dieter Weber Date: Wed, 8 Oct 2025 10:20:13 +0200 Subject: [PATCH 6/6] Changelog entry --- CHANGELOG.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 ==========================