Skip to content

Commit ab7ee22

Browse files
authored
Merge pull request #34 from DanPorter/32-feature-request-custom-atom-form-factors
32 feature request custom atom form factors
2 parents 5e8f3e4 + b39cc2c commit ab7ee22

20 files changed

+1890
-178
lines changed

Dans_Diffraction/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
Diamond
3232
2017-2025
3333
34-
Version 3.3.4
35-
Last updated: 12/04/2025
34+
Version 3.4.0
35+
Last updated: 05/08/2025
3636
3737
Version History:
3838
02/03/18 1.0 Version History started.
@@ -85,6 +85,7 @@
8585
20/11/24 3.3.2 Added alternate option for neutron scattering lengths
8686
06/02/25 3.3.3 Added scattering options for polarised neutron and x-ray scattering. Thanks dragonyanglong!
8787
12/04/25 3.3.4 Improved superstructure calculations by fixing scale parameter
88+
05/08/25 3.4.0 Added custom atomic form factors and dispersion corrections
8889
8990
Acknoledgements:
9091
2018 Thanks to Hepesu for help with Python3 support and ideas about breaking up calculations
@@ -112,6 +113,7 @@
112113
Sep 2024 Thanks to thamnos for suggestion to add complex neutron scattering lengths
113114
Oct 2024 Thanks to Lee Richter for pointing out the error in triclinic basis definition
114115
Dec 2024 Thanks to dragonyanglong for pointing out the error with magnetic neutron scattering
116+
May 2025 Thanks to vbhartiya for suggestions about magnetic neutron scattering
115117
116118
-----------------------------------------------------------------------------
117119
Copyright 2018-2025 Diamond Light Source Ltd.
@@ -166,8 +168,8 @@
166168
'Structures', 'Fdmnes', 'FdmnesAnalysis']
167169

168170

169-
__version__ = '3.3.3'
170-
__date__ = '2025/02/06'
171+
__version__ = '3.4.0'
172+
__date__ = '2025/08/05'
171173

172174

173175
# Build

