Coverage for local_installation/dynasor/post_processing/x_ray_form_factors.py: 90%
47 statements
« prev ^ index » next coverage.py v7.3.2, created at 2024-12-21 12:02 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2024-12-21 12:02 +0000
1import json
2from importlib.resources import files
3from typing import List, Dict
4from warnings import warn
6from numpy import exp, abs, pi
7from pandas import DataFrame, concat
8from .weights import Weights
11class XRayFormFactors(Weights):
12 r"""This class generates sample weights corresponding to X-ray form factors,
13 specifically the non-dispersion corrected parametrized form factors.
14 In general, the form factor for may be written as
16 .. math::
17 f(q, \omega) = f_0(q) + f'(q, \omega) + if''(q, \omega)
19 where :math:`q` is a scalar, corresponding to the norm
20 of the desired reciprocal lattice point.
22 The weights generated by this class corresponds to :math:`f_0(q)`.
23 There are two possible parametrizations of :math:`f_0(q)`
24 to choose from, both based on a sum of exponentials of the form
26 .. math::
28 f_0(q) = \sum_{i=1}^k a_i \exp(-b_i q^2) + c.
30 Two parametrizations are available:
32 * ``'waasmaier-1995'`` corresponds to Table 1 from D. Waasmaier, A. Kirfel,
33 Acta Crystallographica Section A **51**, 416 (1995);
34 `doi: 10.1107/S0108767394013292 <https://doi.org/10.1107/S0108767394013292>`_.
35 This parametrization uses five exponentials (:math:`k=5`) and extends up to
36 :math:`6.0\,\mathrm{Å}^{-1}`.
37 * ``'international-iv-1974'`` corresponds to Table 2.2B from
38 *International Tables for X-ray Crystallography, Vol. IV*,
39 The Kynoch Press: Birmingham, England, 1974. This parametrization uses
40 four exponentials (:math:`k=4`) and extends up to
41 :math:`2.0\,\mathrm{Å}^{-1}`.
43 In practice differences are expected to be insignificant. It is unlikely that
44 you have to deviate from the default, which is ``'waasmaier-1995'``.
46 Parameters
47 ----------
48 atom_types
49 List of atomic species for which to retrieve scattering lengths.
50 source
51 Source to use for parametrization of the form factors :math:`f_0(q)`.
52 Allowed values are ``'waasmaier-1995'`` and ``'international-iv-1974'``
53 (see above).
54 """
56 def __init__(
57 self,
58 atom_types: List[str],
59 source: str = 'waasmaier-1995'
60 ):
61 self._source = source
62 form_factors = self._read_form_factors(source)
63 # Select the relevant species
64 form_factors = form_factors[form_factors.index.isin(atom_types)] # Atom species is index
65 self._form_factors = form_factors
67 # Check if any of the fetched form factors are missing,
68 # indicating that it is missing in the experimental database.
69 for s in atom_types:
70 row = form_factors[form_factors.index == s]
71 if row.empty:
72 if s == 'H':
73 # Manually insert values for H such that the form factor is
74 # zero and raise a warning, since it is such a common element.
75 warn('No parametrization for H. Setting form factor for H to zero.')
76 values = [['DUMMY', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
77 form_factors = concat([DataFrame(data=values,
78 index=['H'],
79 columns=form_factors.columns),
80 form_factors])
81 continue
82 raise ValueError('Missing tabulated values '
83 f'for requested species {s}.')
85 weights_coh = form_factors.to_dict(orient='index')
86 supports_currents = False
87 super().__init__(weights_coh, None, supports_currents=supports_currents)
89 def get_weight_coh(self, atom_type, q_norm):
90 """Get the coherent weight for a given atom type and q-vector norm."""
91 return self._compute_f0(self._weights_coh[atom_type], q_norm, self._source)
93 def _compute_f0(
94 self,
95 coefficients: Dict,
96 q_norm: float,
97 source: str):
98 r"""Compute f_0(q) based on the chosen parametrization.
99 There are two possible parametrizations of f_0(q) to choose from,
100 both based on a sum of exponentials of the form
102 .. math::
104 f_0(q) = \sum_{i=1}^k a_i * exp(-b_i * q**2) + c
106 Parameters
107 ----------
108 coefficients
109 Parametrization parameters, read from the corresponding source file.
110 q_norm
111 The |q|-value at which to evaluate the form factor.
112 source
113 Either 'waasmaier-1995' or 'international-iv-1974',
114 corresponding to the two available sources for the
115 parametrization of the :math:`f_0(q)` term of the form factors.
116 """
117 if source == 'waasmaier-1995': 117 ↛ 133line 117 didn't jump to line 133, because the condition on line 117 was never false
118 s = q_norm / (4 * pi) # q in dynasor is q = 4 pi sin(theta) / lambda.
119 s_squared = s*s # s = sin(theta) / lambda in the Waasmaier paper.
120 if abs(s) > 6.0:
121 warn('Waasmaier parametrization is not reliable for q '
122 'above 75.398 rad/Å (corresponding to s=6.0 1/Å)')
123 # Very verbose and repetitive, but I think it is more readable
124 # than a reduction over the Series object.
125 # Suggestions for a nice & readable solution are welcome.
126 f0 = coefficients['c'] + \
127 coefficients['a1'] * exp(- coefficients['b1'] * s_squared) + \
128 coefficients['a2'] * exp(- coefficients['b2'] * s_squared) + \
129 coefficients['a3'] * exp(- coefficients['b3'] * s_squared) + \
130 coefficients['a4'] * exp(- coefficients['b4'] * s_squared) + \
131 coefficients['a5'] * exp(- coefficients['b5'] * s_squared)
132 return f0
133 elif source == 'international-iv-1974':
134 raise NotImplementedError('The ITC 1974 parametrization has not been implemented yet.')
135 else:
136 raise ValueError(f'Unknown source {source}')
138 def _read_form_factors(self, source: str) -> DataFrame:
139 r"""
140 Extracts the parametrization for the form factors :math:`f_0(q)`,
141 based on either of two sources.
143 Parameters
144 ----------
145 source
146 Either 'waasmaier-1995' or 'international-iv-1974',
147 corresponding to the two available sources for the
148 parametrization of the :math:`f_0(q)` term of the form factors.
149 """
150 if source == 'waasmaier-1995': 150 ↛ 158line 150 didn't jump to line 158, because the condition on line 150 was never false
151 data_file = files(__package__) / \
152 'form-factors/x-ray-parameters-waasmaier-kirfel-1995.json'
153 with open(data_file) as fp:
154 coefficients = json.load(fp)
156 form_factors = DataFrame.from_dict(coefficients)
157 form_factors.index.names = ['species']
158 elif source == 'international-iv-1974':
159 raise NotImplementedError('The ITC 1974 parametrization has not been implemented yet.')
160 else:
161 raise ValueError(f'Unknown source {source}')
163 return form_factors