Dans_Diffraction/classes_crystal.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
22/05/23 3.2.4 Added Symmetry.wyckoff_label(), Symmetry.spacegroup_dict
5252
06/05/24 3.3.0 Symmetry.from_cif now loads operations from find_spacegroup if not already loaded
5353
06/04/25 3.3.1 scale parameter of superlattice improved
54+
15/09/25 3.3.2 Atoms.type changed to always be array type
5455
5556
@author: DGPorter
5657
"""
@@ -1088,6 +1089,7 @@ class Atoms:
10881089
"_atom_site_fract_y",
10891090
"_atom_site_fract_z",
10901091
]
1092+
_type_str_fmt = '<U8'
10911093

10921094
def __init__(self, u=[0], v=[0], w=[0], type=None,
10931095
label=None, occupancy=None, uiso=None, mxmymz=None):
@@ -1100,9 +1102,9 @@ def __init__(self, u=[0], v=[0], w=[0], type=None,
11001102
# ---Defaults---
11011103
# type
11021104
if type is None:
1103-
self.type = np.asarray([self._default_atom] * Natoms)
1105+
self.type = np.asarray([self._default_atom] * Natoms, dtype=self._type_str_fmt)
11041106
else:
1105-
self.type = np.asarray(type, dtype=str).reshape(-1)
1107+
self.type = np.asarray(type, dtype=self._type_str_fmt).reshape(-1)
11061108
# label
11071109
if label is None:
11081110
self.label = self.type.copy()
@@ -1136,7 +1138,7 @@ def __call__(self, u=[0], v=[0], w=[0], type=None,
11361138

11371139
def __getitem__(self, idx):
11381140
if isinstance(idx, str):
1139-
idx = self.label.index(idx)
1141+
idx = list(self.label).index(idx)
11401142
return self.atom(idx)
11411143

11421144
def fromcif(self, cifvals):
@@ -1221,8 +1223,8 @@ def fromcif(self, cifvals):
12211223
self.u = u
12221224
self.v = v
12231225
self.w = w
1224-
self.type = element
1225-
self.label = label
1226+
self.type = np.array(element, dtype=self._type_str_fmt)
1227+
self.label = np.array(label, dtype=self._type_str_fmt)
12261228
self.occupancy = occ
12271229
self.uiso = uiso
12281230
self.mx = mx
@@ -1276,19 +1278,21 @@ def atom(self, idx):
12761278
return atoms[0]
12771279
return atoms
12781280

1279-
def changeatom(self, idx=None, u=None, v=None, w=None, type=None,
1281+
def changeatom(self, idx, u=None, v=None, w=None, type=None,
12801282
label=None, occupancy=None, uiso=None, mxmymz=None):
12811283
"""
1282-
Change an atoms properties
1283-
:param idx:
1284-
:param u:
1285-
:param v:
1286-
:param w:
1287-
:param type:
1288-
:param label:
1289-
:param occupancy:
1290-
:param uiso:
1291-
:param mxmymz:
1284+
Change an atom site's properties.
1285+
If properties are given as None, they are not changed.
1286+
1287+
:param idx: atom array index
1288+
:param u: atomic position u in relative coordinates along basis vector a
1289+
:param v: atomic position u in relative coordinates along basis vector b
1290+
:param w: atomic position u in relative coordinates along basis vector c
1291+
:param type: atomic element type
1292+
:param label: atomic site label
1293+
:param occupancy: atom site occupancy
1294+
:param uiso: atom site isotropic thermal parameter
1295+
:param mxmymz: atom site magnetic vector (mx, my, mz)
12921296
:return: None
12931297
"""
12941298

@@ -1307,7 +1311,7 @@ def changeatom(self, idx=None, u=None, v=None, w=None, type=None,
13071311
if label is not None:
13081312
old_labels = list(self.label)
13091313
old_labels[idx] = label
1310-
self.label = np.array(old_labels)
1314+
self.label = np.array(old_labels, dtype=self._type_str_fmt)
13111315

13121316
if occupancy is not None:
13131317
self.occupancy[idx] = occupancy
@@ -1433,11 +1437,11 @@ def remove_duplicates(self, min_distance=0.01, all_types=False):
14331437
"""
14341438

14351439
uvw = self.uvw()
1436-
type = self.type
1440+
atom_type = self.type
14371441
rem_atom_idx = []
1438-
for n in range(0, len(type) - 1):
1442+
for n in range(0, len(atom_type) - 1):
14391443
match_position = fg.mag(uvw[n, :] - uvw[n + 1:, :]) < min_distance
1440-
match_type = type[n] == type[n + 1:]
1444+
match_type = atom_type[n] == atom_type[n + 1:]
14411445
if all_types:
14421446
rem_atom_idx += list(1 + n + np.where(match_position)[0])
14431447
else:
@@ -1588,6 +1592,10 @@ def mass_fraction(self):
15881592

15891593
return weights / total_weight
15901594

1595+
def scattering_factor_coefficients(self, table='itc'):
1596+
"""Return scattering factor coefficients for the elements"""
1597+
return fc.scattering_factor_coefficients(*self.type, table=table)
1598+
15911599
def info(self, idx=None, type=None):
15921600
"""
15931601
Prints properties of all atoms

Dans_Diffraction/classes_plotting.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,45 @@ def plot_ms_azimuth(self, hkl, energy_kev, azir=[0, 0, 1], pv=[1, 0], numsteps=3
14571457
fp.labels(ttl, r'$\psi$ (deg)', 'Intensity')
14581458
#plt.subplots_adjust(bottom=0.2)
14591459

1460+
def plot_scattering_factors(self, q_max=4, energy_range=None, q_range=None):
1461+
"""
1462+
Plot atomic scattering factors across wavevector or energy
1463+
1464+
if q_range has more values than energy_range, figures will plot scattering factor vs Q
1465+
for each energy.
1466+
if energy_range has more values than q_range, figures will plot scattering factor vs energy
1467+
for each value of Q.
1468+
1469+
:param q_max: use a q_range of 0-q_max
1470+
:param energy_range: energy range in keV
1471+
:param q_range: range of wavecectors, or None to use q_max
1472+
:return: None
1473+
"""
1474+
1475+
if q_range is None:
1476+
q_range = np.arange(0, q_max, 0.01)
1477+
atom_types, atom_idx = np.unique(self.xtl.Structure.type, return_index=True)
1478+
atom_scattering_factors = self.xtl.Scatter.scattering_factors(qmag=q_range, energy_kev=energy_range)
1479+
ttl = f"{self.xtl.Scatter._scattering_type}"
1480+
# plot multiple figures of lower dimension
1481+
if atom_scattering_factors.shape[2] > atom_scattering_factors.shape[0]: # energy_range > q_range
1482+
# different figures for different values of Q
1483+
for n in range(atom_scattering_factors.shape[0]):
1484+
plt.figure(figsize=self._figure_size, dpi=self._figure_dpi)
1485+
nttl = ttl + f" Q={q_range[n]:.2g}" + r" $\AA^{-1}$"
1486+
for atom, idx in zip(atom_types, atom_idx):
1487+
plt.plot(energy_range, atom_scattering_factors[n, idx, :], label=atom)
1488+
fp.labels(nttl, 'Energy [keV]', 'Atomic scattering factor', legend=True)
1489+
else:
1490+
# different figures for different values of E
1491+
for n in range(atom_scattering_factors.shape[2]):
1492+
plt.figure(figsize=self._figure_size, dpi=self._figure_dpi)
1493+
nttl = ttl + (f" E = {energy_range[n]} keV" if atom_scattering_factors.shape[2] > 1 else "")
1494+
for atom, idx in zip(atom_types, atom_idx):
1495+
plt.plot(q_range, np.abs(atom_scattering_factors[:, idx, n]), label=atom)
1496+
fp.labels(nttl, r'Q $\AA^{-1}$', 'Atomic scattering factor', legend=True)
1497+
1498+
14601499
r''' Remove tensor_scattering 26/05/20
14611500
def tensor_scattering_azimuth(self, atom_label, hkl, energy_kev, azir=[0, 0, 1], process='E1E1',
14621501
rank=2, time=+1, parity=+1, mk=None, lk=None, sk=None):

Dans_Diffraction/classes_properties.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
from . import functions_general as fg
3333
from . import functions_crystallography as fc
34+
from . import functions_scattering as fs
3435
from .classes_orbitals import CrystalOrbitals
3536

3637
__version__ = '2.0'
@@ -119,7 +120,30 @@ def magnetic_form_factor(self, hkl):
119120
:return: [nxm] array of scattering factors for each atom and reflection
120121
"""
121122
qmag = self.xtl.Cell.Qmag(hkl)
122-
return fc.magnetic_form_factor(self.xtl.Structure.type, qmag)
123+
return fc.magnetic_form_factor(*self.xtl.Structure.type, qmag=qmag)
124+
125+
def scattering_factors(self, scattering_type, hkl, energy_kev=None,
126+
use_sears=False, use_wasskirf=False):
127+
"""
128+
Return an array of scattering factors based on the radiation
129+
:param scattering_type: str radiation, see "get_scattering_function()"
130+
:param hkl: [mx1] or None, float array of wavevector magnitudes for reflections
131+
:param energy_kev: [ox1] or None, float array of energies in keV
132+
:param use_sears: if True, use neutron scattering lengths from ITC Vol. C, By V. F. Sears
133+
:param use_wasskirf: if True, use x-ray scattering factors from Waasmaier and Kirfel
134+
:return: [nxmxo] array of scattering factors
135+
"""
136+
qmag = self.xtl.Cell.Qmag(hkl)
137+
# Scattering factors
138+
ff = fs.scattering_factors(
139+
scattering_type=scattering_type,
140+
atom_type=self.xtl.Structure.type,
141+
qmag=qmag,
142+
enval=energy_kev,
143+
use_sears=use_sears,
144+
use_wasskirf=use_wasskirf,
145+
)
146+
return np.squeeze(ff)
123147

124148
def xray_edges(self):
125149
"""

0 commit comments

Comments
 (0)